/*
 * This file is part of the Colobot: Gold Edition source code
 * Copyright (C) 2001-2023, Daniel Roux, EPSITEC SA & TerranovaTeam
 * http://epsitec.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 "app/app.h"

#include "common/global.h"
#include "common/make_unique.h"
#include "common/settings.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 "level/robotmain.h"
#include "level/scoreboard.h"

#include "level/parser/parserexceptions.h"
#include "level/parser/parserline.h"
#include "level/parser/parserparam.h"

#include "math/geometry.h"

#include "object/object_manager.h"

#include "object/auto/auto.h"
#include "object/auto/autobase.h"
#include "object/auto/autojostle.h"

#include "object/motion/motion.h"
#include "object/motion/motionvehicle.h"

#include "object/subclass/base_alien.h"
#include "object/subclass/exchange_post.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 <boost/lexical_cast.hpp>
#include <iomanip>


const float VIRUS_DELAY     = 60.0f;        // duration of virus infection

// Object's constructor.

COldObject::COldObject(int id)
    : CObject(id, OBJECT_NULL),
      CInteractiveObject(m_implementedInterfaces),
      CTransportableObject(m_implementedInterfaces),
      CTaskExecutorObjectImpl(m_implementedInterfaces, this),
      CProgramStorageObjectImpl(m_implementedInterfaces, this),
      CProgrammableObjectImpl(m_implementedInterfaces, this),
      CJostleableObject(m_implementedInterfaces),
      CSlottedObject(m_implementedInterfaces),
      CJetFlyingObject(m_implementedInterfaces),
      CControllableObject(m_implementedInterfaces),
      CPowerContainerObjectImpl(m_implementedInterfaces),
      CRangedObject(m_implementedInterfaces),
      CTraceDrawingObject(m_implementedInterfaces),
      CShieldedAutoRegenObject(m_implementedInterfaces),
      m_partiSel()
{
    // A bit of a hack since we don't have subclasses yet, set externally in SetProgrammable()
    m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::ProgramStorage)] = false;
    m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Programmable)] = false;
    // Another hack, see SetMovable()
    m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Movable)] = false;
    // Another hack
    m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Jostleable)] = false;

    m_implementedInterfaces[static_cast<int>(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_NULL;
    m_option = 0;
    m_name = "";
    m_shadowLight   = -1;
    m_shadowHeight  = 0.0f;
    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 = nullptr;
    m_cargo  = nullptr;
    m_transporter = nullptr;
    m_transporterLink = 0;
    m_shield   = 1.0f;
    m_range    = 30.0f;
    m_lastEnergy = 999.9f;
    m_bSelect = false;
    m_bSelectable = true;
    m_bCheckToken = true;
    m_underground = false;
    m_bTrainer = false;
    m_bToy = false;
    m_bManual = false;
    m_aTime = 0.0f;
    m_shotTime = 0.0f;
    m_bVirusMode = false;
    m_virusTime = 0.0f;
    m_lastVirusParticle = 0.0f;
    m_damaging = false;
    m_damageTime = 0.0f;
    m_dying = DeathType::Alive;
    m_bFlat  = false;
    m_gunGoalV = 0.0f;
    m_gunGoalH = 0.0f;
    m_shieldRadius = 0.0f;
    m_magnifyDamage = 1.0f;
    m_hasPowerSlot = false;
    m_hasCargoSlot = false;

    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_bCameraLock = false;

    for (int i=0 ; i<OBJECTMAXPART ; i++ )
    {
        m_objectPart[i].bUsed = false;
    }
    m_totalPart = 0;

    for (int i=0 ; i<4 ; i++ )
    {
        m_partiSel[i] = -1;
    }

    m_time = 0.0f;
    m_burnTime = 0.0f;

    m_buttonAxe    = EVENT_NULL;

    m_reactorRange       = 1.0f;

    m_traceDown = false;
    m_traceColor = TraceColor::Black;
    m_traceWidth = 0.5f;

    DeleteAllCrashSpheres();
}

// Object's destructor.

COldObject::~COldObject()
{
    m_main->HideDropZone(this);
}


// Removes an object.
// If bAll = true, it does not help,
// because all objects in the scene are quickly destroyed!

void COldObject::DeleteObject(bool bAll)
{
    CScriptFunctions::DestroyObjectVar(m_botVar, false);

    if ( m_camera->GetControllingObject() == this )
    {
        m_camera->SetControllingObject(nullptr);
    }
    m_main->CutObjectLink(this);

    if ( !bAll )
    {
        m_engine->GetPyroManager()->CutObjectLink(this);
        m_particle->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_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 ; i<OBJECTMAXPART ; i++ )
    {
        if ( m_objectPart[i].bUsed )
        {
            m_objectPart[i].bUsed = false;
            m_engine->DeleteObject(m_objectPart[i].object);

            if ( m_objectPart[i].masterParti != -1 )
            {
                m_particle->DeleteParticle(m_objectPart[i].masterParti);
                m_objectPart[i].masterParti = -1;
            }
        }
    }

    if (!bAll)
    {
        if (m_power != nullptr)
        {
            if (m_power->Implements(ObjectInterfaceType::Old))
            {
                dynamic_cast<COldObject&>(*m_power).SetTransporter(nullptr);
                dynamic_cast<COldObject&>(*m_power).DeleteObject(bAll);
            }
            m_power = nullptr;
        }
        if (m_cargo != nullptr)
        {
            if (m_cargo->Implements(ObjectInterfaceType::Old))
            {
                dynamic_cast<COldObject&>(*m_cargo).SetTransporter(nullptr);
                dynamic_cast<COldObject&>(*m_cargo).DeleteObject(bAll);
            }
            m_cargo = nullptr;
        }
    }

    if ( !bAll )  m_main->CreateShortcuts();
}

// Simplifies a object (destroys all logic classes, making it a static object)

void COldObject::Simplify()
{
    if ( Implements(ObjectInterfaceType::Programmable) )
    {
        StopProgram();
    }
    m_main->SaveOneScript(this);

    m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::ProgramStorage)] = false;
    m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Programmable)] = false;

    if ( m_physics != nullptr )
    {
        m_physics->DeleteObject();
        m_physics.reset();
    }
    if ( m_motion != nullptr )
    {
        m_motion->DeleteObject();
        m_motion.reset();
    }
    m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Movable)] = false;

    if ( m_objectInterface != nullptr )
    {
        m_objectInterface->DeleteObject();
        m_objectInterface.reset();
    }

    if ( m_auto != nullptr )
    {
        m_auto->DeleteObject();
        m_auto.reset();
    }

    m_main->CreateShortcuts();
}


bool COldObject::DamageObject(DamageType type, float force, CObject* killer)
{
    assert(Implements(ObjectInterfaceType::Damageable));
    assert(!Implements(ObjectInterfaceType::Destroyable) || Implements(ObjectInterfaceType::Shielded) || Implements(ObjectInterfaceType::Fragile));

    if ( IsDying() )  return false;
    if ( Implements(ObjectInterfaceType::Jostleable) ) return false;

    if ( m_type == OBJECT_ANT    ||
         m_type == OBJECT_WORM   ||
         m_type == OBJECT_SPIDER ||
         m_type == OBJECT_BEE     )
    {
        // Fragile, but can have fire effect
        // TODO: IsBurnable()
        force = -1.0f;
    }
    else if ( Implements(ObjectInterfaceType::Fragile) )
    {
        if ((m_type == OBJECT_BOMB         ||
             m_type == OBJECT_RUINmobilew1 ||
             m_type == OBJECT_RUINmobilew2 ||
             m_type == OBJECT_RUINmobilet1 ||
             m_type == OBJECT_RUINmobilet2 ||
             m_type == OBJECT_RUINmobiler1 ||
             m_type == OBJECT_RUINmobiler2 ||
             m_type == OBJECT_RUINfactory  ||
             m_type == OBJECT_RUINdoor     ||
             m_type == OBJECT_RUINsupport  ||
             m_type == OBJECT_RUINradar    ||
             m_type == OBJECT_RUINconvert   ) && type != DamageType::Explosive ) return false; // Mines and ruins can't be destroyed by shooting
        if ( m_type == OBJECT_URANIUM && (type == DamageType::Fire || type == DamageType::Organic) ) return false; // UraniumOre is not destroyable by shooting or aliens (see #777)
        if ( m_type == OBJECT_STONE && (type == DamageType::Fire || type == DamageType::Organic) ) return false; // TitaniumOre is not destroyable either
        // PowerCell, NuclearCell and Titanium are destroyable by shooting, but not by collisions!
        if ( m_type == OBJECT_METAL && type == DamageType::Collision ) return false;
        if ( m_type == OBJECT_POWER && type == DamageType::Collision ) return false;
        if ( m_type == OBJECT_NUCLEAR && type == DamageType::Collision ) return false;

        if ( m_magnifyDamage * m_main->GetGlobalMagnifyDamage() == 0 ) return false; // Don't destroy if magnifyDamage=0

        DestroyObject(DestructionType::Explosion, killer);
        return true;
    }

    if ( type != DamageType::Phazer && m_type == OBJECT_MOTHER ) return false; // AlienQueen can be destroyed only by PhazerShooter

    if ( type == DamageType::Organic )
    {
        // TODO: I don't understand, why does it apply damage only once every 0.5 second?
        if ( m_shotTime < 0.5f )  return false;
        m_shotTime = 0.0f;
    }

    float loss = 1.0f;
    bool dead = true;
    if (Implements(ObjectInterfaceType::Shielded))
    {
        float magnifyDamage = m_magnifyDamage * m_main->GetGlobalMagnifyDamage();
        if (force != std::numeric_limits<float>::infinity())
        {
            // Calculate the shield lost by the explosion
            loss = force * magnifyDamage;
            if (m_type == OBJECT_HUMAN) loss /= 2.5f; // Me is more resistant
            if (loss > 1.0f) loss = 1.0f;

            // Decreases the the shield
            float shield = GetShield();
            shield -= loss;
            SetShield(shield);

            // Sending info about taking damage
            if (!m_damaging)
            {
                SetDamaging(true);
                m_main->UpdateShortcuts();
            }
            m_damageTime = m_time;
        }
        else
        {
            if ( magnifyDamage != 0.0f )
            {
                // Dead immediately
                SetShield(0.0f);
                SetDamaging(false);
            }
        }
        dead = (GetShield() <= 0.0f);
    }

    if (dead && Implements(ObjectInterfaceType::Destroyable))
    {
        if (type == DamageType::Fire)
        {
            DestroyObject(DestructionType::Burn, killer);
        }
        else
        {
            DestroyObject(DestructionType::Explosion, killer);
        }
        return true;
    }

    if ( m_type == OBJECT_HUMAN )
    {
        m_engine->GetPyroManager()->Create(Gfx::PT_SHOTH, this, loss);
    }
    else if ( m_type == OBJECT_MOTHER )
    {
        m_engine->GetPyroManager()->Create(Gfx::PT_SHOTM, this, loss);
    }
    else
    {
        m_engine->GetPyroManager()->Create(Gfx::PT_SHOTT, this, loss);
    }
    return false;
}

void COldObject::DestroyObject(DestructionType type, CObject* killer)
{
    assert(Implements(ObjectInterfaceType::Destroyable));

    if(type == DestructionType::NoEffect) assert(!!"DestructionType::NoEffect should not be passed to DestroyObject()!");
    assert(type != DestructionType::Drowned || m_type == OBJECT_HUMAN);

    if ( IsDying() )  return;

    if (Implements(ObjectInterfaceType::Shielded))
    {
        SetShield(0.0f);
        SetDamaging(false);
    }

    Gfx::PyroType pyroType = Gfx::PT_NULL;
    if ( type == DestructionType::Explosion )   // 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      ||
                  m_type == OBJECT_RUINfactory ||
                  m_type == OBJECT_RUINdoor    ||
                  m_type == OBJECT_RUINsupport ||
                  m_type == OBJECT_RUINradar   ||
                  m_type == OBJECT_RUINconvert  )  // building?
        {
            pyroType = Gfx::PT_FRAGT;
        }
        else if ( m_type == OBJECT_MOBILEtg )
        {
            pyroType = Gfx::PT_FRAGT;
        }
        else
        {
            pyroType = Gfx::PT_EXPLOT;
        }
    }
    else if ( type == DestructionType::ExplosionWater )
    {
        pyroType = Gfx::PT_FRAGW;
    }
    else if ( type == DestructionType::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;
            SetDying(DeathType::Burning);
        }
        else if ( m_type == OBJECT_HUMAN )
        {
            pyroType = Gfx::PT_DEADG;
        }
        else
        {
            pyroType = Gfx::PT_BURNT;
            SetDying(DeathType::Burning);
        }
        SetVirusMode(false);
    }
    else if ( type == DestructionType::Drowned )
    {
        pyroType = Gfx::PT_DEADW;
    }
    else if ( type == DestructionType::Win )
    {
        pyroType = Gfx::PT_WPCHECK;
    }
    else if ( type == DestructionType::Squash )
    {
        pyroType = Gfx::PT_SQUASH;
        DeleteAllCrashSpheres();
    }
    assert(pyroType != Gfx::PT_NULL);
    if (pyroType == Gfx::PT_FRAGT ||
        pyroType == Gfx::PT_FRAGO ||
        pyroType == Gfx::PT_FRAGW)
    {
        SetDying(DeathType::Exploding);
    }
    m_engine->GetPyroManager()->Create(pyroType, this);

    if ( Implements(ObjectInterfaceType::Programmable) )
    {
        StopProgram();
    }
    m_main->SaveOneScript(this);

    if ( GetSelect() )
    {
        SetSelect(false);  // deselects the object
        m_camera->SetType(Gfx::CAM_TYPE_EXPLO);
        m_main->DeselectAll();
    }
    m_main->CutObjectLink(this);

    CScoreboard* scoreboard = m_main->GetScoreboard();
    if (scoreboard)
        scoreboard->ProcessKill(this, killer);

    m_team = 0; // Back to neutral on destruction

    if ( m_botVar != nullptr )
    {
        if ( Implements(ObjectInterfaceType::Transportable) )  // (*)
        {
            CScriptFunctions::DestroyObjectVar(m_botVar, false);
        }
    }
}

// (*)  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 ; i<OBJECTMAXPART ; i++ )
    {
        if ( m_objectPart[i].bUsed )
        {
            m_totalPart = i+1;
        }
    }
}


// Specifies the number of the object of a part.

void COldObject::SetObjectRank(int part, int objRank)
{
    if ( !m_objectPart[part].bUsed )  // object not created?
    {
        InitPart(part);
        UpdateTotalPart();
    }
    m_objectPart[part].object = objRank;
}

// Returns the number of part.

int COldObject::GetObjectRank(int part)
{
    if ( !m_objectPart[part].bUsed )  return -1;
    return m_objectPart[part].object;
}

// Specifies what is the parent of a part.
// Reminder: Part 0 is always the father of all
// and therefore the main part (eg the chassis of a car).

void COldObject::SetObjectParent(int part, int parent)
{
    m_objectPart[part].parentPart = parent;
}


// Specifies the type of the object.

void COldObject::SetType(ObjectType type)
{
    m_type = type;
    m_name = GetObjectName(m_type);

    SetSelectable(IsSelectableByDefault(m_type));

    // TODO: Temporary hack
    if ( m_type == OBJECT_MOBILEfa || // WingedGrabber
         m_type == OBJECT_MOBILEfb || // WingedBuilder
         m_type == OBJECT_MOBILEfs || // WingedSniffer
         m_type == OBJECT_MOBILEfc || // WingedShooter
         m_type == OBJECT_MOBILEfi || // WingedOrgaShooter
         m_type == OBJECT_MOBILEft || // WingedTrainer
         m_type == OBJECT_HUMAN    || // Me
         m_type == OBJECT_TECH     || // Tech
         m_type == OBJECT_CONTROLLER)
    {
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Flying)] = true;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::JetFlying)] = true;
    }
    else if ( m_type == OBJECT_BEE )
    {
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Flying)] = true;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::JetFlying)] = false;
    }
    else
    {
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Flying)] = false;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::JetFlying)] = false;
    }

    // TODO: Another temporary hack
    if (m_type == OBJECT_MOBILEfa ||
        m_type == OBJECT_MOBILEta ||
        m_type == OBJECT_MOBILEwa ||
        m_type == OBJECT_MOBILEia ||
        m_type == OBJECT_MOBILEfb ||
        m_type == OBJECT_MOBILEtb ||
        m_type == OBJECT_MOBILEwb ||
        m_type == OBJECT_MOBILEib ||
        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_MOBILErp ||
        m_type == OBJECT_MOBILEst ||
        m_type == OBJECT_TOWER    ||
        m_type == OBJECT_RESEARCH ||
        m_type == OBJECT_ENERGY   || // TODO not actually a power cell slot
        m_type == OBJECT_LABO     || // TODO not actually a power cell slot
        m_type == OBJECT_NUCLEAR  ) // TODO not actually a power cell slot
    {
        m_hasPowerSlot = true;
    }
    else
    {
        m_hasPowerSlot = false;
    }

    if ( m_type == OBJECT_HUMAN ||
         m_type == OBJECT_TECH ||
         m_type == OBJECT_MOBILEfa || // Grabbers
         m_type == OBJECT_MOBILEta ||
         m_type == OBJECT_MOBILEwa ||
         m_type == OBJECT_MOBILEia ||
         m_type == OBJECT_MOBILEsa || // subber
         m_type == OBJECT_BEE)
    {
        m_hasCargoSlot = true;
    }
    else
    {
        m_hasCargoSlot = false;
    }

    m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Slotted)] = (m_hasPowerSlot || m_hasCargoSlot);

    // TODO: Hacking some more
    if ( m_type == OBJECT_MOBILEtg ||
         m_type == OBJECT_STONE    ||
         m_type == OBJECT_METAL    ||
         m_type == OBJECT_URANIUM  ||
         m_type == OBJECT_POWER    ||
         m_type == OBJECT_ATOMIC   ||
         m_type == OBJECT_TNT      ||
         m_type == OBJECT_BULLET   ||
         m_type == OBJECT_EGG      ||
         m_type == OBJECT_BOMB     ||
         m_type == OBJECT_ANT      ||
         m_type == OBJECT_WORM     ||
         m_type == OBJECT_SPIDER   ||
         m_type == OBJECT_BEE      ||
         m_type == OBJECT_TEEN28    )
    {
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Damageable)] = true;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Destroyable)] = true;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Fragile)] = true;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Shielded)] = false;
    }
    else if (m_type == OBJECT_HUMAN ||
         m_type == OBJECT_MOBILEfa ||
         m_type == OBJECT_MOBILEta ||
         m_type == OBJECT_MOBILEwa ||
         m_type == OBJECT_MOBILEia ||
         m_type == OBJECT_MOBILEfb ||
         m_type == OBJECT_MOBILEtb ||
         m_type == OBJECT_MOBILEwb ||
         m_type == OBJECT_MOBILEib ||
         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_MOBILEft ||
         m_type == OBJECT_MOBILEtt ||
         m_type == OBJECT_MOBILEwt ||
         m_type == OBJECT_MOBILEit ||
         m_type == OBJECT_MOBILErp ||
         m_type == OBJECT_MOBILEst ||
         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_MOTHER    )
    {
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Damageable)] = true;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Destroyable)] = true;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Fragile)] = false;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Shielded)] = true;
    }
    else if (m_type == OBJECT_HUSTON ||
             m_type == OBJECT_BASE    )
    {
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Damageable)] = true;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Destroyable)] = false;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Fragile)] = false;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Shielded)] = false;
    }
    else if (m_type == OBJECT_RUINmobilew1 ||
             m_type == OBJECT_RUINmobilew2 ||
             m_type == OBJECT_RUINmobilet1 ||
             m_type == OBJECT_RUINmobilet2 ||
             m_type == OBJECT_RUINmobiler1 ||
             m_type == OBJECT_RUINmobiler2 ||
             m_type == OBJECT_RUINfactory  ||
             m_type == OBJECT_RUINdoor     ||
             m_type == OBJECT_RUINsupport  ||
             m_type == OBJECT_RUINradar    ||
             m_type == OBJECT_RUINconvert   )
    {
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Damageable)] = true;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Destroyable)] = true;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Fragile)] = true;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Shielded)] = false;
    }
    else if (m_type == OBJECT_PLANT0  ||
             m_type == OBJECT_PLANT1  ||
             m_type == OBJECT_PLANT2  ||
             m_type == OBJECT_PLANT3  ||
             m_type == OBJECT_PLANT4  ||
             m_type == OBJECT_PLANT15 ||
             m_type == OBJECT_PLANT16 ||
             m_type == OBJECT_PLANT17 ||
             m_type == OBJECT_PLANT18 )
    {
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Damageable)] = true;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Destroyable)] = true;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Fragile)] = true;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Shielded)] = false;
    }
    else
    {
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Damageable)] = false;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Destroyable)] = false;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Fragile)] = false;
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Shielded)] = false;
    }

    // TODO: #TooMuchHacking
    m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::ShieldedAutoRegen)] = (m_type == OBJECT_HUMAN);

    // TODO: Hacking in progress...
    if ( m_type == OBJECT_STONE   ||
         m_type == OBJECT_URANIUM ||
         m_type == OBJECT_BULLET  ||
         m_type == OBJECT_METAL   ||
         m_type == OBJECT_POWER   ||
         m_type == OBJECT_ATOMIC  ||
         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_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Transportable)] = true;
    }
    else
    {
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Transportable)] = false;
    }

    // TODO: You have been hacked!
    if (m_type == OBJECT_HUMAN    ||
        m_type == OBJECT_TOTO     ||
        m_type == OBJECT_MOBILEfa ||
        m_type == OBJECT_MOBILEta ||
        m_type == OBJECT_MOBILEwa ||
        m_type == OBJECT_MOBILEia ||
        m_type == OBJECT_MOBILEfb ||
        m_type == OBJECT_MOBILEtb ||
        m_type == OBJECT_MOBILEwb ||
        m_type == OBJECT_MOBILEib ||
        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_MOBILEft ||
        m_type == OBJECT_MOBILEtt ||
        m_type == OBJECT_MOBILEwt ||
        m_type == OBJECT_MOBILEit ||
        m_type == OBJECT_MOBILErp ||
        m_type == OBJECT_MOBILEst ||
        m_type == OBJECT_MOBILEtg ||
        m_type == OBJECT_MOBILEdr ||
        m_type == OBJECT_APOLLO2  ||
        m_type == OBJECT_BASE     ||
        m_type == OBJECT_DERRICK  ||
        m_type == OBJECT_FACTORY  ||
        m_type == OBJECT_REPAIR   ||
        m_type == OBJECT_DESTROYER||
        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_ANT      ||
        m_type == OBJECT_WORM     ||
        m_type == OBJECT_SPIDER   ||
        m_type == OBJECT_BEE      ||
        m_type == OBJECT_MOTHER   ||
        m_type == OBJECT_CONTROLLER)
    {
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Controllable)] = true;
    }
    else
    {
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Controllable)] = false;
    }

    // TODO: Another one? :/
    if ( m_type == OBJECT_POWER   || // PowerCell
         m_type == OBJECT_ATOMIC  || // NuclearCell
         m_type == OBJECT_STATION || // PowerStation
         m_type == OBJECT_ENERGY   ) // PowerPlant
    {
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::PowerContainer)] = true;
    }
    else
    {
        m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::PowerContainer)] = false;
    }


    if ( m_type == OBJECT_MOBILEwc ||
         m_type == OBJECT_MOBILEtc ||
         m_type == OBJECT_MOBILEfc ||
         m_type == OBJECT_MOBILEic ||
         m_type == OBJECT_MOBILEwi ||
         m_type == OBJECT_MOBILEti ||
         m_type == OBJECT_MOBILEfi ||
         m_type == OBJECT_MOBILEii ||
         m_type == OBJECT_MOBILErc )  // cannon vehicle?
    {
        m_cameraType = Gfx::CAM_TYPE_ONBOARD;
    }
}

const char* COldObject::GetName()
{
    return m_name.c_str();
}

// Choosing the option to use.

void COldObject::SetOption(int option)
{
    m_option = option;
}

int COldObject::GetOption()
{
    return m_option;
}


// Saves all the parameters of the object.

void COldObject::Write(CLevelParserLine* line)
{
    Math::Vector    pos;

    line->AddParam("camera", MakeUnique<CLevelParserParam>(GetCameraType()));

    if ( GetCameraLock() )
        line->AddParam("cameraLock", MakeUnique<CLevelParserParam>(GetCameraLock()));

    if ( IsBulletWall() )
        line->AddParam("bulletWall", MakeUnique<CLevelParserParam>(IsBulletWall()));

    if ( GetEnergyLevel() != 0.0f )
        line->AddParam("energy", MakeUnique<CLevelParserParam>(GetEnergyLevel()));

    if ( GetShield() != 1.0f )
        line->AddParam("shield", MakeUnique<CLevelParserParam>(GetShield()));

    if ( GetRange() != 1.0f )
        line->AddParam("range", MakeUnique<CLevelParserParam>(GetRange()));

    if ( !GetSelectable() )
        line->AddParam("selectable", MakeUnique<CLevelParserParam>(GetSelectable()));

    if ( !GetCollisions() )
        line->AddParam("clip", MakeUnique<CLevelParserParam>(GetCollisions()));

    if ( GetLock() )
        line->AddParam("lock", MakeUnique<CLevelParserParam>(GetLock()));

    if ( !GetActivity() )
        line->AddParam("activity", MakeUnique<CLevelParserParam>(GetActivity()));

    if ( GetProxyActivate() )
    {
        line->AddParam("proxyActivate", MakeUnique<CLevelParserParam>(GetProxyActivate()));
        line->AddParam("proxyDistance", MakeUnique<CLevelParserParam>(GetProxyDistance()/g_unit));
    }

    if ( GetMagnifyDamage() != 1.0f )
        line->AddParam("magnifyDamage", MakeUnique<CLevelParserParam>(GetMagnifyDamage()));

    if ( GetTeam() != 0 )
        line->AddParam("team", MakeUnique<CLevelParserParam>(GetTeam()));

    if ( GetGunGoalV() != 0.0f )
        line->AddParam("aimV", MakeUnique<CLevelParserParam>(GetGunGoalV()));

    if ( GetGunGoalH() != 0.0f )
        line->AddParam("aimH", MakeUnique<CLevelParserParam>(GetGunGoalH()));

    if ( GetAnimateOnReset() )
    {
        line->AddParam("reset", MakeUnique<CLevelParserParam>(GetAnimateOnReset()));
    }

    if ( m_bVirusMode )
        line->AddParam("virusMode", MakeUnique<CLevelParserParam>(m_bVirusMode));

    if ( m_virusTime != 0.0f )
        line->AddParam("virusTime", MakeUnique<CLevelParserParam>(m_virusTime));

    line->AddParam("lifetime", MakeUnique<CLevelParserParam>(m_aTime));

    // Sets the parameters of the command line.
    CLevelParserParamVec cmdline;
    for(float value : GetCmdLine())
    {
        cmdline.push_back(MakeUnique<CLevelParserParam>(value));
    }
    if (cmdline.size() > 0)
        line->AddParam("cmdline", MakeUnique<CLevelParserParam>(std::move(cmdline)));

    if ( m_motion != nullptr )
    {
        m_motion->Write(line);
    }

    if ( Implements(ObjectInterfaceType::Programmable) )
    {
        line->AddParam("bVirusActive", MakeUnique<CLevelParserParam>(GetActiveVirus()));
    }

    if ( m_physics != nullptr )
    {
        m_physics->Write(line);
    }

    if ( m_auto != nullptr )
    {
        m_auto->Write(line);
    }

    // AlienWorm time up/down
    if (m_type == OBJECT_WORM)
    {
        assert(Implements(ObjectInterfaceType::Movable));
        CMotion* motion = GetMotion();
        CLevelParserParamVec param;
        param.push_back(std::make_unique<CLevelParserParam>(motion->GetParam(0)));
        param.push_back(std::make_unique<CLevelParserParam>(motion->GetParam(1)));
        line->AddParam("param", std::make_unique<CLevelParserParam>(std::move(param)));
    }
}

// Returns all parameters of the object.

void COldObject::Read(CLevelParserLine* line)
{
    Math::Vector zoom = line->GetParam("zoom")->AsPoint(Math::Vector(1.0f, 1.0f, 1.0f));
    if (zoom.x != 1.0f || zoom.y != 1.0f || zoom.z != 1.0f)
        SetScale(zoom);

    if (line->GetParam("camera")->IsDefined())
        SetCameraType(line->GetParam("camera")->AsCameraType());
    SetCameraLock(line->GetParam("cameraLock")->AsBool(false));

    if (line->GetParam("pyro")->IsDefined())
        m_engine->GetPyroManager()->Create(line->GetParam("pyro")->AsPyroType(), this);

    SetBulletWall(line->GetParam("bulletWall")->AsBool(IsBulletWallByDefault(m_type)));

    SetProxyActivate(line->GetParam("proxyActivate")->AsBool(false));
    SetProxyDistance(line->GetParam("proxyDistance")->AsFloat(15.0f)*g_unit);
    SetCollisions(line->GetParam("clip")->AsBool(true));
    SetAnimateOnReset(line->GetParam("reset")->AsBool(false));
    if (Implements(ObjectInterfaceType::Controllable))
    {
        SetSelectable(line->GetParam("selectable")->AsBool(IsSelectableByDefault(m_type)));
    }
    if (Implements(ObjectInterfaceType::JetFlying))
    {
        SetRange(line->GetParam("range")->AsFloat(30.0f));
    }
    if (Implements(ObjectInterfaceType::Fragile))
    {
        SetMagnifyDamage(line->GetParam("magnifyDamage")->AsFloat(1.0f)); // TODO: This is a temporary hack for now - CFragileObject doesn't have SetMagnifyDamage ~krzys_h
    }
    if (Implements(ObjectInterfaceType::Shielded))
    {
        SetShield(line->GetParam("shield")->AsFloat(1.0f));
        SetMagnifyDamage(line->GetParam("magnifyDamage")->AsFloat(1.0f));
    }
    if (Implements(ObjectInterfaceType::Programmable))
    {
        SetCheckToken(!line->GetParam("checkToken")->IsDefined() ? GetSelectable() : line->GetParam("checkToken")->AsBool(true));

        if (line->GetParam("cmdline")->IsDefined())
        {
            const auto& cmdline = line->GetParam("cmdline")->AsArray();
            for (unsigned int i = 0; i < cmdline.size(); i++)
            {
                SetCmdLine(i, cmdline[i]->AsFloat());
            }
        }
    }

    // SetManual will affect bot speed
    if (m_type == OBJECT_MOBILEdr)
    {
        // TODO: Merge these two settings?
        SetManual(!GetTrainer());
    }

    // AlienWorm time up/down
    // TODO: Refactor function names
    if (m_type == OBJECT_WORM)
    {
        assert(Implements(ObjectInterfaceType::Movable));
        CMotion* motion = GetMotion();
        if (line->GetParam("param")->IsDefined())
        {
            const auto& p = line->GetParam("param")->AsArray();
            for (unsigned int i = 0; i < 10 && i < p.size(); i++)
            {
                motion->SetParam(i, p[i]->AsFloat());
            }
        }
    }

    if (m_auto != nullptr)
    {
        // TODO: Is it used for anything else than AlienEggs?
        m_auto->SetType(line->GetParam("autoType")->AsObjectType(OBJECT_NULL));
        for (int i = 0; i < 5; i++)
        {
            std::string op = "autoValue" + boost::lexical_cast<std::string>(i+1); // autoValue1..autoValue5
            m_auto->SetValue(i, line->GetParam(op)->AsFloat(0.0f));
        }
        m_auto->SetString(const_cast<char*>(line->GetParam("autoString")->AsString("").c_str()));

        int i = line->GetParam("run")->AsInt(-1);
        if (i != -1)
        {
            if (i != PARAM_FIXSCENE && !CSettings::GetInstancePointer()->GetMovies()) i = 0;
            m_auto->Start(i);  // starts the film
        }
    }


    // Everthing below is for use only by saved scenes
    if (line->GetParam("energy")->IsDefined())
        SetEnergyLevel(line->GetParam("energy")->AsFloat());
    SetLock(line->GetParam("lock")->AsBool(false));
    SetActivity(line->GetParam("activity")->AsBool(true));
    SetGunGoalV(line->GetParam("aimV")->AsFloat(0.0f));
    SetGunGoalH(line->GetParam("aimH")->AsFloat(0.0f));

    if (line->GetParam("burnMode")->AsBool(false))
        SetDying(DeathType::Burning);
    m_bVirusMode = line->GetParam("virusMode")->AsBool(false);
    m_virusTime = line->GetParam("virusTime")->AsFloat(0.0f);

    m_aTime = line->GetParam("lifetime")->AsFloat(0.0f);

    if ( m_motion != nullptr )
    {
        m_motion->Read(line);
    }

    if (Implements(ObjectInterfaceType::Programmable))
    {
        SetActiveVirus(line->GetParam("bVirusActive")->AsBool(false));
    }

    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<m_totalPart ; i++ )
    {
        if ( !m_objectPart[i].bUsed )  continue;

        if ( parent == m_objectPart[i].parentPart )
        {
            if ( n-- == 0 )  return i;
        }
    }
    return -1;
}

void COldObject::TransformCrashSphere(Math::Sphere& crashSphere)
{
    if(!Implements(ObjectInterfaceType::Jostleable)) crashSphere.radius *= GetScaleX();

    // Returns to the sphere collisions,
    // which ignores the tilt of the vehicle.
    // This is necessary to collisions with vehicles,
    // so as not to reflect SetTilt, for example.
    // The sphere must necessarily have a center (0, y, 0).
    if (m_crashSpheres.size() == 1 &&
        crashSphere.pos.x == 0.0f &&
        crashSphere.pos.z == 0.0f )
    {
        crashSphere.pos += m_objectPart[0].position;
        return;
    }

    if (m_objectPart[0].bTranslate ||
        m_objectPart[0].bRotate)
    {
        UpdateTransformObject();
    }

    crashSphere.pos = Math::Transform(m_objectPart[0].matWorld, crashSphere.pos);
}

void COldObject::TransformCameraCollisionSphere(Math::Sphere& collisionSphere)
{
    collisionSphere.pos = Math::Transform(m_objectPart[0].matWorld, collisionSphere.pos);
    collisionSphere.radius *= GetScaleX();
}


// Specifies the sphere of jostling, relative to the object.

void COldObject::SetJostlingSphere(const Math::Sphere& jostlingSphere)
{
    m_jostlingSphere = jostlingSphere;
    m_implementedInterfaces[static_cast<int>(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;
}


// 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) )
    {
        a = GetRotationY();
        nn = Math::RotatePoint(-a, Math::Point(n.z, n.x));
        SetRotationX( sinf(nn.x));
        SetRotationZ(-sinf(nn.y));
    }
}


// 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->SetObjectShadowSpotPos(rank, shPos);

        float height = 0.0f;
        if ( Implements(ObjectInterfaceType::Flying) )
        {
            height = pos.y-shPos.y;
        }
        m_engine->SetObjectShadowSpotHeight(rank, height);

        m_engine->UpdateObjectShadowSpotNormal(rank);

        if ( m_shadowLight != -1 )
        {
            Math::Vector lightPos = pos;
            lightPos.y += m_shadowHeight;
            m_lightMan->SetLightPos(m_shadowLight, 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->SetObjectShadowSpotAngle(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->SetObjectShadowSpotAngle(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;
}

bool COldObject::GetPlusTrainer()
{
    return m_main->GetPlusTrainer();
}

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.


int COldObject::GetNumSlots()
{
    assert(m_hasPowerSlot || m_hasCargoSlot); // otherwise implemented[CSlottedObject] is false
    return (m_hasPowerSlot ? 1 : 0) + (m_hasCargoSlot ? 1 : 0);
}
int COldObject::MapPseudoSlot(Pseudoslot pseudoslot)
{
    switch (pseudoslot)
    {
    case Pseudoslot::POWER:
        return m_hasPowerSlot ? 0 : -1;
    case Pseudoslot::CARRYING:
        return m_hasCargoSlot ? (m_hasPowerSlot ? 1 : 0) : -1;
    default:
        return -1;
    }
}
Math::Vector COldObject::GetSlotPosition(int slotNum)
{
    if (slotNum == 0 && m_hasPowerSlot)
        return m_powerPosition;
    else
    {
        assert(m_hasCargoSlot && slotNum == (m_hasPowerSlot ? 1 : 0));
        int grabPartNum;
        Math::Vector grabRelPos;
        // See CTaskManip::TransporterTakeObject call to SetTransporterPart and SetPosition
        switch (m_type)
        {
        case OBJECT_HUMAN:
        case OBJECT_TECH:
            grabPartNum = 4;
            grabRelPos = Math::Vector(1.7f, -0.5f, 1.1f);
            break;
        case OBJECT_MOBILEsa: // subber
            grabPartNum = 2;
            grabRelPos = Math::Vector(1.1f, -1.0f, 1.0f);
            break;
        case OBJECT_MOBILEfa: // Grabbers
        case OBJECT_MOBILEta:
        case OBJECT_MOBILEwa:
        case OBJECT_MOBILEia:
            grabPartNum = 3;
            grabRelPos = Math::Vector(4.7f, 0.0f, 0.0f);
            break;
        case OBJECT_BEE:
            grabPartNum = 0;
            grabRelPos = Math::Vector(0.0f, -3.0f, 0.0f);
            break;
        default: // unreachable, only the above objects have cargo slots
            assert(!m_hasCargoSlot);
            return m_powerPosition;
        }

        return Math::Transform(GetWorldMatrix(0)->Inverse(), Math::Transform(*GetWorldMatrix(grabPartNum), grabRelPos));
    }
}
float COldObject::GetSlotAngle(int slotNum)
{
    if (slotNum == 0 && m_hasPowerSlot)
    {
        switch (m_type)
        {
        case OBJECT_TOWER:
        case OBJECT_RESEARCH:
        case OBJECT_ENERGY:
        case OBJECT_LABO:
        case OBJECT_NUCLEAR:
            return 0;
        default: // robots
            return Math::PI;
        }
    }
    else
    {
        assert(m_hasCargoSlot && slotNum == (m_hasPowerSlot ? 1 : 0));
        return 0;
    }
}
float COldObject::GetSlotAcceptanceAngle(int slotNum)
{
    if (slotNum == 0 && m_hasPowerSlot)
    {
        switch (m_type)
        {
        case OBJECT_TOWER:
        case OBJECT_RESEARCH:
            return 45.0f*Math::PI/180.0f;
        case OBJECT_ENERGY:
            return 90.0f*Math::PI/180.0f;
        case OBJECT_LABO:
            return 120.0f*Math::PI/180.0f;
        case OBJECT_NUCLEAR:
            return 45.0f*Math::PI/180.0f;
        default:
            return 45.0f*Math::PI/180.0f;
        }
    }
    else
    {
        assert(m_hasCargoSlot && slotNum == (m_hasPowerSlot ? 1 : 0));
        return 0; // no acceptance angle for cargo slot
    }
}
CObject *COldObject::GetSlotContainedObject(int slotNum)
{
    if (slotNum == 0 && m_hasPowerSlot)
        return m_power;
    else
    {
        assert(m_hasCargoSlot && slotNum == (m_hasPowerSlot ? 1 : 0));
        return m_cargo;
    }
}
void COldObject::SetSlotContainedObject(int slotNum, CObject *object)
{
    if (slotNum == 0 && m_hasPowerSlot)
        m_power = object;
    else
    {
        assert(m_hasCargoSlot && slotNum == (m_hasPowerSlot ? 1 : 0));
        m_cargo = object;
    }
}
// not part of CSlottedObject; just used for initialization
void COldObject::SetPowerPosition(const Math::Vector& powerPosition)
{
    m_powerPosition = powerPosition;
}



// 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->SetObjectShadowSpotHide(m_objectPart[0].object, (m_transporter != nullptr));
}

CObject* COldObject::GetTransporter()
{
    return m_transporter;
}

// Management of the conveying portion.

void COldObject::SetTransporterPart(int part)
{
    m_transporterLink = part;
}


// 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 ; i<OBJECTMAXPART ; i++ )
    {
        if ( m_objectPart[i].bUsed )
        {
            m_engine->SetObjectDrawFront(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 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->CreateShadowSpot(m_objectPart[0].object);

    m_engine->SetObjectShadowSpotRadius(m_objectPart[0].object, radius*zoom);
    m_engine->SetObjectShadowSpotIntensity(m_objectPart[0].object, intensity);
    m_engine->SetObjectShadowSpotHeight(m_objectPart[0].object, 0.0f);
    m_engine->SetObjectShadowSpotAngle(m_objectPart[0].object, m_objectPart[0].angle.y);
    m_engine->SetObjectShadowSpotType(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 != nullptr )  // 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 != nullptr )  // 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<m_totalPart ; level1++ )
        {
            if ( !m_objectPart[level1].bUsed )  continue;
            UpdateTransformObject(level1, false);
        }
    }
    else
    {
        parent1 = 0;
        bUpdate1 = UpdateTransformObject(parent1, false);

        for ( level1=0 ; level1<m_totalPart ; level1++ )
        {
            rank = SearchDescendant(parent1, level1);
            if ( rank == -1 )  break;

            parent2 = rank;
            bUpdate2 = UpdateTransformObject(rank, bUpdate1);

            for ( level2=0 ; level2<m_totalPart ; level2++ )
            {
                rank = SearchDescendant(parent2, level2);
                if ( rank == -1 )  break;

                parent3 = rank;
                bUpdate3 = UpdateTransformObject(rank, bUpdate2);

                for ( level3=0 ; level3<m_totalPart ; level3++ )
                {
                    rank = SearchDescendant(parent3, level3);
                    if ( rank == -1 )  break;

                    parent4 = rank;
                    bUpdate4 = UpdateTransformObject(rank, bUpdate3);

                    for ( level4=0 ; level4<m_totalPart ; level4++ )
                    {
                        rank = SearchDescendant(parent4, level4);
                        if ( rank == -1 )  break;

                        UpdateTransformObject(rank, bUpdate4);
                    }
                }
            }
        }
    }

    return true;
}


// Puts all the progeny flat (there is more than fathers).
// This allows for debris independently from each other in all directions.

void COldObject::FlatParent()
{
    int     i;

    for ( i=0 ; i<m_totalPart ; i++ )
    {
        m_objectPart[i].position.x = m_objectPart[i].matWorld.Get(1, 4);
        m_objectPart[i].position.y = m_objectPart[i].matWorld.Get(2, 4);
        m_objectPart[i].position.z = m_objectPart[i].matWorld.Get(3, 4);

        m_objectPart[i].matWorld.Set(1, 4, 0.0f);
        m_objectPart[i].matWorld.Set(2, 4, 0.0f);
        m_objectPart[i].matWorld.Set(3, 4, 0.0f);

        m_objectPart[i].matTranslate.Set(1, 4, 0.0f);
        m_objectPart[i].matTranslate.Set(2, 4, 0.0f);
        m_objectPart[i].matTranslate.Set(3, 4, 0.0f);

        m_objectPart[i].parentPart = -1;  // more parents
    }

    m_bFlat = true;
}



// Updates the mapping of the texture of the pile.

void COldObject::UpdateEnergyMapping()
{
    if (Math::IsEqual(GetEnergyLevel(), m_lastEnergy, 0.01f))
        return;

    m_lastEnergy = GetEnergyLevel();

    Gfx::Material mat;
    mat.diffuse = Gfx::Color(1.0f, 1.0f, 1.0f);  // white
    mat.ambient = Gfx::Color(0.5f, 0.5f, 0.5f);

    float a = 0.0f, b = 0.0f;

    if ( m_type == OBJECT_POWER  ||
         m_type == OBJECT_ATOMIC )
    {
        a = 2.0f;
        b = 0.0f;  // dimensions of the battery (according to y)
    }
    else if ( m_type == OBJECT_STATION )
    {
        a = 10.0f;
        b =  4.0f;  // dimensions of the battery (according to y)
    }
    else if ( m_type == OBJECT_ENERGY )
    {
        a = 9.0f;
        b = 3.0f;  // dimensions of the battery (according to y)
    }

    float i = 0.50f+0.25f*GetEnergyLevel();  // origin
    float s = i+0.25f;  // width

    float au = (s-i)/(b-a);
    float bu = s-b*(s-i)/(b-a);

    std::string teamStr = StrUtils::ToString<int>(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)
{
    // NOTE: This should be called befoce CProgrammableObjectImpl::EventProcess, see the other note inside this function
    if (!CTaskExecutorObjectImpl::EventProcess(event)) return false;

    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 ( !IsDying() )  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 ( GetDying() == DeathType::Burning )  // Burning?
            {
                axeZ = -1.0f;  // tomb

                if ( (m_type == OBJECT_ANT    ||
                      m_type == OBJECT_SPIDER ||
                      m_type == OBJECT_WORM   ) )
                {
                    // TODO: Move to CBaseAlien?
                    CBaseAlien* alien = dynamic_cast<CBaseAlien*>(this);
                    assert(alien != nullptr);
                    if (!alien->GetFixed())
                    {
                        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::TaskExecutor))
            {
                canMove = canMove && (!IsForegroundTask() || GetForegroundTask()->IsPilot());
            }
            if (Implements(ObjectInterfaceType::Programmable))
            {
                canMove = canMove && !IsProgram();
            }

            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_type == OBJECT_HUMAN && !m_main->IsResearchDone(RESEARCH_FLY, GetTeam()) )
                {
                    axeZ = -1.0f;  // tomb
                }

                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 )
    {
        if (!GetLock())
        {
            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 false;

    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 ( GetDying() == DeathType::Burning )  m_burnTime += event.rTime;

    m_aTime += event.rTime;
    m_shotTime += event.rTime;

    VirusFrame(event.rTime);
    PartiFrame(event.rTime);

    UpdateMapping();
    UpdateTransformObject();
    UpdateSelectParticle();

    if (Implements(ObjectInterfaceType::ShieldedAutoRegen))
    {
        SetShield(GetShield() + event.rTime*(1.0f/GetShieldFullRegenTime()));
    }

    if (m_damaging && m_time - m_damageTime > 2.0f)
    {
        SetDamaging(false);
        m_main->UpdateShortcuts();
    }

    return true;
}

// Updates the mapping of the object.

void COldObject::UpdateMapping()
{
    if ( Implements(ObjectInterfaceType::PowerContainer) )
    {
        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 ; i<OBJECTMAXPART ; i++ )
    {
        if ( !m_objectPart[i].bUsed )  continue;

        channel = m_objectPart[i].masterParti;
        if ( channel == -1 )  continue;

        if ( !m_particle->GetPosition(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::AdjustCamera(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 ||
              m_type == OBJECT_MOBILErp )
    {
        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 ||
              m_type == OBJECT_MOBILEst )
    {
        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 ( 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 ? m_main->GetGlobalNuclearCapacity() : m_main->GetGlobalCellCapacity() ;
}

bool COldObject::IsRechargeable()
{
    return m_type == OBJECT_POWER;
}


// Management of the shield.

void COldObject::SetShield(float level)
{
    if (level > 1.0f) level = 1.0f;
    if (level < 0.0f) level = 0.0f;
    m_shield = level;
}

float COldObject::GetShield()
{
    if (Implements(ObjectInterfaceType::Fragile))  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;
}

void COldObject::SetReactorRange(float reactorRange)
{
    if (reactorRange > 1.0f) reactorRange = 1.0f;
    if (reactorRange < 0.0f) reactorRange = 0.0f;
    m_reactorRange = reactorRange;
}

float COldObject::GetReactorRange()
{
    return m_reactorRange;
}


// Management of transparency of the object.

void COldObject::SetTransparency(float value)
{
    int     i;

    for ( i=0 ; i<m_totalPart ; i++ )
    {
        if ( m_objectPart[i].bUsed )
        {
            if ( m_type == OBJECT_BASE )
            {
                if ( i != 9 )  continue;  // no central pillar?
            }

            m_engine->SetObjectTransparency(m_objectPart[i].object, value);
        }
    }
}


// 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<CAutoJostle>(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::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<Ui::CObjectInterface>(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;
}


// Sets if this object is underground or not. Underground objects are not detectable. Used by AlienWorm

void COldObject::SetUnderground(bool underground)
{
    m_underground = underground;
}


// Management of the method of increasing damage.

void COldObject::SetMagnifyDamage(float factor)
{
    m_magnifyDamage = factor;
}

float COldObject::GetMagnifyDamage()
{
    return m_magnifyDamage;
}

void COldObject::SetDamaging(bool damaging)
{
    m_damaging = damaging;
}

bool COldObject::IsDamaging()
{
    return m_damaging;
}

void COldObject::SetDying(DeathType deathType)
{
    m_dying = deathType;
    m_burnTime = 0.0f;

    if ( IsDying() && Implements(ObjectInterfaceType::Programmable) )
    {
        StopProgram();  // stops the current task
    }
}

DeathType COldObject::GetDying()
{
    return m_dying;
}

bool COldObject::IsDying()
{
    return m_dying != DeathType::Alive;
}

bool COldObject::GetActive()
{
    // Dying astronaut (m_dying == DeathType::Dead) should be treated as active
    // This is for EndMissionTake to not detect him as actually dead until the animation is finished

    return !GetLock() && !(Implements(ObjectInterfaceType::Destroyable) && IsDying() && GetDying() != DeathType::Dead) && !m_bFlat;
}

bool COldObject::GetDetectable()
{
    return GetActive() && !m_underground;
}


// 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 ||
         m_type == OBJECT_MOBILEfb ||
         m_type == OBJECT_MOBILEtb ||
         m_type == OBJECT_MOBILEwb ||
         m_type == OBJECT_MOBILEib)  // 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 ||
         m_type == OBJECT_MOBILEfb ||
         m_type == OBJECT_MOBILEtb ||
         m_type == OBJECT_MOBILEwb ||
         m_type == OBJECT_MOBILEib)  // 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;
}

float COldObject::GetShowLimitRadius()
{
    if ( m_type == OBJECT_BASE     ) return 200.0f; // SpaceShip
    if ( m_type == OBJECT_MOBILErt ) return 400.0f; // Thumper
    if ( m_type == OBJECT_TOWER    ) return Gfx::LTNG_PROTECTION_RADIUS; // DefenseTower
    if ( m_type == OBJECT_PARA     ) return Gfx::LTNG_PROTECTION_RADIUS; // PowerCaptor
    return 0.0f;
}


// 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_MOBILEfb ||
             m_type == OBJECT_MOBILEtb ||
             m_type == OBJECT_MOBILEwb ||
             m_type == OBJECT_MOBILEib ||
             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_MOBILErp ||
             m_type == OBJECT_MOBILEst ||
             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 ||
         m_type == OBJECT_MOBILErp )  // 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_MOBILEsa ||
              m_type == OBJECT_MOBILEst )  // 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 if ( m_type == OBJECT_MOBILEwt ||
              m_type == OBJECT_MOBILEtt ||
              m_type == OBJECT_MOBILEft ||
              m_type == OBJECT_MOBILEit ||
              GetTrainer())                // 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
    {
        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_MOBILEwt ||
         m_type == OBJECT_MOBILEtt ||
         m_type == OBJECT_MOBILEft ||
         m_type == OBJECT_MOBILEit ||
         GetTrainer())               // trainer?
    {
        pos[2] = Math::Vector(-4.0f, 2.5f,  2.2f);
        pos[3] = Math::Vector(-4.0f, 2.5f, -2.2f);
    }
    else if ( m_type == OBJECT_MOBILEfa ||
              m_type == OBJECT_MOBILEfb ||
              m_type == OBJECT_MOBILEfc ||
              m_type == OBJECT_MOBILEfi ||
              m_type == OBJECT_MOBILEfs )  // 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;
    }
    else if ( m_type == OBJECT_MOBILEwa ||
              m_type == OBJECT_MOBILEwb ||
              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);
    }
    else if ( m_type == OBJECT_MOBILEia ||
              m_type == OBJECT_MOBILEib ||
              m_type == OBJECT_MOBILEic ||
              m_type == OBJECT_MOBILEii ||
              m_type == OBJECT_MOBILEis )  // legs?
    {
        pos[2] = Math::Vector(-4.5f, 2.7f,  2.8f);
        pos[3] = Math::Vector(-4.5f, 2.7f, -2.8f);
    }
    else if ( m_type == OBJECT_MOBILEta ||
              m_type == OBJECT_MOBILEtb ||
              m_type == OBJECT_MOBILEtc ||
              m_type == OBJECT_MOBILEti ||
              m_type == OBJECT_MOBILEts )  // caterpillars?
    {
        pos[2] = Math::Vector(-3.6f, 4.2f,  3.0f);
        pos[3] = Math::Vector(-3.6f, 4.2f, -3.0f);
    }
    else 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_MOBILErp || ( GetTrainer() &&
       ( m_type == OBJECT_MOBILErt ||
         m_type == OBJECT_MOBILErc ||
         m_type == OBJECT_MOBILErr ||
         m_type == OBJECT_MOBILErs)))  // large caterpillars (trainer)?
    {
        pos[2] = Math::Vector(-4.6f, 5.2f,  2.6f);
        pos[3] = Math::Vector(-4.6f, 5.2f, -2.6f);
    }
    if ( m_type == OBJECT_MOBILEsa ||
         m_type == OBJECT_MOBILEst )  // 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++ )
    {
        if (m_partiSel[i] == -1) continue;
        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();
}

// Returns the movement associated to the object.

CMotion* COldObject::GetMotion()
{
    return m_motion.get();
}

// TODO: Temporary hack until we'll have subclasses for objects
void COldObject::SetProgrammable()
{
    m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::ProgramStorage)] = true;
    m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Programmable)] = true;
}

// TODO: Another hack
void COldObject::SetMovable(std::unique_ptr<CMotion> motion, std::unique_ptr<CPhysics> physics)
{
    m_motion = std::move(motion);
    m_physics = std::move(physics);
    m_implementedInterfaces[static_cast<int>(ObjectInterfaceType::Movable)] = true;
}

// Returns the controller associated to the object.

CAuto* COldObject::GetAuto()
{
    return m_auto.get();
}

void COldObject::SetAuto(std::unique_ptr<CAuto> automat)
{
    m_auto = std::move(automat);
}


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);
}

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);

    if (m_type != OBJECT_HUMAN) // Be sure not to stop the death animation!
    {
        m_motion->SetAction(-1);
    }
}

// State management of the pencil drawing robot.

bool COldObject::GetTraceDown()
{
    return m_traceDown;
}

void COldObject::SetTraceDown(bool down)
{
    m_traceDown = down;
}

TraceColor COldObject::GetTraceColor()
{
    return m_traceColor;
}

void COldObject::SetTraceColor(TraceColor color)
{
    m_traceColor = color;
}

float COldObject::GetTraceWidth()
{
    return m_traceWidth;
}

void COldObject::SetTraceWidth(float width)
{
    m_traceWidth = width;
}

bool COldObject::IsRepairable()
{
    if (m_type == OBJECT_HUMAN) return false;
    return true;
}

float COldObject::GetShieldFullRegenTime()
{
    if (m_type == OBJECT_HUMAN) return 120.0f;
    assert(false);
    return 0.0f;
}

float COldObject::GetLightningHitProbability()
{
    if ( m_type == OBJECT_BASE     ||
         m_type == OBJECT_DERRICK  ||
         m_type == OBJECT_FACTORY  ||
         m_type == OBJECT_REPAIR   ||
         m_type == OBJECT_DESTROYER||
         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   )  // building?
    {
        return 1.0f;
    }
    if ( m_type == OBJECT_METAL    ||
         m_type == OBJECT_POWER    ||
         m_type == OBJECT_ATOMIC   ) // resource?
    {
        return 0.3f;
    }
    if ( m_type == OBJECT_MOBILEfa ||
         m_type == OBJECT_MOBILEta ||
         m_type == OBJECT_MOBILEwa ||
         m_type == OBJECT_MOBILEia ||
         m_type == OBJECT_MOBILEfb ||
         m_type == OBJECT_MOBILEtb ||
         m_type == OBJECT_MOBILEwb ||
         m_type == OBJECT_MOBILEib ||
         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_MOBILEft ||
         m_type == OBJECT_MOBILEtt ||
         m_type == OBJECT_MOBILEwt ||
         m_type == OBJECT_MOBILEit ||
         m_type == OBJECT_MOBILErp ||
         m_type == OBJECT_MOBILEst ||
         m_type == OBJECT_MOBILEtg ||
         m_type == OBJECT_MOBILEdr )  // robot?
    {
        return 0.5f;
    }
    return 0.0f;
}

bool COldObject::IsSelectableByDefault(ObjectType type)
{
    if ( type == OBJECT_MOTHER   ||
         type == OBJECT_ANT      ||
         type == OBJECT_SPIDER   ||
         type == OBJECT_BEE      ||
         type == OBJECT_WORM     )
    {
        return false;
    }
    return true;
}

void COldObject::SetBulletWall(bool bulletWall)
{
    m_bulletWall = bulletWall;
}

bool COldObject::IsBulletWall()
{
    return m_bulletWall;
}

bool COldObject::IsBulletWallByDefault(ObjectType type)
{
    if ( type == OBJECT_BARRICADE0 ||
         type == OBJECT_BARRICADE1 )
    {
        return true;
    }
    return false;
}
