Adding vehicles

Get technical support about the C++ source code and about Lua scripts for maps, entities, GUIs, the console, materials, etc. Also covered are the Cafu libraries and APIs, as well as compiling, linking, and the build system.
Stoo
Posts:42
Joined:2012-05-11, 00:28
Re: Adding vehicles

Post by Stoo » 2012-08-18, 16:17

It works!!! :cheesy: :cheesy: :cheesy: :groupwave1:

...well, mostly :oops:

Right now the player can hit the activate key; and if it detects a vehicle, the player's state will be set to Piloting, the player will forward all commands to the vehicle, and the vehicle will respond to the command queue (right now, just by rotating and moving forward).

I still have some outstanding issues:

The vehicle collides with walls properly, but when the player exits, he still is only able to collide with the vehicle in its original position - he can walk through the rendered vehicle at its new position, but can't walk through where the vehicle was originally. I'm guessing this is just due to the way I moved the vehicle; I'm about to see how CompanyBot differs from this:

Code: Select all

if (Keys & PCK_MoveForward ) WishVelocity=             VectorT( VelX,  VelY, 0);
m_Physics.MoveHuman(PlayerCommands[PCNr].FrameTime, m_Heading, WishVelocity, WishVelocity, WishJump, WishJump, 470.0); 
I basically just yanked it out of HumanPlayer and stuck it in there to see if I could get a quick result :P

I also haven't figured out yet how to determine if the player is "touching" the vehicle - ideally, I'd cast a ray or something straight out from the player's crosshairs, find whatever's under the target, and read the length of the ray. If the target is a vehicle and the distance low enough, the player would hop in. I've been looking at your CheckGUI code to try to figure out how this might be done, but I'm having trouble untangling the crosshair->gui cursor parts from the facing and rangecheck parts...

Still, I can jump in and drive around! :cheesy: :mrgreen: :cheesy:
(I assume you refer to something like "surveillance cameras" in "camera views in world guis!" ?)

Yes, this would be nice, but it requires rendering the (surveillance) camera frame to a texture buffer, then rendering this in turn as part of a GUI desktop from the player cameras perspective. The same technique (render-to-texture) also has many other uses, and thus the plan is to implement it universally.
Surveillance camera-type stuff was my first thought; but it'd also make for awesome cockpits, be a fun addition to a seeking missile weapon (or deployed drone), squad tactics might involve seeing what other teammates see (a la Portal 2)... there's a lot of options! Glad it's on your list, though I'm in no hurry - I still have a lot of basic gameplay to implement before I reach this stage!
(Joystick input requires re-introducing DirectInput under Windows. I can't help you much with it, but it shouldn't be a big problem.)
Also a fair ways off - especially as I'm developing on Linux :P Do plan to do both OS's; I expect I'll be able to deal with that when I have a little bit more base gameplay...
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Adding vehicles

Post by Carsten » 2012-08-20, 12:01

Stoo wrote:It works!!! :cheesy: :cheesy: :cheesy: :groupwave1:

...well, mostly :oops:

Right now the player can hit the activate key; and if it detects a vehicle, the player's state will be set to Piloting, the player will forward all commands to the vehicle, and the vehicle will respond to the command queue (right now, just by rotating and moving forward).
Wow, congratulations, it's great to hear this!! :wohow:
The vehicle collides with walls properly, but when the player exits, he still is only able to collide with the vehicle in its original position - he can walk through the rendered vehicle at its new position, but can't walk through where the vehicle was originally.
Without knowing your code, it's hard to tell what the problem might be.
Generally spoken, you probably have to update both the human player as well as the vehicles position (possibly making sure to exclude them both from colliding with themselves when physics computations are applied).
I also haven't figured out yet how to determine if the player is "touching" the vehicle - ideally, I'd cast a ray or something straight out from the player's crosshairs, find whatever's under the target, and read the length of the ray. If the target is a vehicle and the distance low enough, the player would hop in. I've been looking at your CheckGUI code to try to figure out how this might be done, but I'm having trouble untangling the crosshair->gui cursor parts from the facing and rangecheck parts...
Casting a ray should be relatively straightforward, e.g. the weapons that fire projectiles do it as well.

