/* * This file is part of the Colobot: Gold Edition source code * Copyright (C) 2001-2014, Daniel Roux, EPSITEC SA & TerranovaTeam * http://epsiteс.ch; http://colobot.info; http://github.com/colobot * * 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://gnu.org/licenses */ #include "object/old_object.h" #include "CBot/CBotDll.h" #include "app/app.h" #include "common/global.h" #include "common/make_unique.h" #include "common/stringutils.h" #include "graphics/engine/lightman.h" #include "graphics/engine/lightning.h" #include "graphics/engine/particle.h" #include "graphics/engine/pyro_manager.h" #include "graphics/engine/terrain.h" #include "math/geometry.h" #include "object/object_manager.h" #include "object/robotmain.h" #include "object/auto/auto.h" #include "object/auto/autojostle.h" #include "object/level/parserexceptions.h" #include "object/level/parserline.h" #include "object/level/parserparam.h" #include "object/motion/motion.h" #include "object/motion/motionvehicle.h" #include "object/task/taskmanager.h" #include "physics/physics.h" #include "script/cbottoken.h" #include "script/script.h" #include "script/scriptfunc.h" #include "ui/object_interface.h" #include "ui/studio.h" #include "ui/controls/edit.h" #include #include #define ADJUST_ONBOARD 0 // 1 -> adjusts the camera ONBOARD #define ADJUST_ARM 0 // 1 -> adjusts the manipulator arm const float VIRUS_DELAY = 60.0f; // duration of virus infection const float LOSS_SHIELD = 0.24f; // loss of the shield by shot const float LOSS_SHIELD_H = 0.10f; // loss of the shield for humans const float LOSS_SHIELD_M = 0.02f; // loss of the shield for the laying #if ADJUST_ONBOARD static float debug_x = 0.0f; static float debug_y = 0.0f; static float debug_z = 0.0f; #endif #if ADJUST_ARM static float debug_arm1 = 0.0f; static float debug_arm2 = 0.0f; static float debug_arm3 = 0.0f; #endif // Object's constructor. COldObject::COldObject(int id) : CObject(id, OBJECT_NULL) , CInteractiveObject(m_implementedInterfaces) , CTransportableObject(m_implementedInterfaces) , CTaskExecutorObjectImpl(m_implementedInterfaces, this) , CProgrammableObjectImpl(m_implementedInterfaces, this) , CJostleableObject(m_implementedInterfaces) , CCarrierObject(m_implementedInterfaces) , CPoweredObject(m_implementedInterfaces) , CMovableObject(m_implementedInterfaces) , CControllableObject(m_implementedInterfaces) , CPowerContainerObjectImpl(m_implementedInterfaces, this) { // A bit of a hack since we don't have subclasses yet, set externally in SetProgrammable() m_implementedInterfaces[static_cast(ObjectInterfaceType::Programmable)] = false; // Another hack m_implementedInterfaces[static_cast(ObjectInterfaceType::Jostleable)] = false; m_implementedInterfaces[static_cast(ObjectInterfaceType::Old)] = true; m_sound = CApplication::GetInstancePointer()->GetSound(); m_engine = Gfx::CEngine::GetInstancePointer(); m_lightMan = m_engine->GetLightManager(); m_particle = m_engine->GetParticle(); m_main = CRobotMain::GetInstancePointer(); m_terrain = m_main->GetTerrain(); m_camera = m_main->GetCamera(); m_type = OBJECT_FIX; m_option = 0; m_name = ""; m_shadowLight = -1; m_effectLight = -1; m_linVibration = Math::Vector(0.0f, 0.0f, 0.0f); m_cirVibration = Math::Vector(0.0f, 0.0f, 0.0f); m_tilt = Math::Vector(0.0f, 0.0f, 0.0f); m_power = 0; m_cargo = 0; m_transporter = 0; m_transporterLink = 0; m_shield = 1.0f; m_range = 0.0f; m_transparency = 0.0f; m_lastEnergy = 999.9f; m_bSelect = false; m_bSelectable = true; m_bCheckToken = true; m_bVisible = true; m_bTrainer = false; m_bToy = false; m_bManual = false; m_bFixed = false; m_showLimitRadius = 0.0f; m_aTime = 0.0f; m_shotTime = 0.0f; m_bVirusMode = false; m_virusTime = 0.0f; m_lastVirusParticle = 0.0f; m_bExplo = false; m_bCargo = false; m_bBurn = false; m_bDead = false; m_bFlat = false; m_gunGoalV = 0.0f; m_gunGoalH = 0.0f; m_shieldRadius = 0.0f; m_defRank = -1; m_magnifyDamage = 1.0f; m_param = 0.0f; m_infoReturn = NAN; m_character = Character(); m_character.wheelFront = 1.0f; m_character.wheelBack = 1.0f; m_character.wheelLeft = 1.0f; m_character.wheelRight = 1.0f; m_cameraType = Gfx::CAM_TYPE_BACK; m_cameraDist = 50.0f; m_bCameraLock = false; for (int i=0 ; iGetControllingObject() == this ) { m_camera->SetControllingObject(0); } m_main->RemoveFromSelectionHistory(this); if ( !bAll ) { #if 0 type = m_camera->GetType(); if ( (type == Gfx::CAM_TYPE_BACK || type == Gfx::CAM_TYPE_FIX || type == Gfx::CAM_TYPE_EXPLO || type == Gfx::CAM_TYPE_ONBOARD) && m_camera->GetControllingObject() == this ) { obj = m_main->SearchNearest(GetPosition(), this); if ( obj == 0 ) { m_camera->SetControllingObject(0); m_camera->SetType(Gfx::CAM_TYPE_FREE); } else { m_camera->SetControllingObject(obj); m_camera->SetType(Gfx::CAM_TYPE_BACK); } } #endif m_engine->GetPyroManager()->CutObjectLink(this); if ( m_bSelect ) { SetSelect(false); } if ( m_type == OBJECT_BASE || m_type == OBJECT_FACTORY || m_type == OBJECT_REPAIR || m_type == OBJECT_DESTROYER|| m_type == OBJECT_DERRICK || m_type == OBJECT_STATION || m_type == OBJECT_CONVERT || m_type == OBJECT_TOWER || m_type == OBJECT_RESEARCH || m_type == OBJECT_RADAR || m_type == OBJECT_INFO || m_type == OBJECT_ENERGY || m_type == OBJECT_LABO || m_type == OBJECT_NUCLEAR || m_type == OBJECT_PARA || m_type == OBJECT_SAFE || m_type == OBJECT_HUSTON || m_type == OBJECT_START || m_type == OBJECT_END ) // building? { m_terrain->DeleteBuildingLevel(GetPosition()); // flattens the field } } m_type = OBJECT_NULL; // invalid object until complete destruction if ( m_shadowLight != -1 ) { m_lightMan->DeleteLight(m_shadowLight); m_shadowLight = -1; } if ( m_effectLight != -1 ) { m_lightMan->DeleteLight(m_effectLight); m_effectLight = -1; } if ( m_physics != nullptr ) { m_physics->DeleteObject(bAll); } if ( m_objectInterface != nullptr ) { m_objectInterface->DeleteObject(bAll); } if ( m_motion != nullptr ) { m_motion->DeleteObject(bAll); } if ( m_auto != nullptr ) { m_auto->DeleteObject(bAll); } for (int i=0 ; iDeleteObject(m_objectPart[i].object); if ( m_objectPart[i].masterParti != -1 ) { m_particle->DeleteParticle(m_objectPart[i].masterParti); m_objectPart[i].masterParti = -1; } } } if ( !bAll ) m_main->CreateShortcuts(); } // Simplifies a object (he was the programmable, among others). void COldObject::Simplify() { if ( Implements(ObjectInterfaceType::Programmable) ) { StopProgram(); } m_main->SaveOneScript(this); m_implementedInterfaces[static_cast(ObjectInterfaceType::Programmable)] = false; if ( m_physics != nullptr ) { m_physics->DeleteObject(); m_physics.reset(); } if ( m_objectInterface != nullptr ) { m_objectInterface->DeleteObject(); m_objectInterface.reset(); } if ( m_motion != nullptr ) { m_motion->DeleteObject(); m_motion.reset(); } if ( m_auto != nullptr ) { m_auto->DeleteObject(); m_auto.reset(); } m_main->CreateShortcuts(); } // Detonates an object, when struck by a shot. // If false is returned, the object is still screwed. // If true is returned, the object is destroyed. bool COldObject::ExplodeObject(ExplosionType type, float force, float decay) { Gfx::PyroType pyroType; float loss, shield; if ( type == ExplosionType::Burn ) { if ( m_type == OBJECT_MOBILEtg || m_type == OBJECT_METAL || m_type == OBJECT_POWER || m_type == OBJECT_ATOMIC || m_type == OBJECT_TNT || m_type == OBJECT_SCRAP1 || m_type == OBJECT_SCRAP2 || m_type == OBJECT_SCRAP3 || m_type == OBJECT_SCRAP4 || m_type == OBJECT_SCRAP5 || m_type == OBJECT_BULLET || m_type == OBJECT_EGG ) // object that isn't burning? { type = ExplosionType::Bang; force = 1.0f; decay = 1.0f; } } if ( type == ExplosionType::Bang ) { if ( m_shotTime < 0.5f ) return false; m_shotTime = 0.0f; } if ( m_type == OBJECT_HUMAN && m_bDead ) return false; // Calculate the power lost by the explosion. if ( force == 0.0f ) { if ( m_type == OBJECT_HUMAN ) { loss = LOSS_SHIELD_H; } else if ( m_type == OBJECT_MOTHER ) { loss = LOSS_SHIELD_M; } else { loss = LOSS_SHIELD; } } else { loss = force; } loss *= m_magnifyDamage; loss *= m_main->GetGlobalMagnifyDamage(); loss *= decay; // Decreases the power of the shield. shield = GetShield(); shield -= loss; if ( shield < 0.0f ) shield = 0.0f; SetShield(shield); if ( shield > 0.0f ) // not dead yet? { if ( type == ExplosionType::Water ) { if ( m_type == OBJECT_HUMAN ) { pyroType = Gfx::PT_SHOTH; } else { pyroType = Gfx::PT_SHOTW; } } else { if ( m_type == OBJECT_HUMAN ) { pyroType = Gfx::PT_SHOTH; } else if ( m_type == OBJECT_MOTHER ) { pyroType = Gfx::PT_SHOTM; } else { pyroType = Gfx::PT_SHOTT; } } } else // completely dead? { if ( type == ExplosionType::Burn ) // burning? { if ( m_type == OBJECT_MOTHER || m_type == OBJECT_ANT || m_type == OBJECT_SPIDER || m_type == OBJECT_BEE || m_type == OBJECT_WORM || m_type == OBJECT_BULLET ) { pyroType = Gfx::PT_BURNO; SetBurn(true); } else if ( m_type == OBJECT_HUMAN ) { pyroType = Gfx::PT_DEADG; } else { pyroType = Gfx::PT_BURNT; SetBurn(true); } SetVirusMode(false); } else if ( type == ExplosionType::Water ) { if ( m_type == OBJECT_HUMAN ) { pyroType = Gfx::PT_DEADW; } else { pyroType = Gfx::PT_FRAGW; } } else // explosion? { if ( m_type == OBJECT_ANT || m_type == OBJECT_SPIDER || m_type == OBJECT_BEE || m_type == OBJECT_WORM ) { pyroType = Gfx::PT_EXPLOO; } else if ( m_type == OBJECT_MOTHER || m_type == OBJECT_NEST || m_type == OBJECT_BULLET ) { pyroType = Gfx::PT_FRAGO; } else if ( m_type == OBJECT_HUMAN ) { pyroType = Gfx::PT_DEADG; } else if ( m_type == OBJECT_BASE || m_type == OBJECT_DERRICK || m_type == OBJECT_FACTORY || m_type == OBJECT_STATION || m_type == OBJECT_CONVERT || m_type == OBJECT_REPAIR || m_type == OBJECT_DESTROYER|| m_type == OBJECT_TOWER || m_type == OBJECT_NEST || m_type == OBJECT_RESEARCH || m_type == OBJECT_RADAR || m_type == OBJECT_INFO || m_type == OBJECT_ENERGY || m_type == OBJECT_LABO || m_type == OBJECT_NUCLEAR || m_type == OBJECT_PARA || m_type == OBJECT_SAFE || m_type == OBJECT_HUSTON || m_type == OBJECT_START || m_type == OBJECT_END ) // building? { pyroType = Gfx::PT_FRAGT; } else if ( m_type == OBJECT_MOBILEtg ) { pyroType = Gfx::PT_FRAGT; } else { pyroType = Gfx::PT_EXPLOT; } } loss = 1.0f; } m_engine->GetPyroManager()->Create(pyroType, this, loss); if ( shield == 0.0f ) // dead? { if ( Implements(ObjectInterfaceType::Programmable) ) { StopProgram(); } m_main->SaveOneScript(this); } if ( shield > 0.0f ) return false; // not dead yet if ( GetSelect() ) { SetSelect(false); // deselects the object m_camera->SetType(Gfx::CAM_TYPE_EXPLO); m_main->DeselectAll(); } m_main->RemoveFromSelectionHistory(this); m_team = 0; // Back to neutral on destruction if ( m_botVar != nullptr ) { if ( m_type == OBJECT_STONE || m_type == OBJECT_URANIUM || m_type == OBJECT_METAL || m_type == OBJECT_POWER || m_type == OBJECT_ATOMIC || m_type == OBJECT_BULLET || m_type == OBJECT_BBOX || m_type == OBJECT_TNT || m_type == OBJECT_SCRAP1 || m_type == OBJECT_SCRAP2 || m_type == OBJECT_SCRAP3 || m_type == OBJECT_SCRAP4 || m_type == OBJECT_SCRAP5 ) // (*) { CScriptFunctions::DestroyObjectVar(m_botVar, false); } } return true; } // (*) If a robot or cosmonaut dies, the subject must continue to exist, // so that programs of the ants continue to operate as usual. // Initializes a new part. void COldObject::InitPart(int part) { m_objectPart[part].bUsed = true; m_objectPart[part].object = -1; m_objectPart[part].parentPart = -1; m_objectPart[part].position = Math::Vector(0.0f, 0.0f, 0.0f); m_objectPart[part].angle.y = 0.0f; m_objectPart[part].angle.x = 0.0f; m_objectPart[part].angle.z = 0.0f; m_objectPart[part].zoom = Math::Vector(1.0f, 1.0f, 1.0f); m_objectPart[part].bTranslate = true; m_objectPart[part].bRotate = true; m_objectPart[part].bZoom = false; m_objectPart[part].matTranslate.LoadIdentity(); m_objectPart[part].matRotate.LoadIdentity(); m_objectPart[part].matTransform.LoadIdentity(); m_objectPart[part].matWorld.LoadIdentity();; m_objectPart[part].masterParti = -1; } // Removes part. void COldObject::DeletePart(int part) { if ( !m_objectPart[part].bUsed ) return; if ( m_objectPart[part].masterParti != -1 ) { m_particle->DeleteParticle(m_objectPart[part].masterParti); m_objectPart[part].masterParti = -1; } m_objectPart[part].bUsed = false; m_engine->DeleteObject(m_objectPart[part].object); UpdateTotalPart(); } void COldObject::UpdateTotalPart() { int i; m_totalPart = 0; for ( i=0 ; iAddParam("camera", MakeUnique(GetCameraType())); if ( GetCameraLock() ) line->AddParam("cameraLock", MakeUnique(GetCameraLock())); if ( GetEnergyLevel() != 0.0f ) line->AddParam("energy", MakeUnique(GetEnergyLevel())); if ( GetShield() != 1.0f ) line->AddParam("shield", MakeUnique(GetShield())); if ( GetRange() != 1.0f ) line->AddParam("range", MakeUnique(GetRange())); if ( !GetSelectable() ) line->AddParam("selectable", MakeUnique(GetSelectable())); // TODO: doesn't seem to be used // But it is, this is used by aliens after Thumper ~krzys_h if ( GetFixed() ) line->AddParam("fixed", MakeUnique(GetFixed())); if ( !GetCollisions() ) line->AddParam("clip", MakeUnique(GetCollisions())); if ( GetLock() ) line->AddParam("lock", MakeUnique(GetLock())); if ( GetProxyActivate() ) { line->AddParam("proxyActivate", MakeUnique(GetProxyActivate())); line->AddParam("proxyDistance", MakeUnique(GetProxyDistance()/g_unit)); } if ( GetMagnifyDamage() != 1.0f ) line->AddParam("magnifyDamage", MakeUnique(GetMagnifyDamage())); if ( GetTeam() != 0 ) line->AddParam("team", MakeUnique(GetTeam())); if ( GetGunGoalV() != 0.0f ) line->AddParam("aimV", MakeUnique(GetGunGoalV())); if ( GetGunGoalH() != 0.0f ) line->AddParam("aimH", MakeUnique(GetGunGoalH())); if ( GetAnimateOnReset() ) { line->AddParam("resetCap", MakeUnique(GetAnimateOnReset())); } if ( m_bVirusMode ) line->AddParam("virusMode", MakeUnique(m_bVirusMode)); if ( m_virusTime != 0.0f ) line->AddParam("virusTime", MakeUnique(m_virusTime)); // Sets the parameters of the command line. CLevelParserParamVec cmdline; for(float value : GetCmdLine()) { cmdline.push_back(MakeUnique(value)); } if (cmdline.size() > 0) line->AddParam("cmdline", MakeUnique(std::move(cmdline))); if ( m_motion != nullptr ) { m_motion->Write(line); } if ( Implements(ObjectInterfaceType::Programmable) ) { line->AddParam("bVirusActive", MakeUnique(GetActiveVirus())); } if ( Implements(ObjectInterfaceType::TaskExecutor) ) { if ( m_type == OBJECT_MOBILErs ) { line->AddParam("bShieldActive", MakeUnique(IsBackgroundTask())); } } if ( m_physics != nullptr ) { m_physics->Write(line); } if ( m_auto != nullptr ) { m_auto->Write(line); } } // Returns all parameters of the object. void COldObject::Read(CLevelParserLine* line) { Gfx::CameraType cType = line->GetParam("camera")->AsCameraType(Gfx::CAM_TYPE_NULL); if ( cType != Gfx::CAM_TYPE_NULL ) { SetCameraType(cType); } SetCameraDist(line->GetParam("cameraDist")->AsFloat(50.0f)); SetCameraLock(line->GetParam("cameraLock")->AsBool(false)); SetEnergyLevel(line->GetParam("energy")->AsFloat(0.0f)); SetShield(line->GetParam("shield")->AsFloat(1.0f)); SetRange(line->GetParam("range")->AsFloat(1.0f)); SetSelectable(line->GetParam("selectable")->AsBool(true)); SetFixed(line->GetParam("fixed")->AsBool(false)); SetCollisions(line->GetParam("clip")->AsBool(true)); SetLock(line->GetParam("lock")->AsBool(false)); SetProxyActivate(line->GetParam("proxyActivate")->AsBool(false)); SetProxyDistance(line->GetParam("proxyDistance")->AsFloat(15.0f)*g_unit); SetRange(line->GetParam("range")->AsFloat(30.0f)); SetMagnifyDamage(line->GetParam("magnifyDamage")->AsFloat(1.0f)); SetGunGoalV(line->GetParam("aimV")->AsFloat(0.0f)); SetGunGoalH(line->GetParam("aimH")->AsFloat(0.0f)); SetAnimateOnReset(line->GetParam("resetCap")->AsBool(false)); m_bBurn = line->GetParam("burnMode")->AsBool(false); m_bVirusMode = line->GetParam("virusMode")->AsBool(false); m_virusTime = line->GetParam("virusTime")->AsFloat(0.0f); // Sets the parameters of the command line. if (line->GetParam("cmdline")->IsDefined()) { int i = 0; for (auto& p : line->GetParam("cmdline")->AsArray()) { SetCmdLine(i, p->AsFloat()); i++; } } if ( m_motion != nullptr ) { m_motion->Read(line); } if (Implements(ObjectInterfaceType::Programmable)) { SetActiveVirus(line->GetParam("bVirusActive")->AsBool(false)); } if (Implements(ObjectInterfaceType::TaskExecutor)) { if ( m_type == OBJECT_MOBILErs ) { if( line->GetParam("bShieldActive")->AsBool(false) ) { StartTaskShield(TSM_START); } } } if ( m_physics != nullptr ) { m_physics->Read(line); } if ( m_auto != nullptr ) { m_auto->Read(line); } } // Seeking the nth son of a father. int COldObject::SearchDescendant(int parent, int n) { int i; for ( i=0 ; i(ObjectInterfaceType::Jostleable)] = true; } // Specifies the sphere of jostling, in the world. Math::Sphere COldObject::GetJostlingSphere() const { Math::Sphere transformedJostlingSphere = m_jostlingSphere; transformedJostlingSphere.pos = Math::Transform(m_objectPart[0].matWorld, transformedJostlingSphere.pos); return transformedJostlingSphere; } // Specifies the radius of the shield. void COldObject::SetShieldRadius(float radius) { m_shieldRadius = radius; } // Returns the radius of the shield. float COldObject::GetShieldRadius() { return m_shieldRadius; } // Positioning an object on a certain height, above the ground. void COldObject::SetFloorHeight(float height) { Math::Vector pos; pos = m_objectPart[0].position; m_terrain->AdjustToFloor(pos); if ( m_physics != nullptr ) { m_physics->SetLand(height == 0.0f); m_physics->SetMotor(height != 0.0f); } m_objectPart[0].position.y = pos.y+height+m_character.height; m_objectPart[0].bTranslate = true; // it will recalculate the matrices } // Adjust the inclination of an object laying on the ground. void COldObject::FloorAdjust() { Math::Vector pos, n; Math::Point nn; float a; pos = GetPosition(); if ( m_terrain->GetNormal(n, pos) ) { #if 0 SetRotationX( sinf(n.z)); SetRotationZ(-sinf(n.x)); SetRotationY(0.0f); #else a = GetRotationY(); nn = Math::RotatePoint(-a, Math::Point(n.z, n.x)); SetRotationX( sinf(nn.x)); SetRotationZ(-sinf(nn.y)); #endif } } // Getes the linear vibration. void COldObject::SetLinVibration(Math::Vector dir) { if ( m_linVibration.x != dir.x || m_linVibration.y != dir.y || m_linVibration.z != dir.z ) { m_linVibration = dir; m_objectPart[0].bTranslate = true; } } Math::Vector COldObject::GetLinVibration() { return m_linVibration; } // Getes the circular vibration. void COldObject::SetCirVibration(Math::Vector dir) { if ( m_cirVibration.x != dir.x || m_cirVibration.y != dir.y || m_cirVibration.z != dir.z ) { m_cirVibration = dir; m_objectPart[0].bRotate = true; } } Math::Vector COldObject::GetCirVibration() { return m_cirVibration; } // Getes the inclination. void COldObject::SetTilt(Math::Vector dir) { if ( m_tilt.x != dir.x || m_tilt.y != dir.y || m_tilt.z != dir.z ) { m_tilt = dir; m_objectPart[0].bRotate = true; } } Math::Vector COldObject::GetTilt() { return m_tilt; } // Getes the position of center of the object. void COldObject::SetPartPosition(int part, const Math::Vector &pos) { m_objectPart[part].position = pos; m_objectPart[part].bTranslate = true; // it will recalculate the matrices if ( part == 0 && !m_bFlat ) // main part? { int rank = m_objectPart[0].object; Math::Vector shPos = pos; m_terrain->AdjustToFloor(shPos, true); m_engine->SetObjectShadowPos(rank, shPos); float height = 0.0f; if ( m_physics != nullptr && m_physics->GetType() == TYPE_FLYING ) { height = pos.y-shPos.y; } m_engine->SetObjectShadowHeight(rank, height); m_engine->UpdateObjectShadowNormal(rank); if ( m_shadowLight != -1 ) { Math::Vector lightPos = pos; lightPos.y += m_shadowHeight; m_lightMan->SetLightPos(m_shadowLight, lightPos); } if ( m_effectLight != -1 ) { Math::Vector lightPos = pos; lightPos.y += m_effectHeight; m_lightMan->SetLightPos(m_effectLight, lightPos); } } } Math::Vector COldObject::GetPartPosition(int part) const { return m_objectPart[part].position; } // Getes the rotation around three axis. void COldObject::SetPartRotation(int part, const Math::Vector &angle) { m_objectPart[part].angle = angle; m_objectPart[part].bRotate = true; // it will recalculate the matrices if ( part == 0 && !m_bFlat ) // main part? { m_engine->SetObjectShadowAngle(m_objectPart[0].object, m_objectPart[0].angle.y); } } Math::Vector COldObject::GetPartRotation(int part) const { return m_objectPart[part].angle; } // Getes the rotation about the axis Y. void COldObject::SetPartRotationY(int part, float angle) { m_objectPart[part].angle.y = angle; m_objectPart[part].bRotate = true; // it will recalculate the matrices if ( part == 0 && !m_bFlat ) // main part? { m_engine->SetObjectShadowAngle(m_objectPart[0].object, m_objectPart[0].angle.y); } } // Getes the rotation about the axis X. void COldObject::SetPartRotationX(int part, float angle) { m_objectPart[part].angle.x = angle; m_objectPart[part].bRotate = true; // it will recalculate the matrices } // Getes the rotation about the axis Z. void COldObject::SetPartRotationZ(int part, float angle) { m_objectPart[part].angle.z = angle; m_objectPart[part].bRotate = true; //it will recalculate the matrices } float COldObject::GetPartRotationY(int part) { return m_objectPart[part].angle.y; } float COldObject::GetPartRotationX(int part) { return m_objectPart[part].angle.x; } float COldObject::GetPartRotationZ(int part) { return m_objectPart[part].angle.z; } // Getes the global zoom. void COldObject::SetPartScale(int part, float zoom) { m_objectPart[part].bTranslate = true; // it will recalculate the matrices m_objectPart[part].zoom.x = zoom; m_objectPart[part].zoom.y = zoom; m_objectPart[part].zoom.z = zoom; m_objectPart[part].bZoom = ( m_objectPart[part].zoom.x != 1.0f || m_objectPart[part].zoom.y != 1.0f || m_objectPart[part].zoom.z != 1.0f ); } void COldObject::SetPartScale(int part, Math::Vector zoom) { m_objectPart[part].bTranslate = true; // it will recalculate the matrices m_objectPart[part].zoom = zoom; m_objectPart[part].bZoom = ( m_objectPart[part].zoom.x != 1.0f || m_objectPart[part].zoom.y != 1.0f || m_objectPart[part].zoom.z != 1.0f ); } Math::Vector COldObject::GetPartScale(int part) const { return m_objectPart[part].zoom; } void COldObject::SetPartScaleX(int part, float zoom) { m_objectPart[part].bTranslate = true; // it will recalculate the matrices m_objectPart[part].zoom.x = zoom; m_objectPart[part].bZoom = ( m_objectPart[part].zoom.x != 1.0f || m_objectPart[part].zoom.y != 1.0f || m_objectPart[part].zoom.z != 1.0f ); } void COldObject::SetPartScaleY(int part, float zoom) { m_objectPart[part].bTranslate = true; // it will recalculate the matrices m_objectPart[part].zoom.y = zoom; m_objectPart[part].bZoom = ( m_objectPart[part].zoom.x != 1.0f || m_objectPart[part].zoom.y != 1.0f || m_objectPart[part].zoom.z != 1.0f ); } void COldObject::SetPartScaleZ(int part, float zoom) { m_objectPart[part].bTranslate = true; // it will recalculate the matrices m_objectPart[part].zoom.z = zoom; m_objectPart[part].bZoom = ( m_objectPart[part].zoom.x != 1.0f || m_objectPart[part].zoom.y != 1.0f || m_objectPart[part].zoom.z != 1.0f ); } float COldObject::GetPartScaleX(int part) { return m_objectPart[part].zoom.x; } float COldObject::GetPartScaleY(int part) { return m_objectPart[part].zoom.y; } float COldObject::GetPartScaleZ(int part) { return m_objectPart[part].zoom.z; } void COldObject::SetTrainer(bool bEnable) { m_bTrainer = bEnable; if ( m_bTrainer ) // training? { m_cameraType = Gfx::CAM_TYPE_FIX; } } bool COldObject::GetTrainer() { return m_bTrainer; } void COldObject::SetToy(bool bEnable) { m_bToy = bEnable; } bool COldObject::GetToy() { return m_bToy; } void COldObject::SetManual(bool bManual) { m_bManual = bManual; } bool COldObject::GetManual() { return m_bManual; } // Management of the particle master. void COldObject::SetMasterParticle(int part, int parti) { m_objectPart[part].masterParti = parti; } // Management of the stack transport. void COldObject::SetPower(CObject* power) { m_power = power; } CObject* COldObject::GetPower() { return m_power; } // Management of the object transport. void COldObject::SetCargo(CObject* cargo) { m_cargo = cargo; } CObject* COldObject::GetCargo() { return m_cargo; } // Management of the object "transporter" that transports it. void COldObject::SetTransporter(CObject* transporter) { m_transporter = transporter; // Invisible shadow if the object is transported. m_engine->SetObjectShadowHide(m_objectPart[0].object, (m_transporter != 0)); } CObject* COldObject::GetTransporter() { return m_transporter; } // Management of the conveying portion. void COldObject::SetTransporterPart(int part) { m_transporterLink = part; } void COldObject::SetInfoReturn(float value) { m_infoReturn = value; } float COldObject::GetInfoReturn() { return m_infoReturn; } // Returns matrices of an object portion. Math::Matrix* COldObject::GetRotateMatrix(int part) { return &m_objectPart[part].matRotate; } Math::Matrix* COldObject::GetWorldMatrix(int part) { if ( m_objectPart[0].bTranslate || m_objectPart[0].bRotate ) { UpdateTransformObject(); } return &m_objectPart[part].matWorld; } // Indicates whether the object should be drawn over the interface. void COldObject::SetDrawFront(bool bDraw) { int i; for ( i=0 ; iSetObjectDrawFront(m_objectPart[i].object, bDraw); } } } // Creates shade under a vehicle as a negative light. bool COldObject::CreateShadowLight(float height, Gfx::Color color) { if ( !m_engine->GetLightMode() ) return true; Math::Vector pos = GetPosition(); m_shadowHeight = height; Gfx::Light light; light.type = Gfx::LIGHT_SPOT; light.diffuse = color; light.ambient = color * 0.1f; light.position = Math::Vector(pos.x, pos.y+height, pos.z); light.direction = Math::Vector(0.0f, -1.0f, 0.0f); // against the bottom light.spotIntensity = 128; light.attenuation0 = 1.0f; light.attenuation1 = 0.0f; light.attenuation2 = 0.0f; light.spotAngle = 90.0f*Math::PI/180.0f; m_shadowLight = m_lightMan->CreateLight(); if ( m_shadowLight == -1 ) return false; m_lightMan->SetLight(m_shadowLight, light); // Only illuminates the objects on the ground. m_lightMan->SetLightIncludeType(m_shadowLight, Gfx::ENG_OBJTYPE_TERRAIN); return true; } // Returns the number of negative light shade. int COldObject::GetShadowLight() { return m_shadowLight; } // Creates light for the effects of a vehicle. bool COldObject::CreateEffectLight(float height, Gfx::Color color) { if ( !m_engine->GetLightMode() ) return true; m_effectHeight = height; Gfx::Light light; light.type = Gfx::LIGHT_SPOT; light.diffuse = color; light.position = Math::Vector(0.0f, height, 0.0f); light.direction = Math::Vector(0.0f, -1.0f, 0.0f); // against the bottom light.spotIntensity = 0.0f; light.attenuation0 = 1.0f; light.attenuation1 = 0.0f; light.attenuation2 = 0.0f; light.spotAngle = 90.0f*Math::PI/180.0f; m_effectLight = m_lightMan->CreateLight(); if ( m_effectLight == -1 ) return false; m_lightMan->SetLight(m_effectLight, light); m_lightMan->SetLightIntensity(m_effectLight, 0.0f); return true; } // Returns the number of light effects. int COldObject::GetEffectLight() { return m_effectLight; } // Creates the circular shadow underneath a vehicle. bool COldObject::CreateShadowCircle(float radius, float intensity, Gfx::EngineShadowType type) { float zoom; if ( intensity == 0.0f ) return true; zoom = GetScaleX(); m_engine->CreateShadow(m_objectPart[0].object); m_engine->SetObjectShadowRadius(m_objectPart[0].object, radius*zoom); m_engine->SetObjectShadowIntensity(m_objectPart[0].object, intensity); m_engine->SetObjectShadowHeight(m_objectPart[0].object, 0.0f); m_engine->SetObjectShadowAngle(m_objectPart[0].object, m_objectPart[0].angle.y); m_engine->SetObjectShadowType(m_objectPart[0].object, type); return true; } // Calculates the matrix for transforming the object. // Returns true if the matrix has changed. // The rotations occur in the order Y, Z and X. bool COldObject::UpdateTransformObject(int part, bool bForceUpdate) { Math::Vector position, angle, eye; bool bModif = false; int parent; if ( m_transporter != 0 ) // transported by transporter? { m_objectPart[part].bTranslate = true; m_objectPart[part].bRotate = true; } if ( !bForceUpdate && !m_objectPart[part].bTranslate && !m_objectPart[part].bRotate ) return false; position = m_objectPart[part].position; angle = m_objectPart[part].angle; if ( part == 0 ) // main part? { position += m_linVibration; angle += m_cirVibration+m_tilt; } if ( m_objectPart[part].bTranslate || m_objectPart[part].bRotate ) { if ( m_objectPart[part].bTranslate ) { m_objectPart[part].matTranslate.LoadIdentity(); m_objectPart[part].matTranslate.Set(1, 4, position.x); m_objectPart[part].matTranslate.Set(2, 4, position.y); m_objectPart[part].matTranslate.Set(3, 4, position.z); } if ( m_objectPart[part].bRotate ) { Math::LoadRotationZXYMatrix(m_objectPart[part].matRotate, angle); } if ( m_objectPart[part].bZoom ) { Math::Matrix mz; mz.LoadIdentity(); mz.Set(1, 1, m_objectPart[part].zoom.x); mz.Set(2, 2, m_objectPart[part].zoom.y); mz.Set(3, 3, m_objectPart[part].zoom.z); m_objectPart[part].matTransform = Math::MultiplyMatrices(m_objectPart[part].matTranslate, Math::MultiplyMatrices(m_objectPart[part].matRotate, mz)); } else { m_objectPart[part].matTransform = Math::MultiplyMatrices(m_objectPart[part].matTranslate, m_objectPart[part].matRotate); } bModif = true; } if ( bForceUpdate || m_objectPart[part].bTranslate || m_objectPart[part].bRotate ) { parent = m_objectPart[part].parentPart; if ( part == 0 && m_transporter != 0 ) // transported by a transporter? { Math::Matrix* matWorldTransporter; matWorldTransporter = m_transporter->GetWorldMatrix(m_transporterLink); m_objectPart[part].matWorld = Math::MultiplyMatrices(*matWorldTransporter, m_objectPart[part].matTransform); } else { if ( parent == -1 ) // no parent? { m_objectPart[part].matWorld = m_objectPart[part].matTransform; } else { m_objectPart[part].matWorld = Math::MultiplyMatrices(m_objectPart[parent].matWorld, m_objectPart[part].matTransform); } } bModif = true; } if ( bModif ) { m_engine->SetObjectTransform(m_objectPart[part].object, m_objectPart[part].matWorld); } m_objectPart[part].bTranslate = false; m_objectPart[part].bRotate = false; return bModif; } // Updates all matrices to transform the object father and all his sons. // Assume a maximum of 4 degrees of freedom. // Appropriate, for example, to a body, an arm, forearm, hand and fingers. bool COldObject::UpdateTransformObject() { bool bUpdate1, bUpdate2, bUpdate3, bUpdate4; int level1, level2, level3, level4, rank; int parent1, parent2, parent3, parent4; if ( m_bFlat ) { for ( level1=0 ; level1(GetTeam()); if(GetTeam() == 0) teamStr = ""; m_engine->ChangeTextureMapping(m_objectPart[0].object, mat, Gfx::ENG_RSTATE_PART3, "objects/lemt.png"+teamStr, "", Gfx::ENG_TEX_MAPPING_1Y, au, bu, 1.0f, 0.0f); } // Manual action. bool COldObject::EventProcess(const Event &event) { if ( event.type == EVENT_KEY_DOWN ) { #if ADJUST_ONBOARD if ( m_bSelect ) { if ( event.param == 'E' ) debug_x += 0.1f; if ( event.param == 'D' ) debug_x -= 0.1f; if ( event.param == 'R' ) debug_y += 0.1f; if ( event.param == 'F' ) debug_y -= 0.1f; if ( event.param == 'T' ) debug_z += 0.1f; if ( event.param == 'G' ) debug_z -= 0.1f; } #endif #if ADJUST_ARM if ( m_bSelect ) { if ( event.param == 'X' ) debug_arm1 += 5.0f*Math::PI/180.0f; if ( event.param == 'C' ) debug_arm1 -= 5.0f*Math::PI/180.0f; if ( event.param == 'V' ) debug_arm2 += 5.0f*Math::PI/180.0f; if ( event.param == 'B' ) debug_arm2 -= 5.0f*Math::PI/180.0f; if ( event.param == 'N' ) debug_arm3 += 5.0f*Math::PI/180.0f; if ( event.param == 'M' ) debug_arm3 -= 5.0f*Math::PI/180.0f; if ( event.param == 'X' || event.param == 'C' || event.param == 'V' || event.param == 'B' || event.param == 'N' || event.param == 'M' ) { SetPartRotationZ(1, debug_arm1); SetPartRotationZ(2, debug_arm2); SetPartRotationZ(3, debug_arm3); char s[100]; sprintf(s, "a=%.2f b=%.2f c=%.2f", debug_arm1*180.0f/Math::PI, debug_arm2*180.0f/Math::PI, debug_arm3*180.0f/Math::PI); m_engine->SetInfoText(5, s); } } #endif } // NOTE: This should be called befoce CProgrammableObjectImpl::EventProcess, see the other note inside this function if (!CTaskExecutorObjectImpl::EventProcess(event)) return true; if ( m_physics != nullptr ) { if ( !m_physics->EventProcess(event) ) // object destroyed? { if ( GetSelect() && m_type != OBJECT_ANT && m_type != OBJECT_SPIDER && m_type != OBJECT_BEE ) { if ( !m_bDead ) m_camera->SetType(Gfx::CAM_TYPE_EXPLO); m_main->DeselectAll(); } return false; } } if (Implements(ObjectInterfaceType::Movable) && m_physics != nullptr) { bool deselectedStop = !GetSelect(); if (Implements(ObjectInterfaceType::Programmable)) { deselectedStop = deselectedStop && !IsProgram(); } if (Implements(ObjectInterfaceType::TaskExecutor)) { deselectedStop = deselectedStop && !IsForegroundTask(); } if ( deselectedStop ) { float axeX = 0.0f; float axeY = 0.0f; float axeZ = 0.0f; if ( m_bBurn ) // Gifted? { axeZ = -1.0f; // tomb if ( !GetFixed() && (m_type == OBJECT_ANT || m_type == OBJECT_SPIDER || m_type == OBJECT_WORM ) ) { axeY = 2.0f; // zigzag disorganized fast if ( m_type == OBJECT_WORM ) axeY = 5.0f; axeX = 0.5f+sinf(m_time* 1.0f)*0.5f+ sinf(m_time* 6.0f)*2.0f+ sinf(m_time*21.0f)*0.2f; float factor = 1.0f-m_burnTime/15.0f; // slow motion if ( factor < 0.0f ) factor = 0.0f; axeY *= factor; axeX *= factor; } } m_physics->SetMotorSpeedX(axeY); // move forward/move back m_physics->SetMotorSpeedY(axeZ); // up / down m_physics->SetMotorSpeedZ(axeX); // rotate } else if (GetSelect()) { bool canMove = true; if (Implements(ObjectInterfaceType::Programmable)) { canMove = canMove && !IsProgram(); } if (Implements(ObjectInterfaceType::TaskExecutor)) { canMove = canMove || (IsForegroundTask() && GetForegroundTask()->IsPilot()); } if ( canMove ) { if ( event.type == EVENT_OBJECT_LEFT || event.type == EVENT_OBJECT_RIGHT || event.type == EVENT_OBJECT_UP || event.type == EVENT_OBJECT_DOWN || event.type == EVENT_OBJECT_GASUP || event.type == EVENT_OBJECT_GASDOWN ) { m_buttonAxe = event.type; } if ( event.type == EVENT_MOUSE_BUTTON_UP ) { m_buttonAxe = EVENT_NULL; } float axeX = event.motionInput.x; float axeY = event.motionInput.y; float axeZ = event.motionInput.z; if ( (!m_main->GetTrainerPilot() && GetTrainer()) || !m_main->CanPlayerInteract() ) // drive vehicle? { axeX = 0.0f; axeY = 0.0f; axeZ = 0.0f; // Remote control impossible! } if ( m_buttonAxe == EVENT_OBJECT_LEFT ) axeX = -1.0f; if ( m_buttonAxe == EVENT_OBJECT_RIGHT ) axeX = 1.0f; if ( m_buttonAxe == EVENT_OBJECT_UP ) axeY = 1.0f; if ( m_buttonAxe == EVENT_OBJECT_DOWN ) axeY = -1.0f; if ( m_buttonAxe == EVENT_OBJECT_GASUP ) axeZ = 1.0f; if ( m_buttonAxe == EVENT_OBJECT_GASDOWN ) axeZ = -1.0f; if ( m_type == OBJECT_MOBILEdr && GetManual() ) // scribbler in manual mode? { if ( axeX != 0.0f ) axeY = 0.0f; // if running -> not moving! axeX *= 0.5f; axeY *= 0.5f; } if ( !m_main->IsResearchDone(RESEARCH_FLY, GetTeam()) ) { axeZ = -1.0f; // tomb } axeX += m_camera->GetMotorTurn(); // additional power according to camera if ( axeX > 1.0f ) axeX = 1.0f; if ( axeX < -1.0f ) axeX = -1.0f; m_physics->SetMotorSpeedX(axeY); // move forward/move back m_physics->SetMotorSpeedY(axeZ); // up/down m_physics->SetMotorSpeedZ(axeX); // rotate } } } if ( m_objectInterface != nullptr ) { m_objectInterface->EventProcess(event); } if ( m_auto != nullptr ) { m_auto->EventProcess(event); if ( event.type == EVENT_FRAME && m_auto->IsEnded() != ERR_CONTINUE ) { m_auto->DeleteObject(); m_auto.reset(); } } if ( m_motion != nullptr ) { if (!m_motion->EventProcess(event)) return false; } if (!CProgrammableObjectImpl::EventProcess(event)) return true; if ( event.type == EVENT_FRAME ) { return EventFrame(event); } return true; } // Animates the object. bool COldObject::EventFrame(const Event &event) { if ( m_type == OBJECT_HUMAN && m_main->GetMainMovie() == MM_SATCOMopen ) { UpdateTransformObject(); return true; } m_time += event.rTime; if ( m_engine->GetPause() && m_type != OBJECT_SHOW ) return true; if ( m_bBurn ) m_burnTime += event.rTime; m_aTime += event.rTime; m_shotTime += event.rTime; VirusFrame(event.rTime); PartiFrame(event.rTime); UpdateMapping(); UpdateTransformObject(); UpdateSelectParticle(); return true; } // Updates the mapping of the object. void COldObject::UpdateMapping() { if ( m_type == OBJECT_POWER || m_type == OBJECT_ATOMIC || m_type == OBJECT_STATION || m_type == OBJECT_ENERGY ) { UpdateEnergyMapping(); } } // Management of viruses. void COldObject::VirusFrame(float rTime) { if ( !m_bVirusMode ) return; // healthy object? m_virusTime += rTime; if ( m_virusTime >= VIRUS_DELAY ) { m_bVirusMode = false; // the virus is no longer active } if ( m_lastVirusParticle+m_engine->ParticleAdapt(0.2f) <= m_aTime ) { m_lastVirusParticle = m_aTime; Math::Vector pos = GetPosition(); pos.x += (Math::Rand()-0.5f)*10.0f; pos.z += (Math::Rand()-0.5f)*10.0f; Math::Vector speed; speed.x = (Math::Rand()-0.5f)*2.0f; speed.z = (Math::Rand()-0.5f)*2.0f; speed.y = Math::Rand()*4.0f+4.0f; Math::Point dim; dim.x = Math::Rand()*0.3f+0.3f; dim.y = dim.x; m_particle->CreateParticle(pos, speed, dim, Gfx::PARTIVIRUS, 3.0f); } } // Management particles mistresses. void COldObject::PartiFrame(float rTime) { Math::Vector pos, angle, factor; int i, channel; for ( i=0 ; iGetPosition(channel, pos) ) { m_objectPart[i].masterParti = -1; // particle no longer exists! continue; } SetPartPosition(i, pos); // Each song spins differently. switch( i%5 ) { case 0: factor = Math::Vector( 0.5f, 0.3f, 0.6f); break; case 1: factor = Math::Vector(-0.3f, 0.4f,-0.2f); break; case 2: factor = Math::Vector( 0.4f,-0.6f,-0.3f); break; case 3: factor = Math::Vector(-0.6f,-0.2f, 0.0f); break; case 4: factor = Math::Vector( 0.4f, 0.1f,-0.7f); break; } angle = GetPartRotation(i); angle += rTime*Math::PI*factor; SetPartRotation(i, angle); } } // Changes the perspective to view if it was like in the vehicle, // or behind the vehicle. void COldObject::SetViewFromHere(Math::Vector &eye, float &dirH, float &dirV, Math::Vector &lookat, Math::Vector &upVec, Gfx::CameraType type) { float speed; int part; UpdateTransformObject(); part = 0; if ( m_type == OBJECT_HUMAN || m_type == OBJECT_TECH ) { eye.x = -0.2f; eye.y = 3.3f; eye.z = 0.0f; //? eye.x = 1.0f; //? eye.y = 3.3f; //? eye.z = 0.0f; } else if ( m_type == OBJECT_MOBILErt || m_type == OBJECT_MOBILErr || m_type == OBJECT_MOBILErs ) { eye.x = -1.1f; // on the cap eye.y = 7.9f; eye.z = 0.0f; } else if ( m_type == OBJECT_MOBILEwc || m_type == OBJECT_MOBILEtc || m_type == OBJECT_MOBILEfc || m_type == OBJECT_MOBILEic ) // fireball? { //? eye.x = -0.9f; // on the cannon //? eye.y = 3.0f; //? eye.z = 0.0f; //? part = 1; eye.x = -0.9f; // on the cannon eye.y = 8.3f; eye.z = 0.0f; } else if ( m_type == OBJECT_MOBILEwi || m_type == OBJECT_MOBILEti || m_type == OBJECT_MOBILEfi || m_type == OBJECT_MOBILEii ) // orgaball ? { //? eye.x = -3.5f; // on the cannon //? eye.y = 5.1f; //? eye.z = 0.0f; //? part = 1; eye.x = -2.5f; // on the cannon eye.y = 10.4f; eye.z = 0.0f; } else if ( m_type == OBJECT_MOBILErc ) { //? eye.x = 2.0f; // in the cannon //? eye.y = 0.0f; //? eye.z = 0.0f; //? part = 2; eye.x = 4.0f; // on the cannon eye.y = 11.0f; eye.z = 0.0f; } else if ( m_type == OBJECT_MOBILEsa ) { eye.x = 3.0f; eye.y = 4.5f; eye.z = 0.0f; } else if ( m_type == OBJECT_MOBILEdr ) { eye.x = 1.0f; eye.y = 6.5f; eye.z = 0.0f; } else if ( m_type == OBJECT_APOLLO2 ) { eye.x = -3.0f; eye.y = 6.0f; eye.z = -2.0f; } else { eye.x = 0.7f; // between the brackets eye.y = 4.8f; eye.z = 0.0f; } #if ADJUST_ONBOARD eye.x += debug_x; eye.y += debug_y; eye.z += debug_z; char s[100]; sprintf(s, "x=%.2f y=%.2f z=%.2f", eye.x, eye.y, eye.z); m_engine->SetInfoText(4, s); #endif if ( type == Gfx::CAM_TYPE_BACK ) { eye.x -= 20.0f; eye.y += 1.0f; } lookat.x = eye.x+1.0f; lookat.y = eye.y+0.0f; lookat.z = eye.z+0.0f; eye = Math::Transform(m_objectPart[part].matWorld, eye); lookat = Math::Transform(m_objectPart[part].matWorld, lookat); // Camera tilts when turning. upVec = Math::Vector(0.0f, 1.0f, 0.0f); if ( m_physics != nullptr ) { if ( m_physics->GetLand() ) // on ground? { speed = m_physics->GetLinMotionX(MO_REASPEED); lookat.y -= speed*0.002f; speed = m_physics->GetCirMotionY(MO_REASPEED); upVec.z -= speed*0.04f; } else // in flight? { speed = m_physics->GetLinMotionX(MO_REASPEED); lookat.y += speed*0.002f; speed = m_physics->GetCirMotionY(MO_REASPEED); upVec.z += speed*0.08f; } } upVec = Math::Transform(m_objectPart[0].matRotate, upVec); dirH = -(m_objectPart[part].angle.y+Math::PI/2.0f); dirV = 0.0f; } // Management of features. Character* COldObject::GetCharacter() { return &m_character; } // Returns the absolute time. float COldObject::GetAbsTime() { return m_aTime; } float COldObject::GetCapacity() { return m_type == OBJECT_ATOMIC ? 10.0f : 1.0f; } bool COldObject::IsRechargeable() { return m_type == OBJECT_POWER; } // Management of the shield. void COldObject::SetShield(float level) { m_shield = level; } float COldObject::GetShield() { if ( m_type == OBJECT_FRET || m_type == OBJECT_STONE || m_type == OBJECT_URANIUM || m_type == OBJECT_BULLET || m_type == OBJECT_METAL || m_type == OBJECT_BBOX || m_type == OBJECT_KEYa || m_type == OBJECT_KEYb || m_type == OBJECT_KEYc || m_type == OBJECT_KEYd || m_type == OBJECT_TNT || m_type == OBJECT_SCRAP1 || m_type == OBJECT_SCRAP2 || m_type == OBJECT_SCRAP3 || m_type == OBJECT_SCRAP4 || m_type == OBJECT_SCRAP5 || m_type == OBJECT_BOMB || m_type == OBJECT_WAYPOINT || m_type == OBJECT_FLAGb || m_type == OBJECT_FLAGr || m_type == OBJECT_FLAGg || m_type == OBJECT_FLAGy || m_type == OBJECT_FLAGv || m_type == OBJECT_POWER || m_type == OBJECT_ATOMIC || m_type == OBJECT_ANT || m_type == OBJECT_SPIDER || m_type == OBJECT_BEE || m_type == OBJECT_WORM ) return 0.0f; return m_shield; } // Management of flight range (zero = infinity). void COldObject::SetRange(float delay) { m_range = delay; } float COldObject::GetRange() { return m_range; } // Management of transparency of the object. void COldObject::SetTransparency(float value) { int i; m_transparency = value; for ( i=0 ; iSetObjectTransparency(m_objectPart[i].object, value); } } } // Indicates whether an object is stationary (ant on the back). void COldObject::SetFixed(bool bFixed) { m_bFixed = bFixed; } bool COldObject::GetFixed() { return m_bFixed; } // Pushes an object. bool COldObject::JostleObject(float force) { if ( m_type == OBJECT_FLAGb || m_type == OBJECT_FLAGr || m_type == OBJECT_FLAGg || m_type == OBJECT_FLAGy || m_type == OBJECT_FLAGv ) // flag? { if ( m_auto == nullptr ) return false; m_auto->Start(1); } else { if ( m_auto != nullptr ) return false; auto autoJostle = MakeUnique(this); autoJostle->Start(0, force); m_auto = std::move(autoJostle); } return true; } // Management of time from which a virus is active. void COldObject::SetVirusMode(bool bEnable) { m_bVirusMode = bEnable; m_virusTime = 0.0f; if ( m_bVirusMode && Implements(ObjectInterfaceType::Programmable) ) { if ( !IntroduceVirus() ) // tries to infect { m_bVirusMode = false; // program was not contaminated! } } } bool COldObject::GetVirusMode() { return m_bVirusMode; } // Management mode of the camera. void COldObject::SetCameraType(Gfx::CameraType type) { m_cameraType = type; } Gfx::CameraType COldObject::GetCameraType() { return m_cameraType; } void COldObject::SetCameraDist(float dist) { m_cameraDist = dist; } float COldObject::GetCameraDist() { return m_cameraDist; } void COldObject::SetCameraLock(bool lock) { m_bCameraLock = lock; } bool COldObject::GetCameraLock() { return m_bCameraLock; } // Management of the demonstration of the object. void COldObject::SetHighlight(bool highlight) { if (highlight) { int list[OBJECTMAXPART+1]; int j = 0; for (int i = 0; i < m_totalPart; i++) { if ( m_objectPart[i].bUsed ) { list[j++] = m_objectPart[i].object; } } list[j] = -1; // terminate m_engine->SetHighlightRank(list); // gives the list of selected parts } } // Indicates whether the object is selected or not. void COldObject::SetSelect(bool select, bool bDisplayError) { m_bSelect = select; // NOTE: Right now, Ui::CObjectInterface is only for programmable objects. Right now all selectable objects are programmable anyway. // TODO: All UI-related stuff should be moved out of CObject classes if (Implements(ObjectInterfaceType::Programmable)) { if ( m_objectInterface == nullptr ) { m_objectInterface = MakeUnique(this); } m_objectInterface->CreateInterface(m_bSelect); } if ( m_auto != nullptr ) { m_auto->CreateInterface(m_bSelect); } CreateSelectParticle(); // creates / removes particles if ( !m_bSelect ) return; // if not selected, we're done Error err = ERR_OK; if ( m_physics != nullptr ) { err = m_physics->GetError(); } if ( m_auto != nullptr ) { err = m_auto->GetError(); } if ( err != ERR_OK && bDisplayError ) { m_main->DisplayError(err, this); } } // Indicates whether the object is selected or not. bool COldObject::GetSelect() { return m_bSelect; } // Indicates whether the object is selectable or not. void COldObject::SetSelectable(bool bMode) { m_bSelectable = bMode; } // Indicates whether the object is selecionnable or not. bool COldObject::GetSelectable() { return m_bSelectable; } // Indicates if necessary to check the tokens of the object. void COldObject::SetCheckToken(bool bMode) { m_bCheckToken = bMode; } // Indicates if necessary to check the tokens of the object. bool COldObject::GetCheckToken() { return m_bCheckToken; } // Management of the visibility of an object. // The object is not hidden or visually disabled, but ignores detections! // For example: underground worm. void COldObject::SetVisible(bool bVisible) { m_bVisible = bVisible; } // Management of the method of increasing damage. void COldObject::SetMagnifyDamage(float factor) { m_magnifyDamage = factor; } float COldObject::GetMagnifyDamage() { return m_magnifyDamage; } // Management of free parameter. void COldObject::SetParam(float value) { m_param = value; } float COldObject::GetParam() { return m_param; } // Management of the mode "current explosion" of an object. // An object in this mode is not saving. void COldObject::SetExploding(bool bExplo) { m_bExplo = bExplo; } bool COldObject::IsExploding() { return m_bExplo; } // Management of the HS mode of an object. void COldObject::SetBurn(bool bBurn) { m_bBurn = bBurn; m_burnTime = 0.0f; } bool COldObject::GetBurn() { return m_bBurn; } void COldObject::SetDead(bool bDead) { m_bDead = bDead; if ( bDead && Implements(ObjectInterfaceType::Programmable) ) { StopProgram(); // stops the current task } } bool COldObject::GetDead() { return m_bDead; } bool COldObject::GetRuin() { return m_bBurn|m_bFlat; } bool COldObject::GetActive() { return !GetLock() && !m_bBurn && !m_bFlat && m_bVisible; } // Management of the point of aim. void COldObject::SetGunGoalV(float gunGoal) { if ( m_type == OBJECT_MOBILEfc || m_type == OBJECT_MOBILEtc || m_type == OBJECT_MOBILEwc || m_type == OBJECT_MOBILEic ) // fireball? { if ( gunGoal > 10.0f*Math::PI/180.0f ) gunGoal = 10.0f*Math::PI/180.0f; if ( gunGoal < -20.0f*Math::PI/180.0f ) gunGoal = -20.0f*Math::PI/180.0f; SetPartRotationZ(1, gunGoal); } else if ( m_type == OBJECT_MOBILEfi || m_type == OBJECT_MOBILEti || m_type == OBJECT_MOBILEwi || m_type == OBJECT_MOBILEii ) // orgaball? { if ( gunGoal > 20.0f*Math::PI/180.0f ) gunGoal = 20.0f*Math::PI/180.0f; if ( gunGoal < -20.0f*Math::PI/180.0f ) gunGoal = -20.0f*Math::PI/180.0f; SetPartRotationZ(1, gunGoal); } else if ( m_type == OBJECT_MOBILErc ) // phazer? { if ( gunGoal > 45.0f*Math::PI/180.0f ) gunGoal = 45.0f*Math::PI/180.0f; if ( gunGoal < -20.0f*Math::PI/180.0f ) gunGoal = -20.0f*Math::PI/180.0f; SetPartRotationZ(2, gunGoal); } else { gunGoal = 0.0f; } m_gunGoalV = gunGoal; } void COldObject::SetGunGoalH(float gunGoal) { if ( m_type == OBJECT_MOBILEfc || m_type == OBJECT_MOBILEtc || m_type == OBJECT_MOBILEwc || m_type == OBJECT_MOBILEic ) // fireball? { if ( gunGoal > 40.0f*Math::PI/180.0f ) gunGoal = 40.0f*Math::PI/180.0f; if ( gunGoal < -40.0f*Math::PI/180.0f ) gunGoal = -40.0f*Math::PI/180.0f; SetPartRotationY(1, gunGoal); } else if ( m_type == OBJECT_MOBILEfi || m_type == OBJECT_MOBILEti || m_type == OBJECT_MOBILEwi || m_type == OBJECT_MOBILEii ) // orgaball? { if ( gunGoal > 40.0f*Math::PI/180.0f ) gunGoal = 40.0f*Math::PI/180.0f; if ( gunGoal < -40.0f*Math::PI/180.0f ) gunGoal = -40.0f*Math::PI/180.0f; SetPartRotationY(1, gunGoal); } else if ( m_type == OBJECT_MOBILErc ) // phazer? { if ( gunGoal > 40.0f*Math::PI/180.0f ) gunGoal = 40.0f*Math::PI/180.0f; if ( gunGoal < -40.0f*Math::PI/180.0f ) gunGoal = -40.0f*Math::PI/180.0f; SetPartRotationY(2, gunGoal); } else { gunGoal = 0.0f; } m_gunGoalH = gunGoal; } float COldObject::GetGunGoalV() { return m_gunGoalV; } float COldObject::GetGunGoalH() { return m_gunGoalH; } void COldObject::SetShowLimitRadius(float radius) { m_showLimitRadius = radius; } float COldObject::GetShowLimitRadius() { return m_showLimitRadius; } // Creates or removes particles associated to the object. void COldObject::CreateSelectParticle() { Math::Vector pos, speed; Math::Point dim; int i; // Removes particles preceding. for ( i=0 ; i<4 ; i++ ) { if ( m_partiSel[i] != -1 ) { m_particle->DeleteParticle(m_partiSel[i]); m_partiSel[i] = -1; } } if ( m_bSelect || IsProgram() || m_main->GetMissionType() == MISSION_RETRO ) { // Creates particles lens for the headlights. if ( m_type == OBJECT_MOBILEfa || m_type == OBJECT_MOBILEta || m_type == OBJECT_MOBILEwa || m_type == OBJECT_MOBILEia || m_type == OBJECT_MOBILEfc || m_type == OBJECT_MOBILEtc || m_type == OBJECT_MOBILEwc || m_type == OBJECT_MOBILEic || m_type == OBJECT_MOBILEfi || m_type == OBJECT_MOBILEti || m_type == OBJECT_MOBILEwi || m_type == OBJECT_MOBILEii || m_type == OBJECT_MOBILEfs || m_type == OBJECT_MOBILEts || m_type == OBJECT_MOBILEws || m_type == OBJECT_MOBILEis || m_type == OBJECT_MOBILErt || m_type == OBJECT_MOBILErc || m_type == OBJECT_MOBILErr || m_type == OBJECT_MOBILErs || m_type == OBJECT_MOBILEsa || m_type == OBJECT_MOBILEtg || m_type == OBJECT_MOBILEft || m_type == OBJECT_MOBILEtt || m_type == OBJECT_MOBILEwt || m_type == OBJECT_MOBILEit || m_type == OBJECT_MOBILEdr ) // vehicle? { pos = Math::Vector(0.0f, 0.0f, 0.0f); speed = Math::Vector(0.0f, 0.0f, 0.0f); dim.x = 0.0f; dim.y = 0.0f; m_partiSel[0] = m_particle->CreateParticle(pos, speed, dim, Gfx::PARTISELY, 1.0f, 0.0f, 0.0f); m_partiSel[1] = m_particle->CreateParticle(pos, speed, dim, Gfx::PARTISELY, 1.0f, 0.0f, 0.0f); m_partiSel[2] = m_particle->CreateParticle(pos, speed, dim, Gfx::PARTISELR, 1.0f, 0.0f, 0.0f); m_partiSel[3] = m_particle->CreateParticle(pos, speed, dim, Gfx::PARTISELR, 1.0f, 0.0f, 0.0f); UpdateSelectParticle(); } } } // Updates the particles associated to the object. void COldObject::UpdateSelectParticle() { Math::Vector pos[4]; Math::Point dim[4]; float zoom[4]; float angle; int i; if ( !m_bSelect && !IsProgram() && m_main->GetMissionType() != MISSION_RETRO ) return; dim[0].x = 1.0f; dim[1].x = 1.0f; dim[2].x = 1.2f; dim[3].x = 1.2f; // Lens front yellow. if ( m_type == OBJECT_MOBILErt || m_type == OBJECT_MOBILErc || m_type == OBJECT_MOBILErr || m_type == OBJECT_MOBILErs ) // large caterpillars? { pos[0] = Math::Vector(4.2f, 2.8f, 1.5f); pos[1] = Math::Vector(4.2f, 2.8f, -1.5f); dim[0].x = 1.5f; dim[1].x = 1.5f; } else if ( m_type == OBJECT_MOBILEwt || m_type == OBJECT_MOBILEtt || m_type == OBJECT_MOBILEft || m_type == OBJECT_MOBILEit ) // trainer ? { pos[0] = Math::Vector(4.2f, 2.5f, 1.2f); pos[1] = Math::Vector(4.2f, 2.5f, -1.2f); dim[0].x = 1.5f; dim[1].x = 1.5f; } else if ( m_type == OBJECT_MOBILEsa ) // submarine? { pos[0] = Math::Vector(3.6f, 4.0f, 2.0f); pos[1] = Math::Vector(3.6f, 4.0f, -2.0f); } else if ( m_type == OBJECT_MOBILEtg ) // target? { pos[0] = Math::Vector(3.4f, 6.5f, 2.0f); pos[1] = Math::Vector(3.4f, 6.5f, -2.0f); } else if ( m_type == OBJECT_MOBILEdr ) // designer? { pos[0] = Math::Vector(4.9f, 3.5f, 2.5f); pos[1] = Math::Vector(4.9f, 3.5f, -2.5f); } else { pos[0] = Math::Vector(4.2f, 2.5f, 1.5f); pos[1] = Math::Vector(4.2f, 2.5f, -1.5f); } // Red back lens if ( m_type == OBJECT_MOBILEfa || m_type == OBJECT_MOBILEfc || m_type == OBJECT_MOBILEfi || m_type == OBJECT_MOBILEfs || m_type == OBJECT_MOBILEft ) // flying? { pos[2] = Math::Vector(-4.0f, 3.1f, 4.5f); pos[3] = Math::Vector(-4.0f, 3.1f, -4.5f); dim[2].x = 0.6f; dim[3].x = 0.6f; } if ( m_type == OBJECT_MOBILEwa || m_type == OBJECT_MOBILEwc || m_type == OBJECT_MOBILEwi || m_type == OBJECT_MOBILEws ) // wheels? { pos[2] = Math::Vector(-4.5f, 2.7f, 2.8f); pos[3] = Math::Vector(-4.5f, 2.7f, -2.8f); } if ( m_type == OBJECT_MOBILEwt ) // wheels? { pos[2] = Math::Vector(-4.0f, 2.5f, 2.2f); pos[3] = Math::Vector(-4.0f, 2.5f, -2.2f); } if ( m_type == OBJECT_MOBILEia || m_type == OBJECT_MOBILEic || m_type == OBJECT_MOBILEii || m_type == OBJECT_MOBILEis || m_type == OBJECT_MOBILEit ) // legs? { pos[2] = Math::Vector(-4.5f, 2.7f, 2.8f); pos[3] = Math::Vector(-4.5f, 2.7f, -2.8f); } if ( m_type == OBJECT_MOBILEta || m_type == OBJECT_MOBILEtc || m_type == OBJECT_MOBILEti || m_type == OBJECT_MOBILEts || m_type == OBJECT_MOBILEtt ) // caterpillars? { pos[2] = Math::Vector(-3.6f, 4.2f, 3.0f); pos[3] = Math::Vector(-3.6f, 4.2f, -3.0f); } if ( m_type == OBJECT_MOBILErt || m_type == OBJECT_MOBILErc || m_type == OBJECT_MOBILErr || m_type == OBJECT_MOBILErs ) // large caterpillars? { pos[2] = Math::Vector(-5.0f, 5.2f, 2.5f); pos[3] = Math::Vector(-5.0f, 5.2f, -2.5f); } if ( m_type == OBJECT_MOBILEsa ) // submarine? { pos[2] = Math::Vector(-3.6f, 4.0f, 2.0f); pos[3] = Math::Vector(-3.6f, 4.0f, -2.0f); } if ( m_type == OBJECT_MOBILEtg ) // target? { pos[2] = Math::Vector(-2.4f, 6.5f, 2.0f); pos[3] = Math::Vector(-2.4f, 6.5f, -2.0f); } if ( m_type == OBJECT_MOBILEdr ) // designer? { pos[2] = Math::Vector(-5.3f, 2.7f, 1.8f); pos[3] = Math::Vector(-5.3f, 2.7f, -1.8f); } angle = GetRotationY()/Math::PI; zoom[0] = 1.0f; zoom[1] = 1.0f; zoom[2] = 1.0f; zoom[3] = 1.0f; if ( ( IsProgram() || // current program? m_main->GetMissionType() == MISSION_RETRO ) && // Retro mode? Math::Mod(m_aTime, 0.7f) < 0.3f ) { zoom[0] = 0.0f; // blinks zoom[1] = 0.0f; zoom[2] = 0.0f; zoom[3] = 0.0f; } // Updates lens. for ( i=0 ; i<4 ; i++ ) { pos[i] = Math::Transform(m_objectPart[0].matWorld, pos[i]); dim[i].y = dim[i].x; m_particle->SetParam(m_partiSel[i], pos[i], dim[i], zoom[i], angle, 1.0f); } } // Returns the physics associated to the object. CPhysics* COldObject::GetPhysics() { return m_physics.get(); } void COldObject::SetPhysics(std::unique_ptr physics) { m_physics = std::move(physics); } // TODO: Temporary hack until we'll have subclasses for objects void COldObject::SetProgrammable(bool programmable) { m_implementedInterfaces[static_cast(ObjectInterfaceType::Programmable)] = programmable; } // Returns the movement associated to the object. CMotion* COldObject::GetMotion() { return m_motion.get(); } void COldObject::SetMotion(std::unique_ptr motion) { m_motion = std::move(motion); } // Returns the controller associated to the object. CAuto* COldObject::GetAuto() { return m_auto.get(); } void COldObject::SetAuto(std::unique_ptr automat) { m_auto = std::move(automat); } // Management of the position in the file definition. void COldObject::SetDefRank(int rank) { m_defRank = rank; } int COldObject::GetDefRank() { return m_defRank; } Math::Vector COldObject::GetPosition() const { return GetPartPosition(0); } void COldObject::SetPosition(const Math::Vector& pos) { SetPartPosition(0, pos); } Math::Vector COldObject::GetRotation() const { return GetPartRotation(0); } void COldObject::SetRotation(const Math::Vector& rotation) { SetPartRotation(0, rotation); } Math::Vector COldObject::GetScale() const { return GetPartScale(0); } void COldObject::SetScale(const Math::Vector& scale) { SetPartScale(0, scale); } // Move the manipulator arm. Error COldObject::StartTaskTake() { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskTake(); UpdateInterface(); return err; } // Move the manipulator arm. Error COldObject::StartTaskManip(TaskManipOrder order, TaskManipArm arm) { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskManip(order, arm); UpdateInterface(); return err; } // Puts or removes a flag. Error COldObject::StartTaskFlag(TaskFlagOrder order, int rank) { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskFlag(order, rank); UpdateInterface(); return err; } // Built a building. Error COldObject::StartTaskBuild(ObjectType type) { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskBuild(type); UpdateInterface(); return err; } // Probe the ground. Error COldObject::StartTaskSearch() { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskSearch(); UpdateInterface(); return err; } // Delete mark on ground Error COldObject::StartTaskDeleteMark() { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskDeleteMark(); UpdateInterface(); return err; } // Terraformed the ground. Error COldObject::StartTaskTerraform() { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskTerraform(); UpdateInterface(); return err; } // Change pencil. Error COldObject::StartTaskPen(bool down, TraceColor color) { auto motionVehicle = dynamic_cast(m_motion.get()); assert(motionVehicle != nullptr); if (color == TraceColor::Default) color = motionVehicle->GetTraceColor(); motionVehicle->SetTraceDown(down); motionVehicle->SetTraceColor(color); m_physics->SetMotorSpeedX(0.0f); m_physics->SetMotorSpeedY(0.0f); m_physics->SetMotorSpeedZ(0.0f); StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskPen(down, color); UpdateInterface(); return err; } // Recovers a ruin. Error COldObject::StartTaskRecover() { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskRecover(); UpdateInterface(); return err; } // Shoots. Error COldObject::StartTaskFire(float delay) { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskFire(delay); UpdateInterface(); return err; } // Explodes spider. Error COldObject::StartTaskSpiderExplo() { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskSpiderExplo(); UpdateInterface(); return err; } // Shoots to the ant. Error COldObject::StartTaskFireAnt(Math::Vector impact) { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskFireAnt(impact); UpdateInterface(); return err; } Error COldObject::StartTaskWait(float time) { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskWait(time); UpdateInterface(); return err; } Error COldObject::StartTaskAdvance(float length) { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskAdvance(length); UpdateInterface(); return err; } Error COldObject::StartTaskTurn(float angle) { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskTurn(angle); UpdateInterface(); return err; } Error COldObject::StartTaskGoto(Math::Vector pos, float altitude, TaskGotoGoal goalMode, TaskGotoCrash crashMode) { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskGoto(pos, altitude, goalMode, crashMode); UpdateInterface(); return err; } Error COldObject::StartTaskInfo(const char *name, float value, float power, bool bSend) { StopForegroundTask(); m_foregroundTask = MakeUnique(this); Error err = m_foregroundTask->StartTaskInfo(name, value, power, bSend); UpdateInterface(); return err; } // Deploys the shield. Error COldObject::StartTaskShield(TaskShieldMode mode, float delay) { if (m_backgroundTask == nullptr) { m_backgroundTask = MakeUnique(this); } Error err = m_backgroundTask->StartTaskShield(mode, delay); UpdateInterface(); return err; } // Adjusts upward. Error COldObject::StartTaskGunGoal(float dirV, float dirH) { StopBackgroundTask(); m_backgroundTask = MakeUnique(this); Error err = m_backgroundTask->StartTaskGunGoal(dirV, dirH); UpdateInterface(); return err; } void COldObject::UpdateInterface() { if (m_objectInterface != nullptr && GetSelect()) { m_objectInterface->UpdateInterface(); } CreateSelectParticle(); m_main->UpdateShortcuts(); } void COldObject::StopProgram() { CProgrammableObjectImpl::StopProgram(); //TODO: I don't want CProgrammableObjectImpl to depend on motion and physics, refactor this somehow m_physics->SetMotorSpeedX(0.0f); m_physics->SetMotorSpeedY(0.0f); m_physics->SetMotorSpeedZ(0.0f); m_motion->SetAction(-1); }