Additionally or alternatively, you might check (possibly slightly expanded) bounding-boxes for intersection, although special care must then be taken when the player exits the vehicle (placing it far enough away so that it doesn't immediately re-enter).
Still, I can jump in and drive around! :cheesy: :mrgreen: :cheesy:
:cheesy: :up:
Best regards,
Carsten
Stoo
Posts:42
Joined:2012-05-11, 00:28

Re: Adding vehicles

Post by Stoo » 2012-08-25, 01:29

Well, I'd hoped to polish the code a bit more than this before posting it; but it's been a busy week, and I don't want to risk getting sucked into making it *absolutely* perfect. Plus, I'm still on r561, and don't want to fall too far behind :P So, heads up, massive code blocks ahead! :lol:

I've added a new entity type, called EntVehicleT; which I based on the CompanyBot entity. Here's my Vehicle.hpp:

Code: Select all

#ifndef CAFU_VEHICLE_HPP_INCLUDED
#define CAFU_VEHICLE_HPP_INCLUDED

#include "../../BaseEntity.hpp"
#include "Libs/Physics.hpp"
#include "Models/AnimExpr.hpp"
#include "btBulletDynamicsCommon.h"

#include "HumanPlayer.hpp" 
#include "../../PlayerCommand.hpp" 

class CafuModelT;
class EntityCreateParamsT;

class EntHumanPlayerT; //This and HumanPlayer refer to each other; not sure yet which should have the forward declaration...

class EntVehicleT : public BaseEntityT, public btMotionState
{
    public:

    EntVehicleT(const EntityCreateParamsT& Params);
    ~EntVehicleT();

    void SetHeading(unsigned short h) { m_Heading = h; }

    // Implement the BaseEntityT interface.
    void TakeDamage(BaseEntityT* Entity, char Amount, const VectorT& ImpactDir);
    void Think(float FrameTime, unsigned long ServerFrameNr);

    bool GetLightSourceInfo(unsigned long& DiffuseColor, unsigned long& SpecularColor, VectorT& Position, float& Radius, bool& CastsShadows) const;
    void Draw(bool FirstPersonView, float LodDist) const;
    void PostDraw(float FrameTime, bool FirstPersonView);

    // Implement the btMotionState interface.
    void getWorldTransform(btTransform& worldTrans) const;
    void setWorldTransform(const btTransform& worldTrans);

    const cf::TypeSys::TypeInfoT* GetType() const;
    static void* CreateInstance(const cf::TypeSys::CreateParamsT& Params);
    static const cf::TypeSys::TypeInfoT TypeInfo;

    //New vehicle functions
    void AcceptPilot(EntHumanPlayerT* pilot);
    void RemovePilot(); //EjectPilot will be a very different function from just exiting!
    void AcceptCommand(PlayerCommandT command); //Just player commands for now
    
    Vector3fT GetVector(); //Utility method for finding position (to determine player proximity)

    private:

    void DoDeserialize(cf::Network::InStreamT& Stream);     // Override the BaseEntityT base class method.
    
    //Set of potential states. Idle is useable, Inoperative is "dead".
    enum VehicleStateT { Idle, HumanOperated, NPCOperated, Inoperative  };

    void AdvanceModelTime(float Time, bool Loop);

    //Store commands passed from the player
    ArrayT<PlayerCommandT> PlayerCommands;

    VehicleStateT       VehicleState; //Current state
    EntHumanPlayerT*   Pilot; //Referenced to be able to update view
    PhysicsHelperT    m_Physics;
    const CafuModelT* m_VehicleModel;
    IntrusivePtrT<AnimExpressionT>   m_AnimExpr;
    IntrusivePtrT<AnimExprStandardT> m_LastStdAE;
    const CafuModelT* m_WeaponModel;
    float             m_TimeForLightSource;

    btCollisionShape* m_CollisionShape;     ///< The collision shape that is used to approximate and represent this player in the physics world.
    btRigidBody*      m_RigidBody;          ///< The rigid body (of "kinematic" type) for use in the physics world.
};

#endif
..and the corresponding Vehicle.cpp:

Code: Select all

#include "Vehicle.hpp"
#include "EntityCreateParams.hpp"
#include "HumanPlayer.hpp"
#include "PhysicsWorld.hpp"
#include "TypeSys.hpp"
#include "Libs/LookupTables.hpp"
#include "Libs/Physics.hpp"
#include "../../GameWorld.hpp"
#include "ClipSys/ClipWorld.hpp"
#include "ClipSys/CollisionModelMan.hpp"
#include "ClipSys/TraceResult.hpp"
#include "ConsoleCommands/ConVar.hpp"
#include "MaterialSystem/Material.hpp"
#include "MaterialSystem/MaterialManager.hpp"
#include "MaterialSystem/Renderer.hpp"
#include "Models/AnimPose.hpp"
#include "Models/Model_cmdl.hpp"

#include <iostream>  //For cout

// Implement the type info related code.
const cf::TypeSys::TypeInfoT* EntVehicleT::GetType() const
{ return &TypeInfo; }

void* EntVehicleT::CreateInstance(const cf::TypeSys::CreateParamsT& Params)
{ return new EntVehicleT(*static_cast<const EntityCreateParamsT*>(&Params)); }

const cf::TypeSys::TypeInfoT EntVehicleT::TypeInfo(GetBaseEntTIM(), "EntVehicleT", "BaseEntityT", EntVehicleT::CreateInstance, NULL /*MethodsList*/);


EntVehicleT::EntVehicleT(const EntityCreateParamsT& Params)
    : BaseEntityT(Params,
                  BoundingBox3dT(Vector3dT( 600.0,  600.0,   200.0),
                                 Vector3dT(-600.0, -600.0, -3457.6)),   // Nice big bounding box
                  0,
                  EntityStateT(VectorT(),
                               0,
                               0,
                               0,       // ModelIndex
                               0,       // ModelSequNr
                               0.0,     // ModelFrameNr
                               80,      // Health
                               0,       // Armor
                               0,       // HaveItems
                               0,       // HaveWeapons
                               0,       // ActiveWeaponSlot
                               0,       // ActiveWeaponSequNr
                               0.0)),   // ActiveWeaponFrameNr
      m_Physics(m_Origin, State.Velocity, m_Dimensions, ClipModel, GameWorld->GetClipWorld()),
      m_VehicleModel(Params.GameWorld->GetModel("Games/DeathMatch/Models/Players/Sentinel/Sentinel.cmdl")), //Hard-coded, vaguely mechlike model for now
      m_AnimExpr(),
      m_LastStdAE(),
      m_WeaponModel(Params.GameWorld->GetModel("Games/DeathMatch/Models/Weapons/DesertEagle/DesertEagle_p.cmdl")),
      m_TimeForLightSource(0.0f)
{
    m_LastStdAE=m_VehicleModel->GetAnimExprPool().GetStandard(State.ModelSequNr, State.ModelFrameNr);
    m_AnimExpr =m_LastStdAE;

    // First, create a BB of dimensions (-300.0, -300.0, -100.0) - (300.0, 300.0, 100.0).
    const BoundingBox3T<double> ClearingBB(VectorT(m_Dimensions.Min.x*2, m_Dimensions.Min.y*2, -m_Dimensions.Max.z*2), m_Dimensions.Max*2);

    // Move ClearingBB up to a reasonable height (if possible!), such that the *full* BB (that is, m_Dimensions) is clear of (not stuck in) solid.
    cf::ClipSys::TraceResultT Result(1.0);
    GameWorld->GetClipWorld().TraceBoundingBox(ClearingBB, m_Origin, VectorT(0.0, 0.0, 3000.0), MaterialT::Clip_Players, &ClipModel, Result);
    const double AddHeight=3000.0*Result.Fraction;

    // Move ClearingBB down as far as possible.
    GameWorld->GetClipWorld().TraceBoundingBox(ClearingBB, m_Origin+VectorT(0.0, 0.0, AddHeight), VectorT(0.0, 0.0, -999999.0), MaterialT::Clip_Players, &ClipModel, Result);
    const double SubHeight=999999.0*Result.Fraction;

    m_Origin.z=m_Origin.z+AddHeight-SubHeight+(ClearingBB.Min.z-m_Dimensions.Min.z/*1628.8*/)+1.23456789/*Epsilon (sonst Ruckeln am Anfang!)*/;

    if (CollisionModel==NULL)
    {
        // No "normal" collision model has been set for this company bot.
        // Now simply setup a bounding box as the collision model.
        CollisionModel=cf::ClipSys::CollModelMan->GetCM(m_Dimensions, MaterialManager->GetMaterial("Textures/meta/collisionmodel"));
        ClipModel.SetCollisionModel(CollisionModel);
        ClipModel.SetOrigin(m_Origin);
        ClipModel.Register();
    }

    // The /1000 is because our physics world is in meters.
    m_CollisionShape=new btBoxShape(conv((m_Dimensions.Max-m_Dimensions.Min)/2.0/1000.0));  // Should use a btCylinderShapeZ instead of btBoxShape?

    // Our rigid body is of Bullet type "kinematic". That is, we move it ourselves, not the world dynamics.
    m_RigidBody=new btRigidBody(btRigidBody::btRigidBodyConstructionInfo(0, this /*btMotionState for this body*/, m_CollisionShape, btVector3()));

    m_RigidBody->setUserPointer(this);  // This entity is associated to the m_RigidBody.
    m_RigidBody->setCollisionFlags(m_RigidBody->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT);
    m_RigidBody->setActivationState(DISABLE_DEACTIVATION);

    PhysicsWorld->AddRigidBody(m_RigidBody);
}


EntVehicleT::~EntVehicleT()
{
    if (m_RigidBody->isInWorld())
        PhysicsWorld->RemoveRigidBody(m_RigidBody);

    delete m_RigidBody;
    delete m_CollisionShape;
}


void EntVehicleT::DoDeserialize(cf::Network::InStreamT& Stream)
{
    const bool IsAlive=(State.ModelSequNr<18 || State.ModelSequNr>24);

    btTransform WorldTrafo;
    getWorldTransform(WorldTrafo);
    m_RigidBody->setWorldTransform(WorldTrafo);

    if (IsAlive)
    {
        ClipModel.SetOrigin(m_Origin);
        ClipModel.Register();

        if (!m_RigidBody->isInWorld())
            PhysicsWorld->AddRigidBody(m_RigidBody);
    }
    else
    {
        ClipModel.Unregister();

        if (m_RigidBody->isInWorld())
            PhysicsWorld->RemoveRigidBody(m_RigidBody);
    }
}

//Set pilot and state when a player grabs the vehicle
void EntVehicleT::AcceptPilot(EntHumanPlayerT* pilot)
{
    std::cout << "Vehicle.AcceptPilot called. \n"; 
    VehicleState = HumanOperated;
    Pilot = pilot;
    std::cout << "Pilot assigned! Pilot: " << pilot->Name << "\n" ; 
}

//Remove pilot and switch state back
void EntVehicleT::RemovePilot()
{
    std::cout << "Vehicle.RemovePilot called. \n"; 
    VehicleState = Idle;
    Pilot = 0; //Pilot = null;
    std::cout << "Pilot removed.\n";
}

//Allow player to work out if vehicle is close enough to board
Vector3fT EntVehicleT::GetVector()
{  return m_Origin.AsVectorOfFloat();  }

//Pass input from Player class to player's piloted vehicle
//  May need to verify that vehicle pilot matches caller at some point; just prototyping for now
void EntVehicleT::AcceptCommand(PlayerCommandT PlayerCommand)
{
    //Need to cache these; makes much smoother
    PlayerCommands.PushBack(PlayerCommand); //From HumanPlayer.ProcessConfig: PlayerCommands.PushBack(*((PlayerCommandT*)ConfigData));
}

void EntVehicleT::TakeDamage(BaseEntityT* Entity, char Amount, const VectorT& ImpactDir)
{
    // Dead company bots can take no further damage.
    if (State.ModelSequNr>=18 && State.ModelSequNr<=24) return;

    State.Velocity=State.Velocity+scale(VectorT(ImpactDir.x, ImpactDir.y, 0.0), 500.0*Amount);

    if (State.Health<=Amount)
    {
        unsigned short DeltaAngle=Entity->GetHeading()-m_Heading;

        VehicleState = Inoperative;         //State.Health=0; //Need to remove State.* refs...

             if (DeltaAngle>=57344 || DeltaAngle< 8192) State.ModelSequNr=21;   // 315° ...  45° - die forwards
        else if (DeltaAngle>=8192  && DeltaAngle<16384) State.ModelSequNr=22;   //  45° ...  90° - headshot
        else if (DeltaAngle>=16384 && DeltaAngle<24576) State.ModelSequNr=24;   //  90° ... 135° - gutshot
        else if (DeltaAngle>=24576 && DeltaAngle<32768) State.ModelSequNr=19;   // 135° ... 180° - die backwards1
        else if (DeltaAngle>=32768 && DeltaAngle<40960) State.ModelSequNr=20;   // 180° ... 225° - die backwards
        else if (DeltaAngle>=40960 && DeltaAngle<49152) State.ModelSequNr=18;   // 225° ... 270° - die simple
        else /* (DeltaAngle>=49152&&DeltaAngle<57344)*/ State.ModelSequNr=23;   // 270° ... 315° - die spin

        State.ModelFrameNr=0.0;
        ClipModel.Unregister();     // Dead now, don't clip no more.
        PhysicsWorld->RemoveRigidBody(m_RigidBody);

        // Count the frag at the "creator" entity.
        BaseEntityT* FraggingEntity=Entity;

        while (FraggingEntity->ParentID!=0xFFFFFFFF)
        {
            BaseEntityT* ParentOfFE=GameWorld->GetBaseEntityByID(FraggingEntity->ParentID);

            if (ParentOfFE==NULL) break;
            FraggingEntity=ParentOfFE;
        }

        FraggingEntity->AddFrag();
    }
    else
    {
        State.Health-=Amount;
    }
}


void EntVehicleT::Think(float FrameTime, unsigned long /*ServerFrameNr*/)
{
    switch (VehicleState)
    {
        case HumanOperated: 
        {
            for (unsigned long PCNr=0; PCNr<PlayerCommands.Size(); PCNr++)
            {
                PlayerCommandT PlayerCommand = PlayerCommands[PCNr]; //Convenience
/*                std::cout << "Recieved PlayerCommand:\n";
                std::cout << "  PlayerCommand Keys: " << PlayerCommand.Keys << "\n";
                std::cout << "  PlayerCommand DeltaHeading: " << PlayerCommand.DeltaHeading << "\n";
                std::cout << "  PlayerCommand DeltaPitch: " << PlayerCommand.DeltaPitch << "\n";
                std::cout << "  PlayerCommand DeltaBank: " << PlayerCommand.DeltaBank << "\n";
  */              
                m_Heading+=PlayerCommand.DeltaHeading;

                VectorT             WishVelocity;
                bool                WishJump=false;
                const double        VelX    =6000.0*LookupTables::Angle16ToSin[m_Heading];     // 6000 == Client.MoveSpeed
                const double        VelY    =6000.0*LookupTables::Angle16ToCos[m_Heading];     // 6000 == Client.MoveSpeed
                const unsigned long Keys    =PlayerCommands[PCNr].Keys;

                if (Keys & PCK_MoveForward ) WishVelocity=             VectorT( VelX,  VelY, 0);
                m_Physics.MoveHuman(PlayerCommands[PCNr].FrameTime, m_Heading, WishVelocity, WishVelocity, WishJump, WishJump, 470.0); //Complete magic for now; but it works - figure out details when becomes an issue
                ClipModel.SetOrigin(m_Origin);
                ClipModel.Register();
                
                //Update camera (ie, HumanPlayerEntity) position. Just dupe vehicle pstn for now; figure out offset later (can I read where a bone is?)
                if (Pilot) { Pilot->setPositionAndHeading(m_Origin, m_Heading); }
            }
        }
        default: //Will be coding other states later
        {   }
    }
    
    PlayerCommands.Clear();
}


static ConVarT HasLight("game_BotsHaveLight", true, ConVarT::FLAG_GAMEDLL, "Determines whether the company bot entities all carry a dynamic lightsource.");

bool EntVehicleT::GetLightSourceInfo(unsigned long& DiffuseColor, unsigned long& SpecularColor, VectorT& Position, float& Radius, bool& CastsShadows) const
{
    if (!HasLight.GetValueBool()) return false;

    if (m_TimeForLightSource<2.0f)
    {
        // 0.0 <= m_TimeForLightSource < 2.0
        const float         Value=1.0f-0.5f*(1.0f+LookupTables::Angle16ToCos[(unsigned short)(m_TimeForLightSource/4.0f*65536.0f)]);
        const unsigned long Red  =char(255.0f*Value);
        const unsigned long Green=char(255.0f*Value*0.9f);

        DiffuseColor =(Green << 8)+Red;
        SpecularColor=0x440000+(Green << 8);
    }
    else
    {
        // 2.0 <= m_TimeForLightSource < 6.0
        const float         Value=0.5f*(1.0f+LookupTables::Angle16ToCos[(unsigned short)((m_TimeForLightSource-2.0f)/4.0f*65536.0f)]);
        const unsigned long Green=char(255.0f*(Value*0.8f+0.1f));

        DiffuseColor =(Green << 8)+0xFF;
        SpecularColor=0x440000+(Green << 8);
    }

    const float   Value=LookupTables::Angle16ToCos[(unsigned short)((m_TimeForLightSource-2.0f)/4.0f*65536.0f)];
    const VectorT RelX =scale(VectorT(LookupTables::Angle16ToCos[m_Heading], LookupTables::Angle16ToSin[m_Heading], 0.0), 80.0*Value);
    const VectorT RelY =scale(VectorT(LookupTables::Angle16ToSin[m_Heading], LookupTables::Angle16ToCos[m_Heading], 0.0), 500.0);
    const VectorT RelZ =VectorT(0.0, 0.0, -300.0+0.5*char(DiffuseColor >> 8));

    Position    =m_Origin+RelX+RelY+RelZ;
    Radius      =10000.0;
    CastsShadows=true;

    return true;
}


void EntVehicleT::Draw(bool /*FirstPersonView*/, float LodDist) const
{
    //Check whether pilot is local client?
    MatSys::Renderer->GetCurrentLightSourcePosition()[2]+=32.0f;
    MatSys::Renderer->GetCurrentEyePosition        ()[2]+=32.0f;
    MatSys::Renderer->Translate(MatSys::RendererI::MODEL_TO_WORLD, 0.0f, 0.0f, -32.0f);

    AnimPoseT* Pose=m_VehicleModel->GetSharedPose(m_AnimExpr);
    Pose->Draw(-1 /*default skin*/, LodDist);

    AnimPoseT* WeaponPose=m_WeaponModel->GetSharedPose(m_WeaponModel->GetAnimExprPool().GetStandard(0, 0.0f));
    WeaponPose->SetSuperPose(Pose);
    WeaponPose->Draw(-1 /*default skin*/, LodDist);
    WeaponPose->SetSuperPose(NULL);
}

void EntVehicleT::PostDraw(float FrameTime, bool /*FirstPersonView*/)
{
    // Implicit simple "mini-prediction".
    AdvanceModelTime(FrameTime, State.ModelSequNr<18 || State.ModelSequNr>24);

    // Advance the time for the light source.
    m_TimeForLightSource+=FrameTime;
    if (m_TimeForLightSource>6.0f) m_TimeForLightSource-=4.0f;
}

void EntVehicleT::getWorldTransform(btTransform& worldTrans) const
{
    Vector3dT Origin=m_Origin;

    // The box shape of our physics body is equally centered around the origin point,
    // whereas our m_Dimensions box is "non-uniformely displaced".
    // In order to compensate, compute how far the m_Dimensions center is away from the origin.
    Origin.z+=(m_Dimensions.Min.z+m_Dimensions.Max.z)/2.0;

    // Return the current transformation of our rigid body to the physics world.
    worldTrans.setIdentity();
    worldTrans.setOrigin(conv(Origin/1000.0));
}


void EntVehicleT::setWorldTransform(const btTransform& /*worldTrans*/)
{
    // Never called for a kinematic rigid body.
    assert(false);
}


#undef min    // See http://stackoverflow.com/questions/5004858/stdmin-gives-error
#undef max

void EntVehicleT::AdvanceModelTime(float Time, bool Loop)
{
    if (State.ModelSequNr==m_LastStdAE->GetSequNr())
    {
        m_LastStdAE->SetFrameNr(State.ModelFrameNr);
    }
    else
    {
        const bool IsAlive=(State.ModelSequNr<18 || State.ModelSequNr>24);
        float      BlendTime=0.3f;

        if (!IsAlive)
        {
            BlendTime=0.2f;

            if (State.ModelSequNr>=0 && State.ModelSequNr<int(m_VehicleModel->GetAnims().Size()))
            {
                const CafuModelT::AnimT& Anim=m_VehicleModel->GetAnims()[State.ModelSequNr];

                if (Anim.Frames.Size() > 0)
                    BlendTime=std::min(BlendTime, (Anim.Frames.Size()-1) * Anim.FPS * 0.5f);
            }
        }

        m_LastStdAE=m_VehicleModel->GetAnimExprPool().GetStandard(State.ModelSequNr, 0.0f);
        m_AnimExpr =m_VehicleModel->GetAnimExprPool().GetBlend(m_AnimExpr, m_LastStdAE, BlendTime);
    }

    m_LastStdAE->SetForceLoop(Loop);
    m_AnimExpr->AdvanceTime(Time);
    State.ModelFrameNr=m_LastStdAE->GetFrameNr();
}
(I stripped the big front docstrings out for the post, the files are big enough!)

HumanPlayer had some pieces spliced in as well; here's the HumanPlayer.hpp file:

Code: Select all

#ifndef CAFU_HUMAN_PLAYER_HPP_INCLUDED
#define CAFU_HUMAN_PLAYER_HPP_INCLUDED

#include "../../BaseEntity.hpp"
#include "../../PlayerCommand.hpp"
#include "Libs/Physics.hpp"
#include "btBulletDynamicsCommon.h"

//Need to be able to reference the piloted vehicle
#include "Vehicle.hpp"

class EntityCreateParamsT;
class EntVehicleT; //Forward declaration to avoid circular dependency. Done in Vehicle.hpp as well; probably not needed here...
class EntStaticDetailModelT;
namespace cf { namespace GuiSys { class GuiI; } }

class EntHumanPlayerT : public BaseEntityT, public btMotionState
{
    public:

    // Publicly defined enum for access from the "carried weapons".
    enum EventTypesT { EVENT_TYPE_PRIMARY_FIRE, EVENT_TYPE_SECONDARY_FIRE, NUM_EVENT_TYPES };

    EntHumanPlayerT(const EntityCreateParamsT& Params);
    ~EntHumanPlayerT();

    EntityStateT& GetState() { return State; }
    const EntityStateT& GetState() const { return State; }
    unsigned short GetPitch() const { return m_Pitch; }
    unsigned short GetBank() const { return m_Bank; }
    const btRigidBody* GetRigidBody() const { return m_RigidBody; }

    // Implement the BaseEntityT interface.
    void TakeDamage(BaseEntityT* Entity, char Amount, const VectorT& ImpactDir);
    void ProcessConfigString(const void* ConfigData, const char* ConfigString);
    void Think(float FrameTime, unsigned long ServerFrameNr);

    void ProcessEvent(unsigned int EventType, unsigned int NumEvents);
    bool GetLightSourceInfo(unsigned long& DiffuseColor, unsigned long& SpecularColor, VectorT& Position, float& Radius, bool& CastsShadows) const;
    void Draw(bool FirstPersonView, float LodDist) const;
    void PostDraw(float FrameTime, bool FirstPersonView);

    // Implement the btMotionState interface.
    void getWorldTransform(btTransform& worldTrans) const;
    void setWorldTransform(const btTransform& worldTrans);


    const cf::TypeSys::TypeInfoT* GetType() const;
    static void* CreateInstance(const cf::TypeSys::CreateParamsT& Params);
    static const cf::TypeSys::TypeInfoT TypeInfo;
    
    /// Allow Vehicle to update player's position and rotation. (Also useable by script)
    void setPositionAndHeading(Vector3dT& Position, short Heading);

    private:

    /// A helper function for Think().
    bool CheckGUI(EntStaticDetailModelT* GuiEnt, Vector3fT& MousePos) const;
    /// For now, just vehicle-specific
    bool CheckInteract(EntVehicleT* TargetEnt, Vector3fT& MousePos) const;
    ///Pointer to the currently piloted vehicle (if any)
    EntVehicleT* PilotedVehicle;

    ArrayT<PlayerCommandT> PlayerCommands;

    PhysicsHelperT    m_Physics;
    btCollisionShape* m_CollisionShape;         ///< The collision shape that is used to approximate and represent this player in the physics world.
    btRigidBody*      m_RigidBody;              ///< The rigid body (of "kinematic" type) for use in the physics world.

    mutable VectorT   LastSeenAmbientColor;     // This is a client-side variable, unrelated to prediction, and thus allowed.
    float             TimeForLightSource;
    cf::GuiSys::GuiI* GuiHUD;                   ///< The HUD GUI for this local human player entity.
};

#endif
...and excerpts from the HumanPlayer.cpp file (it's large enough I'll just show my changes):

Code: Select all

...
#include "Vehicle.hpp" 
#include "ScriptState.hpp" 
#include <iostream> //For cout - handy in this early phase
...
const char StateOfExistance_FreeSpectator  =3; 
const char StateOfExistance_Piloting       =4; //Piloting is a distinct state
...
//Allow vehicle, script, or anything else to update this entity's position and direction 
//  Will need to set all rotations, not just heading - later update
void EntHumanPlayerT::setPositionAndHeading(Vector3dT& Position, short Heading)
{
    m_Origin = Position;
    m_Heading = Heading;
}
...
//Based on CheckGUI, idea is to allow interaction with any object type
const bool EntHumanPlayerT::CheckInteract(EntVehicleT* TargetEnt, Vector3fT& MousePos) const
{
    std::cout << "Checking interactability of: " << TargetEnt->Name << "\n";
    
    //From Think
    if (TargetEnt->GetType()==&EntStaticDetailModelT::TypeInfo)
    {
        //Let Think keep handling GUIs
        return false;
    }
    
 
 //Just see if we're close, for now. 
    Vector3fT TargetOrigin;
    TargetOrigin = TargetEnt->GetVector();
    std::cout << "Target at x: " << TargetOrigin.x << ", y: " << TargetOrigin.y << ", z: " << TargetOrigin.z << "\n";
    std::cout << "Player at x: " << m_Origin.x << ", y: " << m_Origin.y << ", z: " << m_Origin.z << "\n";
 
    //Range of relative origins:
    float rangeX = TargetOrigin.x - m_Origin.x;
    float rangeY = TargetOrigin.y - m_Origin.y;
    float rangeZ = TargetOrigin.z - m_Origin.z;
    std::cout << "Axial distances: x: " << rangeX << ", y: " << rangeY << ", z: " << rangeZ << "\n";
 
    float flatDist = sqrt(rangeX*rangeX+rangeY*rangeY);
    std::cout << "Level distance: " << flatDist << "\n";
    float distance = sqrt(flatDist*flatDist+rangeZ*rangeZ);
    std::cout << "Total distance: " << distance << "\n";
    if (distance > 2500) return false;

    return true;
}
...
(in Think, after if (CheckGUI(GuiEnt, MousePos)) {...} call) :
                    //Check to see if a vehicle (or something else) can be interacted with:
                    if (OtherEntity->GetType()==&EntVehicleT::TypeInfo) 
                    {
                        if (Keys & PCK_Use )
                        {
                            std::cout << "Use key detected by EntVehicleT object!\n";
                            
                            EntVehicleT* VehicleEnt=(EntVehicleT*)OtherEntity; //Cast?
                            //Trying to read whether vehicle is close enough to board... 
                            Vector3fT MousePos; //Not used for now; left for future compatibility
                            if (CheckInteract(VehicleEnt, MousePos))
                            {
                                VehicleEnt->AcceptPilot(this);
                                PilotedVehicle = VehicleEnt;
                                State.StateOfExistance = StateOfExistance_Piloting;
                                std::cout << "Pilot mode activated!\n";
                            }
                        }
                    }
...
(later, near end of Think(), as new switch(State.StateOfExistance) case):
            case StateOfExistance_Piloting: 
            {
                //Pass all inputs on to vehicle.
                
                //First, remove any existing physics body
                if (m_RigidBody->isInWorld())
                {
                    std::cout << "Removing player from world... \n";
                    PhysicsWorld->RemoveRigidBody(m_RigidBody);
                    ClipModel.Unregister(); 
                }
                
                PilotedVehicle->AcceptCommand(PlayerCommands[PCNr]);

                if (PlayerCommands[PCNr].Keys & PCK_Jump        )  //Yes... for now, you jump out of the vehicle :P
                {
                    std::cout << "Eject (jump) button detected while in Piloting mode... \n";
                    PilotedVehicle->RemovePilot();
                    PilotedVehicle = 0; //PilotedVehicle = null; //This needs to happen *after* anything involving the vehicle! 
                    State.StateOfExistance = StateOfExistance_Alive;
                    std::cout << "Alive state restored\n";
                    
                    //Pop back out on top of vehicle - will need to figure out how to find an open space
                    m_Origin.z=m_Origin.z+2000; 
                }
                
            }
...
(remainder of file)
And finally, to be able to drop a vehicle into a level, a new entry for EntityClassDefs.lua:

Code: Select all

-- A base class for vehicle entities (based on Weapon above)
Vehicle=newEntClassDef(Common, Angles,
{
    isPoint=true;
    size   ={ { -16, -16, -16 }, { 16, 16, 16 } };
    color  ={ 150, 200, 255 };
    --accept/eject method defs
})
EntityClassDefs["vehicle"]=newEntClassDef(Vehicle, { isPoint = true; CppClass="EntVehicleT"; description="Base class for driveable vehicles"; })
If anybody has a handy file upload site, I'd also be happy to link these from there - might be easier to read! Also let me know if any parts are unclear - I don't have a lot of time right this second to review the whole posted entry...

I did figure out the issue with the non-moving clip body; but as I mention in the code, I ended up cheating on the vehicle entry conditions. I've got a couple of things I want to try later on for how players interact with objects; so I just put in some very basic range-checking for now - if the player is within 2500 units of the "vehicle" (still just a floating Sentinel model), pressing Enter will now let them drive it.
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Adding vehicles

Post by Carsten » 2012-08-28, 00:25

Wow, that's looking good!!! :groupwave1: Thanks for posting this code! :wohow:

Uhm, would you mind (at any time that is convenient for you) to post these changes as a patch? Reading your code and live-testing it here would be much easier if the changes were in patch format. Creating a patch is very easy with (Tortoise)SVN and Git, but I think that Beyond Compare can do it as well. :up:
Best regards,
Carsten
Stoo
Posts:42
Joined:2012-05-11, 00:28

Re: Adding vehicles

Post by Stoo » 2012-09-11, 03:22

Okay... after catching up to 2 version updates, tracking down another bug, and various cleanup items... patch submitted! http://trac.cafu.de/ticket/121

Let me know if I need to adjust anything on it, clean it up more, etc... there's a lot of debug data still in right now; so the output will be chatty if you run through a console!
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Adding vehicles

Post by Carsten » 2012-09-11, 23:14

Thank you very much! :thanks:

Things need some polishing, but this is a very good start!
My "full" reply is at http://trac.cafu.de/ticket/121#comment:1 ;-)
Best regards,
Carsten
Post Reply

Who is online

Users browsing this forum: No registered users and 5 guests