/*
===========================================================================
Copyright (C) 2018 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code 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 2 of the License,
or (at your option) any later version.

OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// vehicle.cpp: Script controlled Vehicles.
//

#include "g_local.h"
#include "g_phys.h"
#include "scriptslave.h"
#include "vehicle.h"
#include "player.h"
#include "specialfx.h"
#include "explosion.h"
#include "earthquake.h"
#include "gibs.h"
#include "vehicleturret.h"
#include "scriptexception.h"
#include "debuglines.h"

Event EV_Vehicle_Start
(
    "start",
    EV_DEFAULT,
    NULL,
    NULL,
    "Initialize the vehicle.",
    EV_NORMAL
);
Event EV_Vehicle_Enter
(
    "enter",
    EV_DEFAULT,
    "eS",
    "vehicle driver_anim",
    "Called when someone gets into a vehicle.",
    EV_NORMAL
);
Event EV_Vehicle_Exit
(
    "exit",
    EV_DEFAULT,
    "e",
    "vehicle",
    "Called when driver gets out of the vehicle.",
    EV_NORMAL
);
Event EV_Vehicle_Drivable
(
    "drivable",
    EV_DEFAULT,
    NULL,
    NULL,
    "Make the vehicle drivable",
    EV_NORMAL
);
Event EV_Vehicle_UnDrivable
(
    "undrivable",
    EV_DEFAULT,
    NULL,
    NULL,
    "Make the vehicle undrivable",
    EV_NORMAL
);
Event EV_Vehicle_PathDrivable
(
    "pathDrivable",
    EV_DEFAULT,
    "b",
    "pathDrivable",
    "Make the vehicle drivable along a path",
    EV_NORMAL
);
Event EV_Vehicle_Jumpable
(
    "canjump",
    EV_DEFAULT,
    "b",
    "jumpable",
    "Sets whether or not the vehicle can jump",
    EV_NORMAL
);
Event EV_Vehicle_Lock
(
    "lock",
    EV_DEFAULT,
    NULL,
    NULL,
    "Sets the vehicle to be locked",
    EV_NORMAL
);
Event EV_Vehicle_UnLock
(
    "unlock",
    EV_DEFAULT,
    NULL,
    NULL,
    "Sets the vehicle to be unlocked",
    EV_NORMAL
);
Event EV_Vehicle_SeatAnglesOffset
(
    "seatanglesoffset",
    EV_DEFAULT,
    "v",
    "angles",
    "Set the angles offset of the seat",
    EV_NORMAL
);
Event EV_Vehicle_SeatOffset
(
    "seatoffset",
    EV_DEFAULT,
    "v",
    "offset",
    "Set the offset of the seat",
    EV_NORMAL
);
Event EV_Vehicle_SetWeapon
(
    "setweapon",
    EV_DEFAULT,
    "s",
    "weaponname",
    "Set the weapon for the vehicle",
    EV_NORMAL
);
Event EV_Vehicle_SetName
(
    "name",
    EV_DEFAULT,
    "s",
    "vehicleName",
    "Set the name for the vehicle",
    EV_NORMAL
);
Event EV_Vehicle_ShowWeapon
(
    "showweapon",
    EV_DEFAULT,
    NULL,
    NULL,
    "Set the weapon to be show in the view",
    EV_NORMAL
);
Event EV_Vehicle_SetSpeed
(
    "vehiclespeed",
    EV_DEFAULT,
    "f",
    "speed",
    "Set the speed of the vehicle",
    EV_NORMAL
);
Event EV_Vehicle_SetTurnRate
(
    "turnrate",
    EV_DEFAULT,
    "f",
    "rate",
    "Set the turning rate of the vehicle",
    EV_NORMAL
);
Event EV_Vehicle_SteerInPlace
(
    "steerinplace",
    EV_DEFAULT,
    NULL,
    NULL,
    "Set the vehicle to turn in place",
    EV_NORMAL
);
Event EV_Vehicle_Destroyed
(
    "vehicledestroyed",
    EV_DEFAULT,
    NULL,
    NULL,
    "Driver is dead",
    EV_NORMAL
);
Event EV_Vehicle_Mass
(
    "vehiclemass",
    EV_DEFAULT,
    "f",
    "weight",
    "Sets the mass of the vehicle (backmass = frontmass = mass/2)",
    EV_NORMAL
);
Event EV_Vehicle_Front_Mass
(
    "front_mass",
    EV_DEFAULT,
    "f",
    "weight",
    "Sets the mass of the front of the vehicle",
    EV_NORMAL
);
Event EV_Vehicle_Back_Mass
(
    "back_mass",
    EV_DEFAULT,
    "f",
    "weight",
    "Sets the mass of the back of the vehicle",
    EV_NORMAL
);
Event EV_Vehicle_Tread
(
    "vehicletread",
    EV_DEFAULT,
    "f",
    "size",
    "Sets the size of the wheels",
    EV_NORMAL
);
Event EV_Vehicle_Radius
(
    "vehicleradius",
    EV_DEFAULT,
    "f",
    "size",
    "Sets the radius of the wheels",
    EV_NORMAL
);
Event EV_Vehicle_RollingResistance
(
    "vehiclerollingresistance",
    EV_DEFAULT,
    "f",
    "size",
    "Sets the radius of the wheels",
    EV_NORMAL
);
Event EV_Vehicle_Drag
(
    "vehicledrag",
    EV_DEFAULT,
    "f",
    "size",
    "Sets the Drag Factor",
    EV_NORMAL
);
Event EV_Vehicle_Drive
(
    "drive",
    EV_DEFAULT,
    "vffffV",
    "position speed acceleration reach_distance look_ahead alternate_position",
    "Makes the vehicle drive to position with speed and acceleration until reached_distance close to position",
    EV_NORMAL
);
Event EV_Vehicle_DriveNoWait
(
    "driveNoWait",
    EV_DEFAULT,
    "vfff",
    "position speed acceleration reach_distance",
    "Makes the vehicle drive to position with speed and acceleration until reached_distance close to position, thread "
    "doesn't wait",
    EV_NORMAL
);
Event EV_Vehicle_Stop
(
    "stop",
    EV_DEFAULT,
    NULL,
    NULL,
    "Make the Vehicle Stop Moving... FULL BREAKS!",
    EV_NORMAL
);
Event EV_Vehicle_FullStop
(
    "fullstop",
    EV_DEFAULT,
    NULL,
    NULL,
    "Make the Vehicle Stop Moving... Completely!",
    EV_NORMAL
);
Event EV_Vehicle_Init
(
    "vehicleinit",
    EV_DEFAULT,
    NULL,
    NULL,
    "Initialized the Vehicle as the specified file",
    EV_NORMAL
);
Event EV_Vehicle_BouncyCoef
(
    "vehiclebouncy",
    EV_DEFAULT,
    "f",
    "bouncycoef",
    "Sets the Bouncy Coefficient for the shocks.",
    EV_NORMAL
);
Event EV_Vehicle_SpringyCoef
(
    "vehiclespringy",
    EV_DEFAULT,
    "f",
    "springycoef",
    "Sets the Springy Coefficient for the shocks.",
    EV_NORMAL
);
Event EV_Vehicle_Yaw
(
    "vehicleYaw",
    EV_DEFAULT,
    "fff",
    "min max coef",
    "Sets the Yaw min and max and the acceleration coefficient for the shocks.",
    EV_NORMAL
);
Event EV_Vehicle_Roll
(
    "vehicleRoll",
    EV_DEFAULT,
    "fff",
    "min max coef",
    "Sets the Roll min and max and the acceleration coefficient for the shocks.",
    EV_NORMAL
);
Event EV_Vehicle_Z
(
    "vehicleZ",
    EV_DEFAULT,
    "fff",
    "min max coef",
    "Sets the Z min and max and the acceleration coefficient for the shocks.",
    EV_NORMAL
);
Event EV_Vehicle_QuerySpeed
(
    "QuerySpeed",
    EV_DEFAULT,
    "f",
    "speed",
    "Returns the current speed of the vehicle.",
    EV_RETURN
);
Event EV_Vehicle_QueryFreePassengerSlot
(
    "QueryFreePassengerSlot",
    EV_DEFAULT,
    NULL,
    NULL,
    "Returns a number that represents the first free passenger slot on the vehicle.",
    EV_RETURN
);
Event EV_Vehicle_QueryPassengerSlotPosition
(
    "QueryPassengerSlotPosition",
    EV_DEFAULT,
    "i",
    "slot",
    "Returns the position of the specified slot on the vehicle.",
    EV_RETURN
);
Event EV_Vehicle_QueryPassengerSlotStatus
(
    "QueryPassengerSlotStatus",
    EV_DEFAULT,
    "i",
    "slot",
    "Returns the status of the specified slot on the vehicle.",
    EV_RETURN
);
Event EV_Vehicle_BounceForwardsVelocity
(
    "BounceForwardsVelocity",
    EV_DEFAULT,
    NULL,
    NULL,
    "For vehicles on rails that are moving forwards, reverse our velocity.",
    EV_NORMAL
);
Event EV_Vehicle_BounceBackwardsVelocity
(
    "BounceBackwardsVelocity",
    EV_DEFAULT,
    "i",
    "bStayFullSpeed",
    "For vehicles on rails that are moving backwards, reverse our velocity. (Optionally pass bStayFullSpeed to keep "
    "vehic"
    "le at full speed after the bounce)",
    EV_NORMAL
);
Event EV_Vehicle_StopForwardsVelocity
(
    "StopForwardsVelocity",
    EV_DEFAULT,
    NULL,
    NULL,
    "Stops any forward motion for vehicles on rails.",
    EV_NORMAL
);
Event EV_Vehicle_StopBackwardsVelocity
(
    "StopBackwardsVelocity",
    EV_DEFAULT,
    NULL,
    NULL,
    "Stops any backwards motion for vehicles on rails.",
    EV_NORMAL
);
Event EV_Vehicle_AttachPassengerSlot
(
    "AttachPassengerSlot",
    EV_DEFAULT,
    "ie",
    "slot entity",
    "Attaches an entity to the specified slot.",
    EV_NORMAL
);
Event EV_Vehicle_QueryPassengerSlotEntity
(
    "QueryPassengerSlotEntity",
    EV_DEFAULT,
    "i",
    "slot",
    "Returns an entity at the specified slot.",
    EV_RETURN
);
Event EV_Vehicle_DetachPassengerSlot
(
    "DetachPassengerSlot",
    EV_DEFAULT,
    "iV",
    "slot exit_position",
    "Detaches an entity to the specified slot.",
    EV_NORMAL
);
Event EV_Vehicle_QueryFreeDriverSlot
(
    "QueryFreeDriverSlot",
    EV_DEFAULT,
    NULL,
    NULL,
    "Returns a number that represents the first free driver slot on the vehicle.",
    EV_RETURN
);
Event EV_Vehicle_QueryDriverSlotPosition
(
    "QueryDriverSlotPosition",
    EV_DEFAULT,
    "i",
    "slot",
    "Returns the position of the specified slot on the vehicle.",
    EV_RETURN
);
Event EV_Vehicle_QueryDriverSlotStatus
(
    "QueryDriverSlotStatus",
    EV_DEFAULT,
    "i",
    "slot",
    "Returns the status of the specified slot on the vehicle.",
    EV_RETURN
);
Event EV_Vehicle_AttachDriverSlot
(
    "AttachDriverSlot",
    EV_DEFAULT,
    "ie",
    "slot entity",
    "Attaches an entity to the specified slot.",
    EV_NORMAL
);
Event EV_Vehicle_QueryDriverSlotEntity
(
    "QueryDriverSlotEntity",
    EV_DEFAULT,
    "i",
    "slot",
    "Returns an entity at the specified slot.",
    EV_RETURN
);
Event EV_Vehicle_DetachDriverSlot
(
    "DetachDriverSlot",
    EV_DEFAULT,
    "iV",
    "slot exit_position",
    "Detaches an entity to the specified slot.",
    EV_NORMAL
);
Event EV_Vehicle_QueryFreeTurretSlot
(
    "QueryFreeTurretSlot",
    EV_DEFAULT,
    NULL,
    NULL,
    "Returns a number that represents the first free turret slot on the vehicle.",
    EV_RETURN
);
Event EV_Vehicle_QueryTurretSlotPosition
(
    "QueryTurretSlotPosition",
    EV_DEFAULT,
    "i",
    "slot",
    "Returns the position of the specified slot on the vehicle.",
    EV_RETURN
);
Event EV_Vehicle_QueryTurretSlotStatus
(
    "QueryTurretSlotStatus",
    EV_DEFAULT,
    "i",
    "slot",
    "Returns the status of the specified slot on the vehicle.",
    EV_RETURN
);
Event EV_Vehicle_AttachTurretSlot
(
    "AttachTurretSlot",
    EV_DEFAULT,
    "ie",
    "slot entity",
    "Attaches an entity to the specified slot.",
    EV_NORMAL
);
Event EV_Vehicle_QueryTurretSlotEntity
(
    "QueryTurretSlotEntity",
    EV_DEFAULT,
    "i",
    "slot",
    "Returns an entity at the specified slot.",
    EV_RETURN
);
Event EV_Vehicle_DetachTurretSlot
(
    "DetachTurretSlot",
    EV_DEFAULT,
    "iV",
    "slot exit_position",
    "Detaches an entity to the specified slot.",
    EV_NORMAL
);
Event EV_Vehicle_WheelCorners
(
    "VehicleWheelCorners",
    EV_DEFAULT,
    "vv",
    "size offset",
    "Sets the wheel trace corners.",
    EV_NORMAL
);
Event EV_Vehicle_QueryDriverSlotAngles
(
    "QueryDriverSlotAngles",
    EV_DEFAULT,
    "i",
    "slot",
    "Returns the angles of the specified slot on the vehicle.",
    EV_RETURN
);
Event EV_Vehicle_QueryPassengerSlotAngles
(
    "QueryDriverSlotAngles",
    EV_DEFAULT,
    "i",
    "slot",
    "Returns the angles of the specified slot on the vehicle.",
    EV_RETURN
);
Event EV_Vehicle_QueryTurretSlotAngles
(
    "QueryDriverSlotAngles",
    EV_DEFAULT,
    "i",
    "slot",
    "Returns the angles of the specified slot on the vehicle.",
    EV_RETURN
);
Event EV_Vehicle_AnimationSet
(
    "AnimationSet",
    EV_DEFAULT,
    "s",
    "animset",
    "Sets the Animation Set to use.",
    EV_NORMAL
);
Event EV_Vehicle_SoundSet
(
    "SoundSet",
    EV_DEFAULT,
    "s",
    "soundset",
    "Sets the Sound Set to use.",
    EV_NORMAL
);
Event EV_Vehicle_SpawnTurret
(
    "spawnturret",
    EV_DEFAULT,
    "is",
    "slot tikifile",
    "Spawns a turret with the specified model and connects it to the specified slot",
    EV_NORMAL
);
Event EV_Vehicle_ModifyDrive
(
    "modifydrive",
    EV_DEFAULT,
    "fff",
    "desired_speed acceleration look_ahead",
    "Modifys the parameters of the current drive.",
    EV_NORMAL
);
Event EV_Vehicle_NextDrive
(
    "nextdrive",
    EV_DEFAULT,
    "e",
    "next_path",
    "appends the specified path to the current path",
    EV_NORMAL
);
Event EV_Vehicle_StopAtEnd
(
    "stopatend",
    EV_DEFAULT,
    NULL,
    NULL,
    "Makes the vehicle slow down to a complete stop at the end of the path.",
    EV_NORMAL
);
Event EV_Vehicle_LockMovement
(
    "lockmovement",
    EV_DEFAULT,
    NULL,
    NULL,
    "The Vehicle cannot move.",
    EV_NORMAL
);
Event EV_Vehicle_UnlockMovement
(
    "unlockmovement",
    EV_DEFAULT,
    NULL,
    NULL,
    "The Vehicle can move again.",
    EV_NORMAL
);
Event EV_Vehicle_RemoveOnDeath
(
    "removeondeath",
    EV_DEFAULT,
    "i",
    "removeondeath",
    "If set to a non-zero value, vehicles will not be removed when they die",
    EV_NORMAL
);
Event EV_Vehicle_SetExplosionModel
(
    "explosionmodel",
    EV_DEFAULT,
    "s",
    "model",
    "Sets the TIKI to call when the vehicle dies.",
    EV_NORMAL
);
Event EV_Vehicle_SetCollisionEntity
(
    "setcollisionentity",
    EV_DEFAULT,
    "e",
    "entity",
    "Sets the Collision Entity.",
    EV_NORMAL
);
Event EV_Vehicle_CollisionEntitySetter
(
    "collisionent",
    EV_DEFAULT,
    "e",
    "entity",
    "Gets the Collision Entity",
    EV_SETTER
);
Event EV_Vehicle_CollisionEntityGetter
(
    "collisionent",
    EV_DEFAULT,
    NULL,
    NULL,
    "Gets the Collision Entity",
    EV_GETTER
);
Event EV_Vehicle_SetSoundParameters
(
    "setsoundparameters",
    EV_DEFAULT,
    "ffff",
    "min_speed min_pitch max_speed max_pitch",
    "Sets the Sound parameters for this vehicle",
    EV_NORMAL
);
Event EV_Vehicle_SetVolumeParameters
(
    "setvolumeparameters",
    EV_DEFAULT,
    "ffff",
    "min_speed min_volume max_speed max_volume",
    "Sets the Volume parameters for this vehicle",
    EV_NORMAL
);
Event EV_Vehicle_Skidding
(
    "skidding",
    EV_DEFAULT,
    "i",
    "on_off",
    "Makes the vehicle skid around corners.",
    EV_NORMAL
);
Event EV_Vehicle_ContinueSkidding
(
    "_continueskidding",
    EV_DEFAULT,
    NULL,
    NULL,
    "Continues the skidding animation of a vehicle.",
    EV_NORMAL
);
Event EV_Vehicle_VehicleAnim
(
    "vehicleanim",
    EV_DEFAULT,
    "sF",
    "anim_name weight",
    "Sets an animation to use in the LD Animation slot. \nWeight defaults to 1.0",
    EV_NORMAL
);
Event EV_Vehicle_VehicleAnimDone
(
    "_vehicleanimdone",
    EV_DEFAULT,
    NULL,
    NULL,
    "For Internal Use Only",
    EV_NORMAL
);
Event EV_Vehicle_VehicleMoveAnim
(
    "moveanim",
    EV_DEFAULT,
    "s",
    "anim_name",
    "move the vehicle with an animation",
    EV_NORMAL
);
Event EV_Vehicle_VehicleMoveAnimDone
(
    "_moveanimdone",
    EV_DEFAULT,
    NULL,
    NULL,
    "For Internal Use Only",
    EV_NORMAL
);
Event EV_Vehicle_DamageSounds
(
    "damagesounds",
    EV_DEFAULT,
    "i",
    "on_off",
    "Makes the vehicle play damaged sounds.",
    EV_NORMAL
);
Event EV_Vehicle_RunSounds
(
    "runsounds",
    EV_DEFAULT,
    "i",
    "on_off",
    "Makes the vehicle play running sounds.",
    EV_NORMAL
);
Event EV_Vehicle_ProjectileVulnerable
(
    "projectilevulnerable",
    EV_DEFAULT,
    "I",
    "number_of_hits",
    "Make vehicle vulnerable to being one-shot by projectiles. If number_of_hits is given, it will take this many "
    "shots.",
    EV_NORMAL
);
Event EV_Vehicle_CanUse
(
    "canuse",
    EV_DEFAULT,
    "e",
    "entity",
    "Returns 1 if passed entity can 'use' this vehicle.",
    EV_RETURN
);

cvar_t *g_showvehiclemovedebug;
cvar_t *g_showvehicleentrypoints;
cvar_t *g_showvehicleslotpoints;
cvar_t *g_showvehicletags;
cvar_t *g_showvehiclepath;

CLASS_DECLARATION(Animate, VehicleBase, NULL) {
    {NULL, NULL}
};

VehicleBase::VehicleBase()
{
    if (LoadingSavegame) {
        // Archive function will setup all necessary data
        return;
    }

    takedamage = DAMAGE_NO;
    showModel();

    //
    // rotate the mins and maxs for the model
    //
    setSize(mins, maxs);

    vlink  = NULL;
    offset = vec_zero;

    PostEvent(EV_BecomeNonSolid, EV_POSTSPAWN);
}

CLASS_DECLARATION(VehicleBase, Vehicle, "script_vehicle") {
    {&EV_Blocked,                            &Vehicle::VehicleBlocked            },
    {&EV_Touch,                              &Vehicle::VehicleTouched            },
    {&EV_Use,                                &Vehicle::DriverUse                 },
    {&EV_Vehicle_Start,                      &Vehicle::VehicleStart              },
    {&EV_Vehicle_Drivable,                   &Vehicle::Drivable                  },
    {&EV_Vehicle_PathDrivable,               &Vehicle::PathDrivable              },
    {&EV_Vehicle_ProjectileVulnerable,       &Vehicle::SetProjectileVulnerable   },
    {&EV_Vehicle_UnDrivable,                 &Vehicle::UnDrivable                },
    {&EV_Vehicle_Jumpable,                   &Vehicle::Jumpable                  },
    {&EV_Vehicle_SeatAnglesOffset,           &Vehicle::SeatAnglesOffset          },
    {&EV_Vehicle_SeatOffset,                 &Vehicle::SeatOffset                },
    {&EV_Vehicle_Lock,                       &Vehicle::Lock                      },
    {&EV_Vehicle_UnLock,                     &Vehicle::UnLock                    },
    {&EV_Vehicle_SetWeapon,                  &Vehicle::SetWeapon                 },
    {&EV_Vehicle_SetName,                    &Vehicle::SetName                   },
    {&EV_Vehicle_SetSpeed,                   &Vehicle::SetSpeed                  },
    {&EV_Vehicle_SetTurnRate,                &Vehicle::SetTurnRate               },
    {&EV_Vehicle_SteerInPlace,               &Vehicle::SteerInPlace              },
    {&EV_Vehicle_ShowWeapon,                 &Vehicle::ShowWeaponEvent           },
    {&EV_Damage,                             &Vehicle::EventDamage               },
    {&EV_Vehicle_Destroyed,                  &Vehicle::VehicleDestroyed          },
    {&EV_Vehicle_Mass,                       &Vehicle::SetMass                   },
    {&EV_Vehicle_Front_Mass,                 &Vehicle::SetFrontMass              },
    {&EV_Vehicle_Back_Mass,                  &Vehicle::SetBackMass               },
    {&EV_Vehicle_Tread,                      &Vehicle::SetTread                  },
    {&EV_Vehicle_Radius,                     &Vehicle::SetTireRadius             },
    {&EV_Vehicle_RollingResistance,          &Vehicle::SetRollingResistance      },
    {&EV_Vehicle_Drag,                       &Vehicle::SetDrag                   },
    {&EV_Vehicle_Drive,                      &Vehicle::EventDrive                },
    {&EV_Vehicle_DriveNoWait,                &Vehicle::EventDriveNoWait          },
    {&EV_Vehicle_Stop,                       &Vehicle::EventStop                 },
    {&EV_Vehicle_FullStop,                   &Vehicle::EventFullStop             },
    {&EV_Vehicle_Init,                       &Vehicle::ModelInit                 },
    {&EV_Vehicle_BouncyCoef,                 &Vehicle::BouncyCoef                },
    {&EV_Vehicle_SpringyCoef,                &Vehicle::SpringyCoef               },
    {&EV_Vehicle_Yaw,                        &Vehicle::GetYaw                    },
    {&EV_Vehicle_Roll,                       &Vehicle::RollMinMax                },
    {&EV_Vehicle_Z,                          &Vehicle::ZMinMax                   },
    {&EV_Vehicle_QuerySpeed,                 &Vehicle::QuerySpeed                },
    {&EV_Vehicle_QueryFreePassengerSlot,     &Vehicle::QueryFreePassengerSlot    },
    {&EV_Vehicle_QueryFreeDriverSlot,        &Vehicle::QueryFreeDriverSlot       },
    {&EV_Vehicle_QueryFreeTurretSlot,        &Vehicle::QueryFreeTurretSlot       },
    {&EV_Vehicle_QueryPassengerSlotPosition, &Vehicle::QueryPassengerSlotPosition},
    {&EV_Vehicle_QueryDriverSlotPosition,    &Vehicle::QueryDriverSlotPosition   },
    {&EV_Vehicle_QueryTurretSlotPosition,    &Vehicle::QueryTurretSlotPosition   },
    {&EV_Vehicle_QueryPassengerSlotStatus,   &Vehicle::QueryPassengerSlotStatus  },
    {&EV_Vehicle_QueryDriverSlotStatus,      &Vehicle::QueryDriverSlotStatus     },
    {&EV_Vehicle_QueryTurretSlotStatus,      &Vehicle::QueryTurretSlotStatus     },
    {&EV_Vehicle_QueryPassengerSlotEntity,   &Vehicle::QueryPassengerSlotEntity  },
    {&EV_Vehicle_QueryDriverSlotEntity,      &Vehicle::QueryDriverSlotEntity     },
    {&EV_Vehicle_QueryTurretSlotEntity,      &Vehicle::QueryTurretSlotEntity     },
    {&EV_Vehicle_QueryDriverSlotAngles,      &Vehicle::QueryDriverSlotAngles     },
    {&EV_Vehicle_QueryPassengerSlotAngles,   &Vehicle::QueryPassengerSlotAngles  },
    {&EV_Vehicle_BounceForwardsVelocity,     &Vehicle::BounceForwardsVelocity    },
    {&EV_Vehicle_BounceBackwardsVelocity,    &Vehicle::BounceBackwardsVelocity   },
    {&EV_Vehicle_StopBackwardsVelocity,      &Vehicle::StopBackwardsVelocity     },
    {&EV_Vehicle_StopBackwardsVelocity,      &Vehicle::StopBackwardsVelocity     },
    {&EV_Vehicle_QueryTurretSlotAngles,      &Vehicle::QueryTurretSlotAngles     },
    {&EV_Vehicle_AttachPassengerSlot,        &Vehicle::AttachPassengerSlot       },
    {&EV_Vehicle_AttachDriverSlot,           &Vehicle::AttachDriverSlot          },
    {&EV_Vehicle_AttachTurretSlot,           &Vehicle::AttachTurretSlot          },
    {&EV_Vehicle_DetachPassengerSlot,        &Vehicle::DetachPassengerSlot       },
    {&EV_Vehicle_DetachDriverSlot,           &Vehicle::DetachDriverSlot          },
    {&EV_Vehicle_DetachTurretSlot,           &Vehicle::DetachTurretSlot          },
    {&EV_Vehicle_WheelCorners,               &Vehicle::SetWheelCorners           },
    {&EV_Vehicle_AnimationSet,               &Vehicle::SetAnimationSet           },
    {&EV_Vehicle_SoundSet,                   &Vehicle::SetSoundSet               },
    {&EV_Vehicle_SpawnTurret,                &Vehicle::SpawnTurret               },
    {&EV_Vehicle_ModifyDrive,                &Vehicle::EventModifyDrive          },
    {&EV_Model,                              &Vehicle::EventModel                },
    {&EV_Vehicle_NextDrive,                  &Vehicle::EventNextDrive            },
    {&EV_Vehicle_LockMovement,               &Vehicle::EventLockMovement         },
    {&EV_Vehicle_UnlockMovement,             &Vehicle::EventUnlockMovement       },
    {&EV_Vehicle_RemoveOnDeath,              &Vehicle::EventRemoveOnDeath        },
    {&EV_Vehicle_SetExplosionModel,          &Vehicle::EventSetExplosionModel    },
    {&EV_Vehicle_SetCollisionEntity,         &Vehicle::EventSetCollisionModel    },
    {&EV_Vehicle_SetSoundParameters,         &Vehicle::EventSetSoundParameters   },
    {&EV_Vehicle_SetVolumeParameters,        &Vehicle::EventSetVolumeParameters  },
    {&EV_Vehicle_StopAtEnd,                  &Vehicle::EventStopAtEnd            },
    {&EV_Vehicle_Skidding,                   &Vehicle::EventSkidding             },
    {&EV_Vehicle_ContinueSkidding,           &Vehicle::EventContinueSkidding     },
    {&EV_Vehicle_CollisionEntitySetter,      &Vehicle::EventSetCollisionModel    },
    {&EV_Vehicle_CollisionEntityGetter,      &Vehicle::EventGetCollisionModel    },
    {&EV_Vehicle_VehicleAnim,                &Vehicle::EventVehicleAnim          },
    {&EV_Vehicle_VehicleAnimDone,            &Vehicle::EventVehicleAnimDone      },
    {&EV_Vehicle_VehicleMoveAnim,            &Vehicle::EventVehicleMoveAnim      },
    {&EV_Vehicle_VehicleMoveAnimDone,        &Vehicle::EventVehicleMoveAnimDone  },
    {&EV_Vehicle_DamageSounds,               &Vehicle::EventDamageSounds         },
    {&EV_Vehicle_RunSounds,                  &Vehicle::EventRunSounds            },
    {&EV_Remove,                             &Vehicle::Remove                    },
    {&EV_Turret_SetMaxUseAngle,              &Vehicle::EventSetMaxUseAngle       },
    {&EV_Vehicle_CanUse,                     &Vehicle::EventCanUse               },
    {NULL,                                   NULL                                }
};

/*
====================
Vehicle::Vehicle
====================
*/
Vehicle::Vehicle()
{
    int i;

    entflags |= ECF_VEHICLE;

    AddWaitTill(STRING_DRIVE);
    AddWaitTill(STRING_VEHICLEANIMDONE);
    AddWaitTill(STRING_ANIMDONE);

    g_showvehiclemovedebug   = gi.Cvar_Get("g_showvehiclemovedebug", "0", 0);
    g_showvehicleentrypoints = gi.Cvar_Get("g_showvehicleentrypoints", "0", 0);
    g_showvehicleslotpoints  = gi.Cvar_Get("g_showvehicleslotpoints", "0", 0);
    g_showvehicletags        = gi.Cvar_Get("g_showvehicletags", "0", 0);
    g_showvehiclepath        = gi.Cvar_Get("g_showvehiclepath", "0", 0);

    for (i = 0; i < NUM_VEHICLE_TIRES; i++) {
        VectorClear(m_vTireEnd[i]);
    }
    m_bTireHit[0] = false;
    VectorClear(m_vNormalSum);
    m_iNumNormals = 0;

    if (LoadingSavegame) {
        // Archive function will setup all necessary data
        return;
    }

    edict->s.eType = ET_VEHICLE;

    moveresult   = 0;
    isBlocked    = false;
    m_iGear      = 1;
    m_iRPM       = 0;
    maxtracedist = 0;
    airspeed     = 0;
    for (i = 0; i < 6; i++) {
        m_fGearRatio[i] = 0;
    }

    m_fMass      = 0;
    m_fFrontMass = 0;
    m_fBackMass  = 0;

    m_fWheelBase            = 0.0;
    m_fWheelFrontLoad       = 0.0;
    m_fWheelFrontInnerLoad  = 0.0;
    m_fWheelFrontOutterLoad = 0.0;
    m_fWheelFrontDist       = 0.0;
    m_fWheelFrontSuspension = 0.0;
    m_fWheelBackLoad        = 0.0;
    m_fWheelBackInnerLoad   = 0.0;
    m_fWheelBackOutterLoad  = 0.0;
    m_fWheelBackDist        = 0.0;
    m_fWheelBackSuspension  = 0.0;

    m_fCGHeight     = 0.0;
    m_fBankAngle    = 0.0;
    m_fTread        = 0.0;
    m_fTrackWidth   = 0.0;
    m_fTireFriction = 0.0;
    m_fDrag         = 0.0;
    m_fTireRadius   = 0.0;
    m_fFrontBrakes  = 0.0;
    m_fBackBrakes   = 0.0;

    m_fRollingResistance   = 0.0;
    m_fTireRotationalSpeed = 0.0;
    m_fFrontBrakingForce   = 0.0;
    m_fBackBrakingForce    = 0.0;
    m_fBrakingPerformance  = 0.0;

    m_fLastTurn          = 0.0;
    m_fTangForce         = 0.0;
    m_fInertia           = 0.0;
    m_fDifferentialRatio = 0.0;
    m_fGearEfficiency    = 0.0;
    m_fMaxTraction       = 0.0;
    m_fTractionForce     = 0.0;
    m_fAccelerator       = 0.0;
    m_fTorque            = 0.0;
    m_fStopStartDistance = 0.0;
    m_fStopStartSpeed    = 0.0;
    m_fStopEndDistance   = 0.0;
    m_bWheelSpinning     = 0;
    m_bIsSkidding        = 0;
    m_bIsBraking         = 0;
    m_bAutomatic         = 0;
    m_iNumNormals        = 0;

    memset(&vs, 0, sizeof(vs));
    takedamage                 = DAMAGE_YES;
    seatangles                 = vec_zero;
    driveroffset               = Vector(0, 0, 50);
    seatoffset                 = vec_zero;
    currentspeed               = 0;
    turnangle                  = 0;
    turnimpulse                = 0;
    moveimpulse                = 0;
    jumpimpulse                = 0;
    conesize                   = 75;
    hasweapon                  = false;
    locked                     = true;
    steerinplace               = false;
    drivable                   = false;
    pathDrivable               = false;
    jumpable                   = false;
    showweapon                 = false;
    m_iProjectileHitsRemaining = 0;

    flags |= FL_TOUCH_TRIGGERS | FL_POSTTHINK | FL_THINK | FL_DIE_EXPLODE;
    // touch triggers by default
    gravity = 1;
    mass    = size.length() * 10;

    health      = 1000;
    speed       = 1200;
    maxturnrate = 60.0f;
    m_vAngles   = angles;

    prev_acceleration = vec_zero;
    real_acceleration = vec_zero;
    prev_origin       = origin;
    real_velocity     = vec_zero;
    prev_velocity     = velocity;

    m_vPrevNormal    = vec_zero;
    prev_moveimpulse = 0;

    m_iFrameCtr      = 0;
    m_bFrontSlipping = qfalse;
    m_bBackSlipping  = qfalse;

    m_vAngularVelocity     = vec_zero;
    m_vAngularAcceleration = vec_zero;

    m_sMoveGrid = new cMoveGrid(3, 3, 1);

    m_bAutoPilot       = qfalse;
    m_fIdealSpeed      = 0;
    m_fMaxSpeed        = 0;
    m_fIdealAccel      = 0;
    m_fIdealDistance   = 100;
    m_bBounceBackwards = false;

    m_vOriginOffset  = vec_zero;
    m_vOriginOffset2 = vec_zero;
    m_vAnglesOffset  = vec_zero;

    m_fBouncyCoef  = 0.2f;
    m_fSpringyCoef = 0.8f;
    m_fYawMin      = -10.0f;
    m_fYawMax      = 10.0f;
    m_fYawCoef     = 0.1f;
    m_fRollMin     = -10.0f;
    m_fRollMax     = 10.0f;
    m_fRollCoef    = 0.1f;
    m_fZMin        = -10.0f;
    m_fZMax        = 10.0f;
    m_fZCoef       = 0.1f;

    m_vAngularVelocity     = vec_zero;
    m_vAngularAcceleration = vec_zero;
    v_angle                = vec_zero;
    m_bThinkCalled         = qfalse;
    m_pCurPath             = NULL;
    m_iCurNode             = 0;
    m_pAlternatePath       = NULL;
    m_iAlternateNode       = 0;
    m_pNextPath            = NULL;
    m_iNextPathStartNode   = -1;

    m_fLookAhead            = 0;
    m_fShaderOffset         = 0;
    vs.hit_obstacle         = false;
    vs.groundTrace.fraction = 1.0f;
    edict->clipmask         = MASK_VEHICLE;

    m_fLeftForce    = 0;
    m_fRightForce   = 0;
    m_fForwardForce = 0;
    m_fBackForce    = 0;
    m_fUpForce      = 0;
    m_fDownForce    = 0;

    edict->s.eFlags |= EF_LINKANGLES;

    m_bMovementLocked  = qfalse;
    m_bRemoveOnDeath   = qtrue;
    m_sExplosionModel  = "fx/fx_explosion.tik";
    m_pCollisionEntity = NULL;

    m_fSoundMinPitch  = 0.95f;
    m_fSoundMinSpeed  = 0;
    m_fSoundMaxPitch  = 1.0f;
    m_fSoundMaxSpeed  = 200.0f;
    m_fVolumeMinPitch = 1.0f;
    m_fVolumeMinSpeed = 0;
    m_fVolumeMaxPitch = 1.5f;
    m_fVolumeMaxSpeed = 200.0f;

    m_eSoundState              = ST_OFF;
    m_fNextSoundState          = level.time;
    m_pVehicleSoundEntities[0] = NULL;
    m_pVehicleSoundEntities[1] = NULL;
    m_pVehicleSoundEntities[2] = NULL;
    m_pVehicleSoundEntities[3] = NULL;

    m_bStopEnabled    = qfalse;
    m_bEnableSkidding = qfalse;
    m_fSkidAngle      = 0;
    m_vSkidOrigin     = vec_zero;
    m_fSkidLeftForce  = 0;
    m_fSkidRightForce = 0;

    m_sAnimationSet = "";
    m_sSoundSet     = "";

    m_iLastTiresUpdate = -1;
    for (i = 0; i < NUM_VEHICLE_TIRES; i++) {
        m_vTireEnd[i] = origin;
        m_bTireHit[i] = false;
    }

    m_bAnimMove            = false;
    m_fMaxUseAngle         = 0;
    m_bBounceStayFullSpeed = false;

    ResetSlots();

    PostEvent(EV_Vehicle_Start, EV_POSTSPAWN);
}

/*
====================
Vehicle::~Vehicle
====================
*/
Vehicle::~Vehicle()
{
    RemoveVehicleSoundEntities();

    if (m_pCollisionEntity) {
        m_pCollisionEntity->ProcessEvent(EV_Remove);
    }

    entflags &= ~ECF_VEHICLE;
}

/*
====================
Vehicle::ResetSlots
====================
*/
void Vehicle::ResetSlots(void)
{
    driver.ent                 = NULL;
    driver.boneindex           = -1;
    driver.enter_boneindex     = -1;
    driver.flags               = 0;
    lastdriver.ent             = NULL;
    lastdriver.boneindex       = -1;
    lastdriver.enter_boneindex = -1;
    lastdriver.flags           = SLOT_UNUSED;

    for (int i = 0; i < MAX_PASSENGERS; i++) {
        Passengers[i].ent             = NULL;
        Passengers[i].boneindex       = -1;
        Passengers[i].enter_boneindex = -1;
        Passengers[i].flags           = SLOT_UNUSED;
    }

    for (int i = 0; i < MAX_TURRETS; i++) {
        Turrets[i].ent             = NULL;
        Turrets[i].boneindex       = -1;
        Turrets[i].enter_boneindex = -1;
        Turrets[i].flags           = SLOT_UNUSED;
    }
}

/*
====================
Vehicle::OpenSlotsByModel
====================
*/
void Vehicle::OpenSlotsByModel(void)
{
    str bonename;
    int bonenum;
    int boneindex;

    driver.boneindex       = gi.Tag_NumForName(edict->tiki, "driver");
    driver.enter_boneindex = gi.Tag_NumForName(edict->tiki, "driver_enter");

    if (driver.flags & SLOT_UNUSED) {
        driver.ent   = NULL;
        driver.flags = SLOT_FREE;
    }

    numPassengers = 0;

    for (bonenum = 0; bonenum < MAX_PASSENGERS; bonenum++) {
        str bonenumstr = bonenum;
        bonename       = "passenger" + bonenumstr;
        boneindex      = gi.Tag_NumForName(edict->tiki, bonename.c_str());

        if (boneindex >= 0) {
            numPassengers++;

            Passengers[bonenum].boneindex       = boneindex;
            Passengers[bonenum].enter_boneindex = gi.Tag_NumForName(edict->tiki, "passenger_enter" + bonenumstr);

            if (Passengers[bonenum].flags & SLOT_UNUSED) {
                Passengers[bonenum].ent   = NULL;
                Passengers[bonenum].flags = SLOT_FREE;
            }
        }
    }

    numTurrets = 0;

    for (bonenum = 0; bonenum < MAX_TURRETS; bonenum++) {
        str bonenumstr = bonenum;
        bonename       = "turret" + bonenumstr;
        boneindex      = gi.Tag_NumForName(edict->tiki, bonename.c_str());

        if (boneindex >= 0) {
            numTurrets++;

            Turrets[bonenum].boneindex       = boneindex;
            Turrets[bonenum].enter_boneindex = gi.Tag_NumForName(edict->tiki, "turret_enter" + bonenumstr);

            if (Turrets[bonenum].flags & SLOT_UNUSED) {
                Turrets[bonenum].ent   = NULL;
                Turrets[bonenum].flags = SLOT_FREE;
            }
        }
    }
}

/*
====================
Vehicle::EventModelInit
====================
*/
void Vehicle::ModelInit(Event *ev)
{
    SetControllerTag(0, gi.Tag_NumForName(edict->tiki, "Tire_rotate_front_left"));
    SetControllerTag(1, gi.Tag_NumForName(edict->tiki, "Tire_rotate_front_right"));
    SetControllerTag(2, gi.Tag_NumForName(edict->tiki, "steeringwheel_center"));
}

/*
====================
Vehicle::VehicleStart
====================
*/
void Vehicle::VehicleStart(Event *ev)
{
    Entity       *ent;
    VehicleBase  *last;
    orientation_t orient;

    // become solid
    setSolidType(SOLID_BBOX);
    edict->r.contents = CONTENTS_BBOX;

    last = this;

    setLocalOrigin(localorigin + Vector(0.0f, 0.0f, 30.0f));
    angles.x = m_vAngles.x;
    angles.z = m_vAngles.z;

    for (ent = G_NextEntity(NULL); ent != NULL; ent = G_NextEntity(ent)) {
        if ((ent != this) && (ent->isSubclassOf(VehicleBase))) {
            if ((ent->absmax.x >= absmin.x) && (ent->absmax.y >= absmin.y) && (ent->absmax.z >= absmin.z)
                && (ent->absmin.x <= absmax.x) && (ent->absmin.y <= absmax.y) && (ent->absmin.z <= absmax.z)) {
                last->vlink  = (VehicleBase *)ent;
                last         = (VehicleBase *)ent;
                last->offset = last->origin - origin;
                last->offset = getLocalVector(last->offset);
                last->edict->s.scale *= edict->s.scale;
            }
        }
    }

    last->vlink = NULL;

    OpenSlotsByModel();

    //
    // get the seat offset
    //
    if (GetRawTag("seat", &orient)) {
        driveroffset = Vector(orient.origin);
    }
    driveroffset += seatoffset * edict->s.scale;

    SetDriverAngles(angles + seatangles);

    max_health = health;

    //
    // since 2.0: set targetname for all busy turrets
    //
    for (int i = 0; i < numTurrets; i++) {
        if (Turrets[i].flags & SLOT_BUSY) {
            Turrets[i].ent->SetTargetName(TargetName() + "_turret" + str(i));
        }
    }

    last_origin = origin;
    link();
}

/*
====================
Vehicle::SetWheelCorners
====================
*/
void Vehicle::SetWheelCorners(Event *ev)
{
    Vector size;
    Vector offset;

    size   = ev->GetVector(1);
    offset = ev->GetVector(2);

    m_vOriginCornerOffset = offset;
    maxtracedist          = size[2];

    Corners[0][0] = -(size[0] / 2);
    Corners[0][1] = (size[1] / 2);
    Corners[0][2] = size[2];

    Corners[1][0] = (size[0] / 2);
    Corners[1][1] = (size[1] / 2);
    Corners[1][2] = size[2];

    Corners[2][0] = -(size[0] / 2);
    Corners[2][1] = -(size[1] / 2);
    Corners[2][2] = size[2];

    Corners[3][0] = (size[0] / 2);
    Corners[3][1] = -(size[1] / 2);
    Corners[3][2] = size[2];

    SetupVehicleSoundEntities();
}

/*
====================
Vehicle::QuerySpeed
====================
*/
float Vehicle::QuerySpeed(void)
{
    return currentspeed;
}

/*
====================
Vehicle::QueryFreePassengerSlot
====================
*/
int Vehicle::QueryFreePassengerSlot(void)
{
    for (int i = 0; i < numPassengers; i++) {
        if (Passengers[i].flags & SLOT_FREE) {
            return i;
        }
    }

    return -1;
}

/*
====================
Vehicle::QueryFreeDriverSlot
====================
*/
int Vehicle::QueryFreeDriverSlot(void)
{
    return (driver.flags & SLOT_FREE) ? 0 : -1;
}

/*
====================
Vehicle::QueryFreeTurretSlot
====================
*/
int Vehicle::QueryFreeTurretSlot(void)
{
    for (int i = 0; i < numTurrets; i++) {
        if (Turrets[i].flags & SLOT_FREE) {
            return i;
        }
    }

    return -1;
}

/*
====================
Vehicle::QueryPassengerSlotPosition
====================
*/
void Vehicle::QueryPassengerSlotPosition(int slot, float *pos)
{
    orientation_t orient;

    if (Passengers[slot].enter_boneindex >= 0) {
        GetTagPositionAndOrientation(Passengers[slot].enter_boneindex, &orient);
        VectorCopy(orient.origin, pos);
    } else {
        VectorCopy(origin, pos);
    }
}

/*
====================
Vehicle::QueryDriverSlotPosition
====================
*/
void Vehicle::QueryDriverSlotPosition(int slot, float *pos)
{
    orientation_t orient;

    if (driver.enter_boneindex >= 0) {
        GetTagPositionAndOrientation(driver.enter_boneindex, &orient);
        VectorCopy(orient.origin, pos);
    } else {
        VectorCopy(origin, pos);
    }
}

/*
====================
Vehicle::QueryTurretSlotPosition
====================
*/
void Vehicle::QueryTurretSlotPosition(int slot, float *pos)
{
    orientation_t orient;

    if (Turrets[slot].enter_boneindex >= 0) {
        GetTagPositionAndOrientation(Turrets[slot].enter_boneindex, &orient);
        VectorCopy(orient.origin, pos);
    } else if (Turrets[slot].boneindex >= 0) {
        GetTagPositionAndOrientation(Turrets[slot].boneindex, &orient);
        VectorCopy(orient.origin, pos);
    } else {
        VectorCopy(origin, pos);
    }
}

/*
====================
Vehicle::QueryPassengerSlotAngles
====================
*/
void Vehicle::QueryPassengerSlotAngles(int slot, float *ang)
{
    orientation_t orient;

    GetTagPositionAndOrientation(Passengers[slot].enter_boneindex, &orient);
    MatrixToEulerAngles(orient.axis, ang);
}

/*
====================
Vehicle::QueryDriverSlotAngles
====================
*/
void Vehicle::QueryDriverSlotAngles(int slot, float *ang)
{
    orientation_t orient;

    GetTagPositionAndOrientation(driver.enter_boneindex, &orient);
    MatrixToEulerAngles(orient.axis, ang);
}

/*
====================
Vehicle::QueryTurretSlotAngles
====================
*/
void Vehicle::QueryTurretSlotAngles(int slot, float *ang)
{
    orientation_t orient;

    GetTagPositionAndOrientation(Turrets[slot].enter_boneindex, &orient);
    MatrixToEulerAngles(orient.axis, ang);
}

/*
====================
Vehicle::QueryPassengerSlotStatus
====================
*/
int Vehicle::QueryPassengerSlotStatus(int slot)
{
    return Passengers[slot].flags;
}

/*
====================
Vehicle::QueryDriverSlotStatus
====================
*/
int Vehicle::QueryDriverSlotStatus(int slot)
{
    return driver.flags;
}

/*
====================
Vehicle::QueryTurretSlotStatus
====================
*/
int Vehicle::QueryTurretSlotStatus(int slot)
{
    return Turrets[slot].flags;
}

/*
====================
Vehicle::QuerySpeed
====================
*/
void Vehicle::QuerySpeed(Event *ev)
{
    ev->AddFloat(QuerySpeed());
}

/*
====================
Vehicle::EventQueryFreePassengerSlot
====================
*/
void Vehicle::QueryFreePassengerSlot(Event *ev)
{
    ev->AddInteger(QueryFreePassengerSlot());
}

/*
====================
Vehicle::EventQueryFreeDriverSlot
====================
*/
void Vehicle::QueryFreeDriverSlot(Event *ev)
{
    ev->AddInteger(QueryFreeDriverSlot());
}

/*
====================
Vehicle::EventQueryFreeTurretSlot
====================
*/
void Vehicle::QueryFreeTurretSlot(Event *ev)
{
    ev->AddInteger(QueryFreeTurretSlot());
}

/*
====================
Vehicle::EventQueryPassengerSlotPosition
====================
*/
void Vehicle::QueryPassengerSlotPosition(Event *ev)
{
    Vector vPos;
    int    iSlot;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_PASSENGERS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    QueryPassengerSlotPosition(iSlot, vPos);
    ev->AddVector(vPos);
}

/*
====================
Vehicle::EventQueryDriverSlotPosition
====================
*/
void Vehicle::QueryDriverSlotPosition(Event *ev)
{
    Vector vPos;
    int    iSlot;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_DRIVERS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    QueryDriverSlotPosition(iSlot, vPos);
    ev->AddVector(vPos);
}

/*
====================
Vehicle::EventQueryTurretSlotPosition
====================
*/
void Vehicle::QueryTurretSlotPosition(Event *ev)
{
    Vector vPos;
    int    iSlot;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_TURRETS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    QueryTurretSlotPosition(iSlot, vPos);
    ev->AddVector(vPos);
}

/*
====================
Vehicle::EventQueryPassengerSlotAngles
====================
*/
void Vehicle::QueryPassengerSlotAngles(Event *ev)
{
    Vector vAngles;
    int    iSlot;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_PASSENGERS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    QueryPassengerSlotAngles(iSlot, vAngles);
    ev->AddVector(vAngles);
}

/*
====================
Vehicle::EventQueryDriverSlotAngles
====================
*/
void Vehicle::QueryDriverSlotAngles(Event *ev)
{
    Vector vAngles;
    int    iSlot;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_DRIVERS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    QueryDriverSlotAngles(iSlot, vAngles);
    ev->AddVector(vAngles);
}

/*
====================
Vehicle::EventQueryTurretSlotAngles
====================
*/
void Vehicle::QueryTurretSlotAngles(Event *ev)
{
    Vector vAngles;
    int    iSlot;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_TURRETS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    QueryTurretSlotAngles(iSlot, vAngles);
    ev->AddVector(vAngles);
}

/*
====================
Vehicle::EventQueryPassengerSlotStatus
====================
*/
void Vehicle::QueryPassengerSlotStatus(Event *ev)
{
    int iSlot;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_PASSENGERS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    ev->AddInteger(QueryPassengerSlotStatus(iSlot));
}

/*
====================
Vehicle::EventQueryDriverSlotStatus
====================
*/
void Vehicle::QueryDriverSlotStatus(Event *ev)
{
    int iSlot;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_DRIVERS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    ev->AddInteger(QueryDriverSlotStatus(iSlot));
}

/*
====================
Vehicle::EventQueryTurretSlotStatus
====================
*/
void Vehicle::QueryTurretSlotStatus(Event *ev)
{
    int iSlot;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_TURRETS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    ev->AddInteger(QueryTurretSlotStatus(iSlot));
}

/*
====================
Vehicle::EventQueryPassengerSlotEntity
====================
*/
void Vehicle::QueryPassengerSlotEntity(Event *ev)
{
    int iSlot;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_PASSENGERS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    ev->AddEntity(QueryPassengerSlotEntity(iSlot));
}

/*
====================
Vehicle::EventQueryDriverSlotEntity
====================
*/
void Vehicle::QueryDriverSlotEntity(Event *ev)
{
    int iSlot;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_DRIVERS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    ev->AddEntity(QueryDriverSlotEntity(iSlot));
}

/*
====================
Vehicle::EventQueryTurretSlotEntity
====================
*/
void Vehicle::QueryTurretSlotEntity(Event *ev)
{
    int iSlot;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_TURRETS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    ev->AddEntity(QueryTurretSlotEntity(iSlot));
}

/*
====================
Vehicle::EventAttachPassengerSlot
====================
*/
void Vehicle::AttachPassengerSlot(Event *ev)
{
    int iSlot;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_PASSENGERS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    AttachPassengerSlot(iSlot, ev->GetEntity(2), vec_zero);
    UpdatePassengerSlot(iSlot);
}

/*
====================
Vehicle::EventAttachDriverSlot
====================
*/
void Vehicle::AttachDriverSlot(Event *ev)
{
    int iSlot;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_DRIVERS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    AttachDriverSlot(0, ev->GetEntity(2), vec_zero);
    UpdateDriverSlot(0);
}

/*
====================
Vehicle::EventAttachTurretSlot
====================
*/
void Vehicle::AttachTurretSlot(Event *ev)
{
    int iSlot;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_TURRETS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    AttachTurretSlot(iSlot, ev->GetEntity(2), vec_zero, NULL);
    UpdateTurretSlot(iSlot);
}

/*
====================
Vehicle::EventDetachPassengerSlot
====================
*/
void Vehicle::DetachPassengerSlot(Event *ev)
{
    int    iSlot;
    Vector vExitPosition;
    Vector vExitAngles;
    bool   bHasExitAngles = false;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_PASSENGERS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    if (ev->NumArgs() == 2) {
        if (ev->IsVectorAt(2)) {
            vExitPosition = ev->GetVector(2);
        } else if (ev->IsEntityAt(2)) {
            vExitPosition  = ev->GetEntity(2)->origin;
            bHasExitAngles = true;
        } else if (ev->IsSimpleEntityAt(2)) {
            vExitPosition = ev->GetSimpleEntity(2)->origin;
        }

        DetachPassengerSlot(iSlot, vExitPosition, bHasExitAngles ? &vExitAngles : NULL);
    } else {
        DetachPassengerSlot(iSlot, vec_zero, NULL);
    }
}

/*
====================
Vehicle::EventDetachDriverSlot
====================
*/
void Vehicle::DetachDriverSlot(Event *ev)
{
    int    iSlot;
    Vector vExitPosition;
    Vector vExitAngles;
    bool   bHasExitAngles = false;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_DRIVERS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    if (ev->NumArgs() == 2) {
        if (ev->IsVectorAt(2)) {
            vExitPosition = ev->GetVector(2);
        } else if (ev->IsEntityAt(2)) {
            vExitPosition  = ev->GetEntity(2)->origin;
            bHasExitAngles = true;
        } else if (ev->IsSimpleEntityAt(2)) {
            vExitPosition = ev->GetSimpleEntity(2)->origin;
        }

        DetachDriverSlot(iSlot, vExitPosition, bHasExitAngles ? &vExitAngles : NULL);
    } else {
        DetachDriverSlot(iSlot, vec_zero, NULL);
    }
}

/*
====================
Vehicle::EventDetachTurretSlot
====================
*/
void Vehicle::DetachTurretSlot(Event *ev)
{
    int    iSlot;
    Vector vExitPosition;
    Vector vExitAngles;
    bool   bHasExitAngles = false;

    iSlot = ev->GetInteger(1);
    if (iSlot >= MAX_TURRETS) {
        ScriptError("Slot Specified is greater than maximum allowed for that parameter\n");
    }

    if (ev->NumArgs() == 2) {
        if (ev->IsVectorAt(2)) {
            vExitPosition = ev->GetVector(2);
        } else if (ev->IsEntityAt(2)) {
            vExitAngles    = ev->GetEntity(2)->angles;
            bHasExitAngles = true;
            vExitPosition  = ev->GetEntity(2)->origin;
        } else if (ev->IsSimpleEntityAt(2)) {
            vExitPosition = ev->GetSimpleEntity(2)->origin;
        }
    }

    DetachTurretSlot(iSlot, vExitPosition, bHasExitAngles ? &vExitAngles : NULL);
}

/*
====================
Vehicle::QueryPassengerSlotEntity
====================
*/
Entity *Vehicle::QueryPassengerSlotEntity(int slot)
{
    return Passengers[slot].ent;
}

/*
====================
Vehicle::QueryDriverSlotEntity
====================
*/
Entity *Vehicle::QueryDriverSlotEntity(int slot)
{
    return driver.ent;
}

/*
====================
Vehicle::QueryTurretSlotEntity
====================
*/
Entity *Vehicle::QueryTurretSlotEntity(int slot)
{
    return Turrets[slot].ent;
}

/*
====================
Vehicle::FindExitPosition
====================
*/
bool Vehicle::FindExitPosition(Entity *pEnt, const Vector& vOrigin, const Vector *vAngles)
{
    Event  *ev;
    Vector  yawAngles;
    Vector  forward;
    Vector  offset;
    float   radius;
    int     i, j;
    trace_t trace;

    if (!pEnt) {
        return false;
    }

    if ((g_target_game < target_game_e::TG_MOHTA || !pEnt->IsSubclassOfPlayer()) && vOrigin != vec_zero) {
        if (vAngles) {
            pEnt->setAngles(*vAngles);
        }

        pEnt->setOrigin(vOrigin);
        pEnt->velocity = vec_zero;

        ev = new Event(EV_Vehicle_Exit);
        ev->AddEntity(this);
        pEnt->ProcessEvent(ev);

        return true;
    }

    radius = (pEnt->size.length() + size.length()) * 0.5;

    for (i = 0; i < 128; i += 32) {
        for (j = 0; j < 360; j += 30) {
            yawAngles    = vec_zero;
            yawAngles[1] = j + angles[1] + 180;
            yawAngles.AngleVectorsLeft(&forward);

            offset = origin + forward * radius;
            offset[2] += i;

            trace = G_Trace(
                offset,
                pEnt->mins,
                pEnt->maxs,
                offset,
                NULL,
                pEnt->edict->clipmask,
                pEnt->IsSubclassOfPlayer() ? qtrue : qfalse,
                " Vehicle::FindExitPosition"
            );

            if (!trace.startsolid && !trace.allsolid) {
                Vector end;

                offset = trace.endpos;
                end    = offset;
                end[2] -= 128;

                trace = G_Trace(
                    offset,
                    pEnt->mins,
                    pEnt->maxs,
                    end,
                    NULL,
                    pEnt->edict->clipmask,
                    pEnt->IsSubclassOfPlayer() ? qtrue : qfalse,
                    " Vehicle::FindExitPosition"
                );

                if (!trace.startsolid && !trace.allsolid && trace.fraction < 1) {
                    pEnt->setOrigin(trace.endpos);
                    pEnt->velocity = vec_zero;

                    if (vAngles) {
                        pEnt->setAngles(*vAngles);
                    }

                    ev = new Event(EV_Vehicle_Exit);
                    ev->AddEntity(this);
                    pEnt->ProcessEvent(ev);
                    return true;
                }
            }
        }
    }

    return false;
}

/*
====================
Vehicle::AttachPassengerSlot
====================
*/
void Vehicle::AttachPassengerSlot(int slot, Entity *ent, Vector vExitPosition)
{
    Entity *passenger;
    str     sName;

    if (!ent) {
        return;
    }

    passenger = Passengers[slot].ent;
    if (!passenger) {
        Passengers[slot].ent   = ent;
        Passengers[slot].flags = SLOT_BUSY;

        sName = m_sSoundSet + "snd_doorclose";
        Sound(sName);

        Event *event = new Event(EV_Vehicle_Enter);
        event->AddEntity(this);
        Passengers[slot].ent->ProcessEvent(event);

        offset = ent->origin - origin;

        flags |= FL_POSTTHINK;
        SetDriverAngles(seatangles + angles);
    } else if (!isLocked() && ent == passenger) {
        DetachPassengerSlot(slot, vec_zero, NULL);
    }
}

/*
====================
Vehicle::AttachDriverSlot
====================
*/
void Vehicle::AttachDriverSlot(int slot, Entity *ent, Vector vExitPosition)
{
    Entity *d;
    str     sName;

    if (!ent || !ent->IsSubclassOfSentient()) {
        return;
    }

    d = Driver();
    if (!d) {
        driver.ent     = ent;
        driver.flags   = SLOT_BUSY;
        lastdriver.ent = driver.ent;

        sName = m_sSoundSet + "snd_doorclose";
        Sound(sName);

        sName = m_sSoundSet + "snd_start";
        Sound(sName);

        Event *event = new Event(EV_Vehicle_Enter);
        event->AddEntity(this);
        driver.ent->ProcessEvent(event);

        offset = ent->origin - origin;

        flags |= FL_POSTTHINK;
        SetDriverAngles(seatangles + angles);
        DriverAdded();
    } else if (ent == d && !isLocked()) {
        DetachDriverSlot(slot, vec_zero, NULL);
    }
}

/*
====================
Vehicle::AttachTurretSlot

Attach a turret or a sentient that will use the turret to the vehicle.
====================
*/
void Vehicle::AttachTurretSlot(int slot, Entity *ent, Vector vExitPosition, Vector *vExitAngles)
{
    TurretGun        *pTurret;
    VehicleTurretGun *pVehicleTurret;
    str               sName;

    if (!ent) {
        return;
    }

    pTurret = (TurretGun *)Turrets[slot].ent.Pointer();
    if (pTurret && ent->IsSubclassOfWeapon()) {
        if (ent == pTurret && !isLocked()) {
            DetachTurretSlot(slot, vec_zero, NULL);
        }
    } else if (ent->IsSubclassOfWeapon()) {
        Turrets[slot].ent   = ent;
        Turrets[slot].flags = SLOT_BUSY;

        pTurret = (TurretGun *)ent;

        ent->takedamage = DAMAGE_NO;
        ent->setSolidType(SOLID_NOT);

        Event *event = new Event(EV_Vehicle_Enter);
        event->AddEntity(this);
        Turrets[slot].ent->ProcessEvent(event);

        offset = ent->origin - origin;

        flags |= FL_POSTTHINK;
        Turrets[slot].ent->setAngles(angles);

        if (pTurret->IsSubclassOfTurretGun()) {
            pTurret->m_bUsable   = false;
            pTurret->m_bRestable = false;
        }
    } else if (pTurret) {
        Entity *pTurretOwner       = NULL;
        Entity *pRemoteTurretOwner = NULL;

        if (pTurret->IsSubclassOfTurretGun()) {
            pTurretOwner = pTurret->GetOwner();
        }

        if (pTurret->IsSubclassOfVehicleTurretGun()) {
            pVehicleTurret     = (VehicleTurretGun *)pTurret;
            pRemoteTurretOwner = pVehicleTurret->GetRawRemoteOwner();
        }

        if (pTurret->IsSubclassOfTurretGun()) {
            if (pTurret->IsSubclassOfVehicleTurretGun() && pVehicleTurret->isLocked()) {
                ScriptError("Turret is locked, cannot attach to turret slot.");
            }

            pTurret->m_bUsable = true;
        }

        Event *event = new Event(EV_Use);
        event->AddEntity(ent);
        pTurret->ProcessEvent(event);

        if (ent->IsSubclassOfPlayer()) {
            Player *pPlayer     = (Player *)ent;
            pPlayer->m_pVehicle = this;
        }

        if (pTurret->IsSubclassOfTurretGun()) {
            pTurret->m_bUsable = false;
        }

        if (pTurretOwner == ent || pRemoteTurretOwner == ent) {
            if (pRemoteTurretOwner) {
                pVehicleTurret->SetRemoteOwner(NULL);
            }

            FindExitPosition(ent, vExitPosition, vExitAngles);
        }
    }
}

/*
====================
Vehicle::DetachPassengerSlot
====================
*/
void Vehicle::DetachPassengerSlot(int slot, Vector vExitPosition, Vector *vExitAngles)
{
    Entity *passenger = Passengers[slot].ent;

    if (!passenger) {
        return;
    }

    if (!FindExitPosition(passenger, vExitPosition, vExitAngles)) {
        // cannot exit
        return;
    }

    Sound(m_sSoundSet + "snd_dooropen");
    Passengers[slot].ent   = NULL;
    Passengers[slot].flags = SLOT_FREE;
}

/*
====================
Vehicle::DetachDriverSlot
====================
*/
void Vehicle::DetachDriverSlot(int slot, Vector vExitPosition, Vector *vExitAngles)
{
    Entity *other = driver.ent;

    if (!other) {
        return;
    }

    if (!FindExitPosition(other, vExitPosition, vExitAngles)) {
        // cannot exit
        return;
    }

    turnimpulse = 0;
    moveimpulse = 0;
    jumpimpulse = 0;

    if (drivable) {
        StopLoopSound();
        Sound("snd_dooropen", CHAN_BODY);
        Sound("snd_stop", CHAN_VOICE);
        driver.ent->setSolidType(SOLID_BBOX);
    }

    driver.ent   = NULL;
    driver.flags = SLOT_FREE;
}

/*
====================
Vehicle::DetachTurretSlot

Detach a turret or a sentient.
====================
*/
void Vehicle::DetachTurretSlot(int slot, Vector vExitPosition, Vector *vExitAngles)
{
    Entity *passenger = Turrets[slot].ent;

    if (!passenger) {
        return;
    }

    if (!FindExitPosition(passenger, vExitPosition, vExitAngles)) {
        // cannot exit
        return;
    }

    turnimpulse = 0;
    moveimpulse = 0;
    jumpimpulse = 0;
    passenger->setSolidType(SOLID_BBOX);

    Sound(m_sSoundSet + "snd_dooropen");

    if (passenger->IsSubclassOfTurretGun()) {
        TurretGun *pTurret   = static_cast<TurretGun *>(passenger);
        pTurret->m_bUsable   = true;
        pTurret->m_bRestable = true;
    }

    Turrets[slot].ent   = NULL;
    Turrets[slot].flags = SLOT_FREE;
}

/*
====================
Vehicle::FindPassengerSlotByEntity
====================
*/
int Vehicle::FindPassengerSlotByEntity(Entity *ent)
{
    for (int i = 0; i < MAX_PASSENGERS; i++) {
        if (Passengers[i].ent == ent) {
            return i;
        }
    }

    return -1;
}

/*
====================
Vehicle::FindDriverSlotByEntity
====================
*/
int Vehicle::FindDriverSlotByEntity(Entity *ent)
{
    return driver.ent == ent ? 0 : -1;
}

/*
====================
Vehicle::FindTurretSlotByEntity
====================
*/
int Vehicle::FindTurretSlotByEntity(Entity *ent)
{
    for (int i = 0; i < MAX_TURRETS; i++) {
        if (Turrets[i].ent == ent) {
            return i;
        }
    }

    return -1;
}

/*
====================
Vehicle::Drivable
====================
*/
void Vehicle::Drivable(Event *ev)
{
    setMoveType(MOVETYPE_VEHICLE);
    drivable = true;
}

/*
====================
Vehicle::PathDrivable
====================
*/
void Vehicle::PathDrivable(Event *ev)
{
    pathDrivable = ev->GetBoolean(1);
}

/*
====================
Vehicle::UnDrivable
====================
*/
void Vehicle::UnDrivable(Event *ev)
{
    setMoveType(MOVETYPE_PUSH);
    drivable = false;
}

/*
====================
Vehicle::Jumpable
====================
*/
void Vehicle::Jumpable(Event *ev)
{
    jumpable = true;
}

/*
====================
Vehicle::Lock
====================
*/
void Vehicle::Lock(Event *ev)
{
    Lock();
}

/*
====================
Vehicle::UnLock
====================
*/
void Vehicle::UnLock(Event *ev)
{
    UnLock();
}

/*
====================
Vehicle::SteerInPlace
====================
*/
void Vehicle::SteerInPlace(Event *ev)
{
    steerinplace = true;
}

/*
====================
Vehicle::SeatAnglesOffset
====================
*/
void Vehicle::SeatAnglesOffset(Event *ev)
{
    seatangles = ev->GetVector(1);
}

/*
====================
Vehicle::SeatOffset
====================
*/
void Vehicle::SeatOffset(Event *ev)
{
    seatoffset = ev->GetVector(1);
}

/*
====================
Vehicle::SetWeapon
====================
*/
void Vehicle::SetWeapon(Event *ev)
{
    showweapon = true;
    hasweapon  = true;
    weaponName = ev->GetString(1);
}

/*
====================
Vehicle::SetName
====================
*/
void Vehicle::SetName(Event *ev)
{
    vehicleName = ev->GetString(1);
}

/*
====================
Vehicle::ShowWeaponEvent
====================
*/
void Vehicle::ShowWeaponEvent(Event *ev)
{
    showweapon = true;
}

/*
====================
Vehicle::HasWeapon
====================
*/
qboolean Vehicle::HasWeapon(void)
{
    return hasweapon;
}

/*
====================
Vehicle::ShowWeapon
====================
*/
qboolean Vehicle::ShowWeapon(void)
{
    return showweapon;
}

/*
====================
Vehicle::SetDriverAngles
====================
*/
void Vehicle::SetDriverAngles(Vector angles)
{
    int i;

    if (!driver.ent || !driver.ent->client) {
        return;
    }

    for (i = 0; i < 3; i++) {
        driver.ent->client->ps.delta_angles[i] = ANGLE2SHORT(angles[i] - driver.ent->client->cmd_angles[i]);
    }
}

/*
====================
Vehicle::Driver
====================
*/
Entity *Vehicle::Driver(void)
{
    return driver.ent;
}

/*
====================
Vehicle::IsDrivable
====================
*/
qboolean Vehicle::IsDrivable(void)
{
    return drivable;
}

/*
====================
Vehicle::BounceForwardsVelocity
====================
*/
void Vehicle::BounceForwardsVelocity(Event *ev)
{
    if (m_bBounceBackwards) {
        m_bBounceStayFullSpeed = false;
        m_bBounceBackwards     = false;
        currentspeed           = -currentspeed;
        moveimpulse            = -moveimpulse;
    }
}

/*
====================
Vehicle::BounceBackwardsVelocity
====================
*/
void Vehicle::BounceBackwardsVelocity(Event *ev)
{
    if (!m_bBounceBackwards) {
        m_bBounceBackwards = true;
        currentspeed       = -currentspeed;
        moveimpulse        = -moveimpulse;
        if (ev->NumArgs() > 0) {
            m_bBounceStayFullSpeed = ev->GetBoolean(1);
        }
    }
}

/*
====================
Vehicle::StopForwardsVelocity
====================
*/
void Vehicle::StopForwardsVelocity(Event *ev)
{
    if (!m_bBounceBackwards) {
        moveimpulse = 0;
    }
}

/*
====================
Vehicle::StopBackwardsVelocity
====================
*/
void Vehicle::StopBackwardsVelocity(Event *ev)
{
    if (m_bBounceBackwards) {
        moveimpulse = 0;
    }
}

/*
====================
Vehicle::EventSetSpeed
====================
*/
void Vehicle::SetSpeed(Event *ev)
{
    speed = ev->GetFloat(1);
}

/*
====================
Vehicle::EventSetTurnRate
====================
*/
void Vehicle::SetTurnRate(Event *ev)
{
    maxturnrate = ev->GetFloat(1);
}

/*
====================
Vehicle::SetMass
====================
*/
void Vehicle::SetMass(Event *ev)
{
    m_fMass      = ev->GetFloat(1);
    m_fFrontMass = m_fMass * 0.5;
    m_fBackMass  = m_fMass * 0.5;
}

/*
====================
Vehicle::SetFrontMass
====================
*/
void Vehicle::SetFrontMass(Event *ev)
{
    m_fFrontMass = ev->GetFloat(1);
    m_fMass      = m_fFrontMass + m_fBackMass;
}

/*
====================
Vehicle::SetBackMass
====================
*/
void Vehicle::SetBackMass(Event *ev)
{
    m_fBackMass = ev->GetFloat(1);
    m_fMass     = m_fFrontMass + m_fBackMass;
}

/*
====================
Vehicle::SetTread
====================
*/
void Vehicle::SetTread(Event *ev)
{
    m_fTread = ev->GetFloat(1);
}

/*
====================
Vehicle::SetTireRadius
====================
*/
void Vehicle::SetTireRadius(Event *ev)
{
    m_fTireRadius = ev->GetFloat(1);
}

/*
====================
Vehicle::SetRollingResistance
====================
*/
void Vehicle::SetRollingResistance(Event *ev)
{
    m_fRollingResistance = ev->GetFloat(1);
}

/*
====================
Vehicle::SetDrag
====================
*/
void Vehicle::SetDrag(Event *ev)
{
    m_fDrag = ev->GetFloat(1);
}

/*
====================
Vehicle::BouncyCoef
====================
*/
void Vehicle::BouncyCoef(Event *ev)
{
    m_fBouncyCoef = ev->GetFloat(1);
}

/*
====================
Vehicle::SpringyCoef
====================
*/
void Vehicle::SpringyCoef(Event *ev)
{
    m_fSpringyCoef = ev->GetFloat(1);
}

/*
====================
Vehicle::YawMinMax
====================
*/
void Vehicle::YawMinMax(Event *ev)
{
    if (ev->NumArgs() != 3) {
        ScriptError("No Parameter for YawMinMax");
    }

    m_fYawMin  = ev->GetFloat(1);
    m_fYawMax  = ev->GetFloat(2);
    m_fYawCoef = ev->GetFloat(3);

    if (m_fYawMin > m_fYawMax) {
        ScriptError("Mismatched mins and maxs for YawMinMax");
    }
}

/*
====================
Vehicle::RollMinMax
====================
*/
void Vehicle::RollMinMax(Event *ev)
{
    if (ev->NumArgs() != 3) {
        ScriptError("No Parameter for RollMinMax");
    }

    m_fRollMin  = ev->GetFloat(1);
    m_fRollMax  = ev->GetFloat(2);
    m_fRollCoef = ev->GetFloat(3);

    if (m_fRollMin > m_fRollMax) {
        ScriptError("Mismatched mins and maxs for RollMinMax");
    }
}

/*
====================
Vehicle::ZMinMax
====================
*/
void Vehicle::ZMinMax(Event *ev)
{
    if (ev->NumArgs() != 3) {
        ScriptError("No Parameter for ZMinMax");
    }

    m_fZMin  = ev->GetFloat(1);
    m_fZMax  = ev->GetFloat(2);
    m_fZCoef = ev->GetFloat(3);

    if (m_fZMin > m_fZMax) {
        ScriptError("Mismatched mins and maxs for ZMinMax");
    }
}

/*
====================
Vehicle::SetAnimationSet
====================
*/
void Vehicle::SetAnimationSet(Event *ev)
{
    m_sAnimationSet = ev->GetString(1);
}

/*
====================
Vehicle::SetSoundSet
====================
*/
void Vehicle::SetSoundSet(Event *ev)
{
    m_sSoundSet = ev->GetString(1);
}

/*
====================
Vehicle::GetTagPositionAndOrientation
====================
*/
qboolean Vehicle::GetTagPositionAndOrientation(int tagnum, orientation_t *new_or)
{
    int           i;
    orientation_t tag_or;
    float         axis[3][3];

    GetRawTag(tagnum, &tag_or);

    AnglesToAxis(angles, axis);

    VectorCopy(origin, new_or->origin);

    for (i = 0; i < 3; i++) {
        VectorMA(new_or->origin, tag_or.origin[i], tag_or.axis[i], new_or->origin);
    }

    MatrixMultiply(tag_or.axis, axis, new_or->axis);
    return true;
}

/*
====================
Vehicle::GetTagPositionAndOrientation
====================
*/
qboolean Vehicle::GetTagPositionAndOrientation(str tagname, orientation_t *new_or)
{
    int tagnum = gi.Tag_NumForName(edict->tiki, tagname.c_str());

    if (tagnum < 0) {
        warning("Vehicle::GetTagPositionAndOrientation", "Could not find tag \"%s\"", tagname.c_str());
        return false;
    } else {
        return GetTagPositionAndOrientation(tagnum, new_or);
    }
}

/*
====================
Vehicle::CheckWater
====================
*/
void Vehicle::CheckWater(void)
{
    Vector       point;
    int          cont;
    int          sample1;
    int          sample2;
    VehicleBase *v;

    unlink();
    v = this;
    while (v->vlink) {
        v = v->vlink;
        v->unlink();
    }

    if (driver.ent) {
        driver.ent->unlink();
    }

    //
    // get waterlevel
    //
    waterlevel = 0;
    watertype  = 0;

    sample2 = maxs[2] - mins[2];
    sample1 = sample2 / 2;

    point = origin;
    point[2] += mins[2];
    cont = gi.pointcontents(point, 0);

    if (cont & MASK_WATER) {
        watertype  = cont;
        waterlevel = 1;
        point[2]   = origin[2] + mins[2] + sample1;
        cont       = gi.pointcontents(point, 0);
        if (cont & MASK_WATER) {
            waterlevel = 2;
            point[2]   = origin[2] + mins[2] + sample2;
            cont       = gi.pointcontents(point, 0);
            if (cont & MASK_WATER) {
                waterlevel = 3;
            }
        }
    }

    link();
    v = this;
    while (v->vlink) {
        v = v->vlink;
        v->link();
    }

    if (driver.ent) {
        driver.ent->link();
        driver.ent->waterlevel = waterlevel;
        driver.ent->watertype  = watertype;
    }
}

/*
====================
Vehicle::Drive
====================
*/
qboolean Vehicle::Drive(usercmd_t *ucmd)
{
    Vector i, j, k;

    i = velocity;
    VectorNormalize(i);

    if (!driver.ent || !driver.ent->isClient()) {
        return false;
    }

    if (!drivable) {
        driver.ent->client->ps.pm_flags |= PMF_FROZEN;
        ucmd->forwardmove = 0;
        ucmd->rightmove   = 0;
        ucmd->upmove      = 0;
        return false;
    }

    driver.ent->client->ps.pm_flags |= PMF_NO_PREDICTION;

    moveimpulse  = ((float)ucmd->forwardmove) * (VectorLength(i) + 1.0);
    m_bIsBraking = ucmd->forwardmove < 0;
    m_fAccelerator += ((float)ucmd->forwardmove) * 0.005;
    if (m_fAccelerator < 0.0) {
        m_fAccelerator = 0.0;
    } else if (m_fAccelerator > 1.0) {
        m_fAccelerator = 1.0;
    }

    turnimpulse = ((float)-ucmd->rightmove);
    jumpimpulse = ((float)ucmd->upmove * gravity) / 350;
    if ((jumpimpulse < 0) || (!jumpable)) {
        jumpimpulse = 0;
    }

    turnimpulse += angledist(SHORT2ANGLE(ucmd->angles[1]) - driver.ent->client->cmd_angles[1]) * 8;

    return true;
}

/*
====================
Vehicle::PathDrive
====================
*/
qboolean Vehicle::PathDrive(usercmd_t *ucmd)
{
    if (!pathDrivable) {
        return false;
    }

    if (m_bAutoPilot) {
        if (ucmd->forwardmove > 0) {
            //
            // forward speed
            //
            if (m_bBounceBackwards) {
                m_fIdealSpeed = 0;
                if (!currentspeed) {
                    m_bBounceBackwards = false;
                }
            } else {
                m_fIdealSpeed = m_fMaxSpeed;
            }
        } else if (ucmd->forwardmove < 0) {
            //
            // backward speed
            //
            if (m_bBounceBackwards) {
                m_fIdealSpeed = -m_fMaxSpeed;
            } else {
                m_fIdealSpeed = 0;
                if (!currentspeed) {
                    m_bBounceBackwards = true;
                }
            }
        } else {
            //
            // stopped
            //
            m_fIdealSpeed = 0;
        }
    }

    return true;
}

/*
====================
Vehicle::AutoPilot
====================
*/
void Vehicle::AutoPilot(void)
{
    Vector vAngles;
    Vector vDeltaAngles, vDelta, vDeltaSave;
    float *vTmp;
    vec3_t vPrev, vCur, vTotal;
    float  fTotal, fCoef;
    Vector vWishPosition, vAlternateWishPosition;
    float  fDistToCurPos, fCurPathPosition;

    if (!m_pCurPath || m_pCurPath->m_iPoints == 0) {
        m_bAutoPilot = false;
        return;
    }

    if (g_showvehiclepath && g_showvehiclepath->integer) {
        int    iFlags;
        float  fBS;
        Vector vTmp1;
        Vector vTmp2;
        Vector vBS;

        iFlags = 0;
        fBS    = sin(level.time) * 16;
        vBS    = Vector(1, 1, 1) * fBS;

        for (int i = 0; i < m_pCurPath->m_iPoints; i++) {
            float  fZ = 0;
            Vector vColor(0, 1, 1);

            vTmp  = m_pCurPath->GetByNode(i, &iFlags);
            vTmp1 = vTmp + 1;
            fZ    = 0;

            if (iFlags & 1) {
                vColor = Vector(1, 1, 1);
                fZ += 1;
                G_DebugString(vTmp1 + Vector(0, 0, fZ * 32), sin(level.time) + 3, 1, 1, 0, "START_STOPPING");
            }
            if (iFlags & 2) {
                vColor = Vector(0, 1, 1);
                fZ += 1;
                G_DebugString(vTmp1 + Vector(0, 0, fZ * 32), sin(level.time) + 3, 0, 1, 0, "START_SKIDDING");
            }
            if (iFlags & 4) {
                fZ += 1;
                G_DebugString(vTmp1 + Vector(0, 0, fZ * 32), sin(level.time) + 3, 0, 0, 1, "STOP_SKIDDING");
            }

            if (i == m_iCurNode) {
                G_DebugBBox(vTmp1, vBS * -1, vBS, 1, 0, 0, 1);
            } else {
                G_DebugBBox(vTmp1, vBS * -1, vBS, vColor[0], vColor[1], vColor[2], 1);
            }

            vTmp  = m_pCurPath->GetByNode(i + 1, NULL);
            vTmp2 = vTmp + 1;

            G_DebugLine(vTmp1, vTmp2, 0, 1, 0, 1);
        }
    }

    do {
        if (m_iCurNode > 0) {
            int iFlags;

            vTmp = m_pCurPath->GetByNode(m_iCurNode, NULL);
            VectorCopy(vTmp + 1, vCur);

            if (g_showvehiclemovedebug->integer) {
                G_DebugString(vCur, 3, 1, 1, 1, "%f", vTmp[0]);
            }

            vTmp = m_pCurPath->GetByNode(m_iCurNode - 1, NULL);
            VectorCopy(vTmp + 1, vPrev);

            if (g_showvehiclemovedebug->integer) {
                G_DebugString(vPrev, 3, 1, 1, 1, "%f", vTmp[0]);
            }

            VectorSubtract(vCur, vPrev, vTotal);
            fTotal      = VectorLength(vTotal);
            m_vIdealDir = vTotal;

            VectorNormalize(m_vIdealDir);
            angles.AngleVectorsLeft(&vDelta, NULL, NULL);
            fCoef = ProjectLineOnPlane(vDelta, DotProduct(origin, vDelta), vPrev, vCur, NULL);

            if (g_showvehiclemovedebug->integer) {
                G_DebugBBox(vPrev, Vector(-32, -32, -32), Vector(32, 32, 32), 0, 1, 1, 1);
                G_DebugBBox(vCur, Vector(-32, -32, -32), Vector(32, 32, 32), 1, 1, 0, 1);
                G_DebugArrow(vCur, m_vIdealDir * -1, (1 - fCoef) * fTotal, 0, 1, 0, 1);
                G_DebugArrow(vPrev, m_vIdealDir * 1, fCoef * fTotal, 0, 0, 1, 1);
            }

            vTmp             = m_pCurPath->GetByNode(m_iCurNode - (1.0 - fCoef), NULL);
            fCurPathPosition = vTmp[0];
            // Added in 2.30
            //  Check if vehicle bounces backwards
            if (m_bBounceBackwards) {
                vTmp = m_pCurPath->Get(fCurPathPosition - m_fLookAhead, NULL);
            } else {
                vTmp = m_pCurPath->Get(fCurPathPosition + m_fLookAhead, NULL);
            }
            vWishPosition = vTmp + 1;
            fDistToCurPos = Vector(origin[0] - vWishPosition[0], origin[1] - vWishPosition[1], 0).length();

            // Added in 2.30
            //  Check if vehicle bounces backwards
            if (fCoef > 1 && !m_bBounceBackwards) {
                m_iCurNode++;
                if (m_iCurNode >= m_pCurPath->m_iPoints) {
                    break;
                }

                m_pCurPath->GetByNode(m_iCurNode, &iFlags);
                if (iFlags & 1) {
                    ProcessEvent(EV_Vehicle_StopAtEnd);
                }

                if (iFlags & 2) {
                    Event *event = new Event(EV_Vehicle_Skidding);
                    event->AddInteger(1);
                    ProcessEvent(event);
                }

                if (iFlags & 4) {
                    Event *event = new Event(EV_Vehicle_Skidding);
                    event->AddInteger(0);
                    ProcessEvent(event);
                }
            } else if (fCoef < 0 && m_bBounceBackwards) {
                m_iCurNode--;
                if (m_iCurNode < 1) {
                    m_iCurNode         = 1;
                    m_bBounceBackwards = false;
                    currentspeed       = -currentspeed;
                    moveimpulse        = -moveimpulse;
                }
            }

        cont:
            if (m_pAlternatePath && m_pAlternatePath->m_iPoints) {
                if (m_iAlternateNode > 1) {
                    vTmp = m_pAlternatePath->GetByNode(m_iAlternateNode, NULL);
                    VectorCopy(vTmp + 1, vCur);

                    vTmp = m_pAlternatePath->GetByNode(m_iAlternateNode - 1, NULL);
                    VectorCopy(vTmp + 1, vPrev);

                    VectorSubtract(vCur, vPrev, vTotal);
                    fTotal      = VectorLength(vTotal);
                    m_vIdealDir = vTotal;

                    VectorNormalize(m_vIdealDir);
                    angles.AngleVectorsLeft(&vDelta, NULL, NULL);
                    fCoef = ProjectLineOnPlane(vDelta, DotProduct(origin, vDelta), vPrev, vCur, NULL);

                    vTmp = m_pCurPath->GetByNode(m_iAlternateNode - (1.0 - fCoef), NULL);
                    // 2.30: bounce backward
                    if (m_bBounceBackwards) {
                        vTmp = m_pCurPath->Get(vTmp[0] - m_fLookAhead, NULL);
                    } else {
                        vTmp = m_pCurPath->Get(vTmp[0] + m_fLookAhead, NULL);
                    }
                    vAlternateWishPosition = vTmp + 1;

                    if (fCoef > 1) {
                        m_iAlternateNode++;
                    } else if (fCoef < 0) {
                        // 2.30: backward
                        m_iAlternateNode--;
                    }
                } else {
                    vTmp                   = m_pAlternatePath->GetByNode(m_iAlternateNode, NULL);
                    vAlternateWishPosition = vTmp + 1;
                    m_vIdealDir            = origin - vWishPosition;
                    m_vIdealDir[2]         = 0;
                    m_vIdealDir.normalize();

                    vDelta    = vAlternateWishPosition - origin;
                    vDelta[2] = 0;

                    if (vDelta.length() <= m_fIdealDistance) {
                        m_iAlternateNode++;
                    }
                }
            }

            vDelta = vWishPosition - origin;

            if (g_showvehiclemovedebug->integer) {
                G_DebugLine(vWishPosition, origin, 1, 0, 0, 1);
            }

            vDeltaSave = vDelta;
            vDelta.normalize();

            if (m_pAlternatePath && m_pAlternatePath->m_iPoints) {
                trace_t trace;

                trace = G_Trace(
                    origin, vec_zero, vec_zero, vWishPosition, this, edict->clipmask, qfalse, "Vehicle::AutoPilot"
                );
                if ((trace.fraction < 1 || trace.startsolid || trace.allsolid) && trace.ent
                    && trace.entityNum != ENTITYNUM_WORLD) {
                    vDelta     = vAlternateWishPosition - origin;
                    vDeltaSave = vDelta;
                    vDelta.normalize();
                }
            }

            vDelta[2]     = 0;
            vDeltaSave[2] = 0;

            if (fDistToCurPos > 1) {
                // Added 2.30: checks if it doesn't bounce backwards
                if (moveimpulse >= 0 && !m_bBounceBackwards) {
                    vectoangles(vDelta, vDeltaAngles);
                    turnimpulse = angledist(vDeltaAngles.y - angles.y);
                } else {
                    Vector vVec = vDelta * -1;

                    vectoangles(vVec, vDeltaAngles);
                    if (IsSubclassOfVehicleTank()) {
                        turnimpulse = angledist(vDeltaAngles.y - angles.y);
                    } else {
                        turnimpulse = -angledist(vDeltaAngles.y - angles.y);
                    }
                }
            }

            if (m_bStopEnabled) {
                moveimpulse = (fCurPathPosition - m_fStopStartDistance) / (m_fStopEndDistance - m_fStopStartDistance);
                moveimpulse *= moveimpulse;
                moveimpulse *= moveimpulse;
                moveimpulse *= moveimpulse;
                moveimpulse *= moveimpulse;
                moveimpulse = 1 - moveimpulse;
                moveimpulse *= m_fStopStartSpeed;

                if (fabs(moveimpulse) < 2) {
                    if (m_fIdealSpeed >= 0) {
                        moveimpulse = 2;
                    } else {
                        moveimpulse = -2;
                    }
                }
                // 2.30: check if it stays full speed when bouncing
            } else if (m_bBounceStayFullSpeed) {
                if (m_fIdealSpeed > fabs(moveimpulse)) {
                    moveimpulse -= m_fIdealAccel * level.frametime;
                    if (fabs(moveimpulse) > m_fIdealSpeed) {
                        moveimpulse = -m_fIdealSpeed;
                    }
                }
            } else if (moveimpulse < m_fIdealSpeed) {
                moveimpulse += m_fIdealAccel * level.frametime;
                if (moveimpulse > m_fIdealSpeed) {
                    moveimpulse = m_fIdealSpeed;
                }
            } else if (moveimpulse > m_fIdealSpeed) {
                moveimpulse -= m_fIdealAccel * level.frametime;
                if (moveimpulse < m_fIdealSpeed) {
                    moveimpulse = m_fIdealSpeed;
                }
            }

            if (m_iNextPathStartNode >= 0 && m_pNextPath && m_pNextPath->m_iPoints
                && m_iCurNode > m_iNextPathStartNode + 2) {
                cVehicleSpline *spline;

                spline      = m_pCurPath;
                m_pCurPath  = m_pNextPath;
                m_pNextPath = spline;

                m_iCurNode = 2;
                m_pNextPath->Reset();
                m_iNextPathStartNode = -1;

                // notify scripts that the drive has finished
                Unregister(STRING_DRIVE);

                m_bStopEnabled    = false;
                m_bEnableSkidding = false;
            }
            return;
        }

        vTmp           = m_pCurPath->GetByNode(m_iCurNode, NULL);
        vWishPosition  = vTmp + 1;
        m_vIdealDir    = origin - vWishPosition;
        m_vIdealDir[2] = 0;
        m_vIdealDir.normalize();

        vDelta    = vWishPosition - origin;
        vDelta[2] = 0;

        fDistToCurPos = 2;
        if (vDelta.length() > m_fIdealDistance) {
            goto cont;
        }

        fDistToCurPos = 0;

        m_iCurNode++;
    } while (m_iCurNode < m_pCurPath->m_iPoints);

    if (m_bStopEnabled) {
        moveimpulse = 0;
    }

    m_bAutoPilot      = false;
    m_bStopEnabled    = false;
    m_bEnableSkidding = false;

    StopLoopSound();
    // play the stop sound
    Sound(m_sSoundSet + "snd_stop", CHAN_VOICE);

    delete m_pCurPath;
    m_pCurPath  = NULL;
    m_iCurNode  = 0;
    turnimpulse = 0;

    //
    // notify scripts that driving has stopped
    //
    Unregister(STRING_DRIVE);
}

/*
====================
Vehicle::EventDriveInternal
====================
*/
void Vehicle::EventDriveInternal(Event *ev, bool wait)
{
    SimpleEntity *path;
    SimpleEntity *alternate_path = NULL;

    m_fIdealDistance = 100;
    m_fLookAhead     = 256;
    m_fIdealAccel    = 35;
    m_fIdealSpeed    = 250;
    m_fMaxSpeed      = 250; // Added in 2.30

    switch (ev->NumArgs()) {
    case 6:
        alternate_path = ev->GetSimpleEntity(6);
    case 5:
        m_fLookAhead = ev->GetFloat(5);
    case 4:
        m_fIdealDistance = ev->GetFloat(4);
    case 3:
        m_fIdealAccel = ev->GetFloat(3);
    case 2:
        m_fIdealSpeed = ev->GetFloat(2);
        m_fMaxSpeed   = m_fIdealSpeed; // Added in 2.30
    case 1:
        path = ev->GetSimpleEntity(1);
        if (!path) {
            ScriptError("Vehicle Given Drive Command with NULL path.");
            return;
        }
        break;
    default:
        ScriptError("wrong number of arguments");
    }

    if (!m_pCurPath) {
        m_pCurPath = new cVehicleSpline;
    }

    if (!m_pAlternatePath) {
        m_pAlternatePath = new cVehicleSpline;
    }

    SetupPath(m_pCurPath, path);

    // Setup the alternate path
    if (alternate_path) {
        SetupPath(m_pAlternatePath, alternate_path);
    }

    m_bAutoPilot     = true;
    m_iCurNode       = 0;
    m_iAlternateNode = 0;

    Sound(m_sSoundSet + "snd_start");
}

/*
====================
Vehicle::SetupPath
====================
*/
void Vehicle::SetupPath(cVehicleSpline *pPath, SimpleEntity *se)
{
    Vector        vLastOrigin;
    SimpleEntity *ent;
    float         fCurLength = 0.0f;
    int           i          = 1;

    if (!pPath) {
        return;
    }

    pPath->Reset();

    if (!se->Target().length() || !se->Target()[0]) {
        return;
    }

    vLastOrigin = se->origin;

    for (ent = se; ent != NULL; ent = ent->Next(), i++) {
        Vector vDelta = vLastOrigin - ent->origin;
        float  vTmp[4];

        if (vDelta.length() == 0 && i > 1) {
            Com_Printf("^~^~^Warning: Vehicle Driving with a Path that contains 2 equal points\n");
            continue;
        }

        fCurLength += vDelta.length();

        vTmp[0] = fCurLength;
        VectorCopy(ent->origin, vTmp + 1);

        if (ent->IsSubclassOfVehiclePoint()) {
            pPath->Add(vTmp, static_cast<VehiclePoint*>(ent)->spawnflags);
        } else {
            pPath->Add(vTmp, 0);
        }

        vLastOrigin = ent->origin;

        if (ent == se && i > 1) {
            break;
        }
    }
}

/*
====================
Vehicle::EventDrive
====================
*/
void Vehicle::EventDrive(Event *ev)
{
    EventDriveInternal(ev, true);
}

/*
====================
Vehicle::EventDriveNoWait
====================
*/
void Vehicle::EventDriveNoWait(Event *ev)
{
    EventDriveInternal(ev, false);
}

/*
====================
Vehicle::EventStop
====================
*/
void Vehicle::EventStop(Event *ev)
{
    m_bStopEnabled = false;
    m_bAutoPilot   = false;
    m_bIsSkidding  = false;
    moveimpulse    = 0;
    turnimpulse    = 0;
    m_iCurNode     = 0;
    Unregister(STRING_DRIVE);
}

/*
====================
Vehicle::EventFullStop
====================
*/
void Vehicle::EventFullStop(Event *ev)
{
    m_bAutoPilot   = false;
    m_bStopEnabled = false;
    m_bIsSkidding  = false;
    moveimpulse    = 0;
    turnimpulse    = 0;
    velocity       = vec_zero;
    m_iCurNode     = 0;
    Unregister(STRING_DRIVE);
}

/*
====================
Vehicle::VehicleDestroyed
====================
*/
void Vehicle::VehicleDestroyed(Event *ev)
{
    Entity      *ent;
    Entity      *targetEnt;
    Event       *event;
    const char  *name;
    VehicleBase *v;
    Vector       vDelta;

    takedamage = DAMAGE_NO;
    setSolidType(SOLID_NOT);
    hideModel();

    ent = ev->GetEntity(1);
    if (driver.ent) {
        EntityPtr driverPtr;

        velocity  = vec_zero;
        driverPtr = driver.ent;

        event = new Event(EV_Use);
        event->AddEntity(driverPtr);
        ProcessEvent(event);

        vDelta = driverPtr->origin - origin;
        vDelta[2] += 64;
        vDelta.normalize();

        // kill the driver
        driverPtr->Damage(this, this, driverPtr->health * 2, origin, vDelta, vec_zero, 50, 0, MOD_VEHICLE);
    }

    if (flags & FL_DIE_EXPLODE) {
        setSolidType(SOLID_NOT);
        hideModel();
        CreateExplosion(origin, edict->s.scale * 150, this, this, this, m_sExplosionModel);
    }

    if (flags & FL_DIE_GIBS) {
        setSolidType(SOLID_NOT);
        hideModel();
        CreateGibs(this, -150, edict->s.scale, 3, NULL);
    }

    //
    // remove all links
    //
    for (v = this; v->vlink; v = vlink) {
        v->vlink->PostEvent(EV_Remove, EV_VEHICLE);
    }

    //
    // remove all kill targets
    //
    name = KillTarget();
    if (name && strcmp(name, "")) {
        for (targetEnt = G_FindTarget(NULL, name); targetEnt; targetEnt = G_FindTarget(targetEnt, name)) {
            targetEnt->PostEvent(EV_Remove, EV_VEHICLE);
        }
    }

    //
    // activate targets
    //
    name = Target();
    if (name && strcmp(name, "")) {
        for (targetEnt = G_FindTarget(NULL, name); targetEnt; targetEnt = G_FindTarget(targetEnt, name)) {
            event = new Event(EV_Activate);
            event->AddEntity(ent);
            targetEnt->ProcessEvent(event);
        }
    }

    PostEvent(EV_Remove, EV_VEHICLE);
}

/*
====================
Vehicle::DetachRemoteOwner
====================
*/
void Vehicle::DetachRemoteOwner()
{
    VehicleTurretGun *vtg = static_cast<VehicleTurretGun *>(Turrets[0].ent.Pointer());
    vtg->EndRemoteControl();
}

static float GetAngleBetweenVectors2D(const Vector& a, const Vector& b)
{
    float value;
    float angle;

    value = atan2f(a.x, a.y);
    angle = RAD2DEG(value - atan2f(b.x, b.y));
    if (fabs(angle) <= 180) {
        return angle;
    } else if (angle < 0) {
        return angle + 360;
    } else {
        return angle - 360;
    }
}

/*
====================
Vehicle::DriverUse
====================
*/
void Vehicle::DriverUse(Event *ev)
{
    int     slot;
    Vector  pos;
    Vector  dist;
    float   min_length = 1e30f;
    int     min_slot   = 0;
    int     min_type   = -1;
    Entity *ent;
    Vector  vExitAngles;
    bool    bHasExitAngles;
    Vector  vExitPosition;
    bool    bHasExitPosition;

    ent = ev->GetEntity(1);

    bHasExitAngles   = false;
    bHasExitPosition = false;

    if (locked) {
        return;
    }

    // Added in 2.30
    //  Check use angle
    if (!driver.ent && m_fMaxUseAngle && ent && Turrets[0].ent) {
        Vector vForward;
        Vector vDir;

        AngleVectors(Turrets[0].ent->angles, vForward, NULL, NULL);

        vDir = Turrets[0].ent->origin - ent->origin;
        VectorNormalize(vDir);

        if (fabs(GetAngleBetweenVectors2D(vForward, vDir)) > m_fMaxUseAngle) {
            // Not usable
            return;
        }
    }

    if (ev->NumArgs() == 2) {
        if (ev->IsVectorAt(2)) {
            vExitPosition    = ev->GetVector(2);
            bHasExitPosition = true;
        } else if (ev->IsEntityAt(2)) {
            Entity *pEnt = ev->GetEntity(2);

            bHasExitAngles   = true;
            vExitAngles      = pEnt->angles;
            vExitPosition    = pEnt->origin;
            bHasExitPosition = true;
        } else if (ev->IsSimpleEntityAt(2)) {
            SimpleEntity *pEnt = ev->GetSimpleEntity(2);

            bHasExitAngles   = true;
            vExitAngles      = pEnt->angles;
            vExitPosition    = pEnt->origin;
            bHasExitPosition = true;
        }
    }

    slot = FindDriverSlotByEntity(ent);

    if (slot >= 0) {
        DetachDriverSlot(slot, vec_zero, NULL);

        if (IsSubclassOfVehicleTank()) {
            // Added check to see if the turret is valid in OPM
            if (Turrets[0].ent && Turrets[0].ent->IsSubclassOfVehicleTurretGun()) {
                DetachRemoteOwner();
            }
        }

        return;
    }

    slot = FindPassengerSlotByEntity(ent);

    if (slot >= 0) {
        DetachPassengerSlot(slot, vec_zero, NULL);
        return;
    }

    if (ent->IsSubclassOfWeapon()) {
        slot = FindTurretSlotByEntity(ent);

        if (slot >= 0) {
            DetachTurretSlot(slot, vec_zero, NULL);
        }
    } else if (ent->IsSubclassOfSentient()) {
        Sentient  *sent       = static_cast<Sentient *>(ent);
        TurretGun *sentTurret = sent->GetTurret();

        if (sentTurret) {
            slot = FindTurretSlotByEntity(sent->GetTurret());

            if (slot >= 0) {
                if (!bHasExitPosition) {
                    AttachTurretSlot(slot, sent, vec_zero, NULL);
                } else if (!bHasExitAngles) {
                    AttachTurretSlot(slot, sent, vExitPosition, NULL);
                } else {
                    AttachTurretSlot(slot, sent, vExitPosition, &vExitAngles);
                }

                sent->SetVehicle(NULL);
                return;
            }
        }
    }

    // Check for passengers slots
    for (slot = 0; slot < MAX_PASSENGERS; slot++) {
        if (!(Passengers[slot].flags & SLOT_FREE)) {
            continue;
        }

        QueryPassengerSlotPosition(slot, (float *)&pos);

        dist = pos - ent->origin;

        if (dist.length() < min_length) {
            min_length = dist.length();
            min_type   = 0;
            min_slot   = slot;
        }
    }

    // Check for turrets slots
    if (ent->IsSubclassOfWeapon()) {
        for (slot = 0; slot < MAX_TURRETS; slot++) {
            if (!(Turrets[slot].flags & SLOT_FREE)) {
                continue;
            }

            QueryTurretSlotPosition(slot, (float *)&pos);

            dist = pos - ent->origin;

            if (dist.length() < min_length) {
                min_length = dist.length();
                min_type   = 1;
                min_slot   = slot;
            }
        }
    } else {
        for (slot = 0; slot < MAX_TURRETS; slot++) {
            if (!(Turrets[slot].flags & SLOT_BUSY)) {
                continue;
            }

            QueryTurretSlotPosition(slot, (float *)&pos);

            dist = pos - ent->origin;

            if (dist.length() < min_length) {
                min_length = dist.length();
                min_type   = 1;
                min_slot   = slot;
            }
        }
    }

    slot = 0;

    // Check for driver(s) slot(s)
    if (driver.flags & SLOT_FREE) {
        float length;

        QueryDriverSlotPosition(slot, (float *)&pos);

        dist = pos - ent->origin;

        length = dist.length();
        if (ent->IsSubclassOfPlayer()) {
            length /= 2.0;
        }

        if (length < min_length) {
            min_length = dist.length();
            min_type   = 2;
            min_slot   = slot;
        }
    }

    if (g_gametype->integer == GT_SINGLE_PLAYER || min_type == 2) {
        switch (min_type) {
        case 0:
            AttachPassengerSlot(min_slot, ent, vec_zero);
            break;
        case 1:
            AttachTurretSlot(min_slot, ent, vec_zero, NULL);
            break;
        case 2:
            AttachDriverSlot(min_slot, ent, vec_zero);
            break;
        }
    }
}

/*
====================
Vehicle::SetMoveInfo
====================
*/
void Vehicle::SetMoveInfo(vmove_t *vm)
{
    memset(vm, 0, sizeof(vmove_t));

    origin.copyTo(vs.origin);
    vs.useGravity = false;
    vs.entityNum  = entnum;

    vm->vs        = &vs;
    vm->frametime = level.frametime;
    vm->tracemask = edict->clipmask;
    mins.copyTo(vm->mins);
    maxs.copyTo(vm->maxs);

    vs.entityNum      = edict->s.number;
    vs.desired_dir[0] = velocity[0];
    vs.desired_dir[1] = velocity[1];

    vm->desired_speed = VectorNormalize2D(vs.desired_dir);
}

/*
====================
Vehicle::GetMoveInfo
====================
*/
void Vehicle::GetMoveInfo(vmove_t *vm)
{
    Vector newOrigin = vm->vs->origin;

    if (bindmaster) {
        newOrigin = vm->vs->origin - origin;
    }

    setLocalOrigin(newOrigin);

    groundentity = NULL;
    if (vm->vs->groundEntityNum != ENTITYNUM_NONE) {
        groundentity = &g_entities[vm->vs->groundEntityNum];
    }
}

/*
====================
Vehicle::SetCEMoveInfo
====================
*/
void Vehicle::SetCEMoveInfo(vmove_t *vm)
{
    Vector mins, maxs;

    SetMoveInfo(vm);

    mins = m_pCollisionEntity->mins;
    maxs = m_pCollisionEntity->maxs;
    mins -= Vector(24, 24, 0);
    maxs += Vector(24, 24, 0);

    mins.copyTo(vm->mins);
    maxs.copyTo(vm->maxs);
}

/*
====================
Vehicle::GetCEMoveInfo
====================
*/
void Vehicle::GetCEMoveInfo(vmove_t *vm)
{
    GetMoveInfo(vm);
}

/*
====================
Vehicle::SetViewAngles
====================
*/
void Vehicle::SetViewAngles(Vector newViewangles)
{
    client->ps.delta_angles[0] = floor(ANGLE2SHORT(newViewangles.x));
    client->ps.delta_angles[1] = floor(ANGLE2SHORT(newViewangles.y));
    client->ps.delta_angles[2] = floor(ANGLE2SHORT(newViewangles.z));

    v_angle = newViewangles;
    AnglesToAxis(v_angle, orientation);

    yaw_forward = orientation[0];
    yaw_left    = orientation[1];
}

/*
====================
Vehicle::SetSlotsNonSolid
====================
*/
void Vehicle::SetSlotsNonSolid(void)
{
    for (int i = 0; i < MAX_PASSENGERS; i++) {
        Passengers[i].NotSolid();
    }

    for (int i = 0; i < MAX_TURRETS; i++) {
        Turrets[i].NotSolid();
    }

    driver.NotSolid();

    if (m_pCollisionEntity) {
        m_pCollisionEntity->NotSolid();
    }
}

/*
====================
Vehicle::SetSlotsSolid
====================
*/
void Vehicle::SetSlotsSolid(void)
{
    for (int i = 0; i < MAX_PASSENGERS; i++) {
        Passengers[i].Solid();
    }

    for (int i = 0; i < MAX_TURRETS; i++) {
        Turrets[i].Solid();
    }

    driver.Solid();

    if (m_pCollisionEntity) {
        m_pCollisionEntity->Solid();
    }
}

/*
====================
Vehicle::AssertMove
====================
*/
bool Vehicle::AssertMove(Vector vNewOrigin, Vector vOldOrigin)
{
    Entity    *check;
    gentity_t *edict;
    int        touch[MAX_GENTITIES];
    int        i;
    int        num;

    if (m_pCollisionEntity) {
        num = gi.AreaEntities(m_pCollisionEntity->mins, m_pCollisionEntity->maxs, touch, MAX_GENTITIES);
    } else {
        num = gi.AreaEntities(mins, maxs, touch, MAX_GENTITIES);
    }

    if (num <= 0) {
        return true;
    }

    for (i = 0; i < num; i++) {
        edict = &g_entities[touch[i]];
        check = edict->entity;

        if (check->edict->s.number != edict->s.number && edict->solid && check->movetype != MOVETYPE_STOP) {
            if (check->movetype != MOVETYPE_NONE && check->movetype != MOVETYPE_NOCLIP
                && edict->r.contents != CONTENTS_PLAYERCLIP && IsTouching(check)
                && G_TestEntityPosition(check, check->origin)) {
                return false;
            }
        }
    }

    return true;
}

/*
====================
Vehicle::AssertRotation
====================
*/
bool Vehicle::AssertRotation(Vector vNewAngles, Vector vOldAngles)
{
    Vector i;
    Vector j;
    Vector k;
    Vector i2;
    Vector j2;
    Vector k2;
    Vector vAngleDiff;
    float  mAngleDiff[3][3];

    AngleVectorsLeft(vOldAngles, i, j, k);
    AngleVectorsLeft(vNewAngles, i2, j2, k2);
    AnglesSubtract(vOldAngles, vNewAngles, vAngleDiff);
    AngleVectorsLeft(vAngleDiff, mAngleDiff[0], mAngleDiff[1], mAngleDiff[2]);

    // FIXME: shrug

    return true;
}

/*
====================
Vehicle::NoMove
====================
*/
void Vehicle::NoMove(void)
{
    vmove_t vm;

    SetMoveInfo(&vm);
    VectorClear2D(vs.desired_dir);
    VmoveSingle(&vm);
    GetMoveInfo(&vm);
}

/*
====================
Vehicle::SlidePush
====================
*/
void Vehicle::SlidePush(Vector vPush)
{
    vmove_t    vm;
    int        i;
    int        j;
    Entity    *pSkippedEntities[MAX_SKIPPED_ENTITIES];
    int        iContentsEntities[MAX_SKIPPED_ENTITIES];
    solid_t    solidEntities[MAX_SKIPPED_ENTITIES];
    int        iNumSkippedEntities = 0;
    int        iNumSkipped         = 0;
    gentity_t *other;
    Vector     newOrigin;

    do {
        SetMoveInfo(&vm);

        vPush.copyTo(vm.vs->velocity);
        vm.frametime = 1;

        vs.desired_dir[0] = vPush[0];
        vs.desired_dir[1] = vPush[1];
        vm.desired_speed  = VectorNormalize2D(vs.desired_dir);

        if (g_showvehiclemovedebug->integer) {
            G_DebugBBox(origin, vm.mins, vm.maxs, 1, 0, 0, 1);
            G_DebugBBox(origin, vm.mins, vm.maxs, 0, 1, 0, 1);
        }

        VmoveSingle(&vm);

        iNumSkippedEntities = 0;

        for (i = 0; i < vm.numtouch; i++) {
            other = &g_entities[vm.touchents[i]];

            for (j = 0; j < i; j++) {
                if (&g_entities[j] == other) {
                    break;
                }
            }

            if (j == i && other->entity) {
                other->entity->CheckGround();

                if (other->entity->groundentity && (other->entity->groundentity == edict
                    || m_pCollisionEntity && other->entity->groundentity->entity == m_pCollisionEntity)) {
                    // save the entity
                    pSkippedEntities[iNumSkipped] = other->entity;
                    iContentsEntities[iNumSkipped] = other->r.contents;
                    solidEntities[iNumSkipped] = other->solid;
                    iNumSkipped++;

                    if (iNumSkipped >= MAX_SKIPPED_ENTITIES) {
                        gi.Error(ERR_DROP, "MAX_SKIPPED_ENTITIES hit in VehicleMove.\n");
                        return;
                    }

                    other->entity->setSolidType(SOLID_NOT);
                    iNumSkippedEntities++;
                }

                if (g_showvehiclemovedebug->integer) {
                    Com_Printf(
                        "Vehicle Hit(SP): %s : %s\n", other->entity->getClassname(), other->entity->targetname.c_str()
                    );
                }
            }
        }
    } while (iNumSkippedEntities != 0);

    newOrigin = vm.vs->origin;
    if (bindmaster) {
        newOrigin -= bindmaster->origin;
    }

    setLocalOrigin(newOrigin);

    if (g_showvehiclemovedebug->integer) {
        G_DebugBBox(origin, vm.mins, vm.maxs, 0, 0, 1, 1);
    }

    TouchStuff(&vm);

    for (i = 0; i < iNumSkipped; i++) {
        pSkippedEntities[i]->setSolidType(solidEntities[i]);
        pSkippedEntities[i]->setContents(iContentsEntities[i]);
    }
}

/*
====================
Vehicle::AnimMoveVehicle
====================
*/
void Vehicle::AnimMoveVehicle(void)
{
    Vector  vPosition;
    Vector  vAngles;
    Entity *ent;

    //
    // velocity
    //
    vPosition = origin + frame_delta;
    velocity  = (vPosition - origin) * (1.0 / level.frametime);

    setOrigin(vPosition);

    //
    // angular velocity
    //
    vAngles = angles;
    vAngles[1] += angular_delta;
    avelocity = (vAngles - angles) * (1.0 / level.frametime);

    setAngles(vAngles);

    for (ent = teamchain; ent; ent = ent->teamchain) {
        ent->setLocalOrigin(ent->localorigin);
        ent->setAngles(ent->localangles);
    }
}

/*
====================
Vehicle::MoveVehicle
====================
*/
void Vehicle::MoveVehicle(void)
{
    trace_t      tr;
    Vector       vecStart;
    Vector       vecStart2;
    Vector       vecEnd;
    Vector       vecDelta;
    Vector       vecAng;
    int          i;
    gridpoint_t *gp;
    vmove_t      vm;
    float        flMoveFrac = 1;
    float        fSpeed;
    bool         bDoPush    = true;
    bool         bDoGravity = false;
    bool         bHitPerson = false;
    Entity      *pSkippedEntities[MAX_SKIPPED_ENTITIES];
    int          iContentsEntities[MAX_SKIPPED_ENTITIES];
    solid_t      solidEntities[MAX_SKIPPED_ENTITIES];
    int          iNumSkippedEntities = 0;
    Event       *event               = nullptr;
    Entity      *chain;
    Entity      *driverEnt = NULL;
    solid_t     solidDriver = SOLID_NOT;
    int         contentsDriver = 0;

    if (m_bMovementLocked) {
        return;
    }

    setAngles();
    if (m_pCollisionEntity) {
        SetCEMoveInfo(&vm);
    } else {
        SetMoveInfo(&vm);
    }
    m_sMoveGrid->SetMoveInfo(&vm);
    m_sMoveGrid->SetOrientation(orientation);
    m_sMoveGrid->CalculateBoxPoints();
    CheckGround();

    if (velocity.length() > 0.5f) {
        Vector vVel;

        if (driver.ent && driver.ent->edict->solid != SOLID_NOT) {
            //
            // Added in OPM
            //  Make the driver nonsolid while moving.
            //  Scripts usually set the player to nonsolid.
            //  However some scripts still rely on the old 1.11 behavior
            //  where the player is set to nonsolid when driving some type of vehicles
            solidDriver = driver.ent->edict->solid;
            contentsDriver = driver.ent->getContents();

            driver.ent->setSolidType(SOLID_NOT);
        }

        fSpeed   = orientation[0] * velocity;
        vecDelta = velocity * level.frametime;

        vVel        = velocity * level.frametime * 8;
        vecDelta[2] = 0;

        for (i = 0; i < 3 && bDoPush; i++) {
            bool bContinue = false;

            if (fSpeed > 0.0f) {
                gp = m_sMoveGrid->GetGridPoint(2, i, 0);
            } else {
                gp = m_sMoveGrid->GetGridPoint(0, i, 0);
            }
            vecStart = origin + gp->origin;

            if (real_velocity.length() > 0.5f) {
                vecEnd = vecStart + vVel;

                for (;;) {
                    tr = G_Trace(
                        vecEnd,
                        Vector(gp->vm.mins) - Vector(-32, -32, -32),
                        Vector(gp->vm.maxs) + Vector(32, 32, 32),
                        vecEnd,
                        this,
                        MASK_MOVEVEHICLE,
                        false,
                        "Vehicle::MoveVehicle"
                    );

                    if (!tr.ent || !tr.ent->entity || tr.ent->entity == world) {
                        break;
                    }

                    tr.ent->entity->CheckGround();

                    if (!tr.ent->entity->groundentity
                        || (tr.ent->entity->groundentity != edict
                            && (!m_pCollisionEntity || tr.ent->entity->groundentity->entity != m_pCollisionEntity))) {
                        Event *event = new Event(EV_Touch);
                        event->AddEntity(this);
                        tr.ent->entity->ProcessEvent(event);

                        event = new Event(EV_Touch);
                        event->AddEntity(tr.ent->entity);
                        ProcessEvent(event);

                        if (tr.ent->entity->IsSubclassOfSentient()) {
                            bHitPerson = true;
                        }

                        if (g_showvehiclemovedebug->integer) {
                            Com_Printf(
                                "Vehicle Hit(MV0): %s : %s\n",
                                tr.ent->entity->getClassname(),
                                tr.ent->entity->targetname.c_str()
                            );
                        }

                        // Added in OPM
                        //  Skip the entity if the sentient is dead.
                        //  This is a workaround against sentients getting in path of vehicles
                        //  and thus blocking them temporarily while dying.
                        //  For example in e1l1, the first tank would get stuck, because sometimes
                        //  there are too many actors moving in the path of the vehicle
                        if (!bHitPerson || !tr.ent->entity->IsDead()) {
                            break;
                        }
                    }

                    pSkippedEntities[iNumSkippedEntities]  = tr.ent->entity;
                    iContentsEntities[iNumSkippedEntities] = tr.ent->r.contents;
                    solidEntities[iNumSkippedEntities]     = tr.ent->solid;
                    iNumSkippedEntities++;

                    if (iNumSkippedEntities >= MAX_SKIPPED_ENTITIES) {
                        gi.Error(ERR_DROP, "MAX_SKIPPED_ENTITIES hit in VehicleMove.\n");
                        return;
                    }

                    tr.ent->entity->setSolidType(SOLID_NOT);

                    if (g_showvehiclemovedebug->integer) {
                        Com_Printf(
                            "Vehicle Skipped(MV0): %s : %s\n",
                            tr.ent->entity->getClassname(),
                            tr.ent->entity->targetname.c_str()
                        );
                    }
                }
            }

            if (g_showvehiclemovedebug->integer) {
                G_DebugBBox(vecStart, gp->vm.mins, gp->vm.maxs, 1, 0, 0, 1);
            }

            vecStart.z += 64;
            if (g_showvehiclemovedebug->integer) {
                G_DebugBBox(vecStart, gp->vm.mins, gp->vm.maxs, 1, 0, 0, 1);
            }

            vecEnd = vecStart + vecDelta;

            for (;;) {
                tr = G_Trace(
                    vecStart,
                    Vector(gp->vm.mins),
                    Vector(gp->vm.maxs),
                    vecEnd,
                    this,
                    edict->clipmask,
                    false,
                    "Vehicle::MoveVehicle"
                );

                if (tr.fraction == 1.0f && !tr.allsolid && !tr.startsolid) {
                    bContinue = true;
                    break;
                }

                if (!tr.ent || !tr.ent->entity || tr.ent->entity == world) {
                    break;
                }

                tr.ent->entity->CheckGround();

                if (!tr.ent->entity->groundentity
                    || (tr.ent->entity->groundentity != edict
                        && (!m_pCollisionEntity || tr.ent->entity->groundentity->entity != m_pCollisionEntity))) {
                    Event *event = new Event(EV_Touch);
                    event->AddEntity(this);
                    tr.ent->entity->ProcessEvent(event);

                    event = new Event(EV_Touch);
                    event->AddEntity(tr.ent->entity);
                    ProcessEvent(event);

                    if (tr.ent->entity->IsSubclassOfSentient()) {
                        bHitPerson = true;
                    }

                    if (g_showvehiclemovedebug->integer) {
                        Com_Printf(
                            "Vehicle Hit(MV): %s : %s\n",
                            tr.ent->entity->getClassname(),
                            tr.ent->entity->TargetName().c_str()
                        );
                    }

                    // Added in OPM
                    //  (see the comment above)
                    if (!bHitPerson || !tr.ent->entity->IsDead()) {
                        break;
                    }
                }

                pSkippedEntities[iNumSkippedEntities]  = tr.ent->entity;
                iContentsEntities[iNumSkippedEntities] = tr.ent->r.contents;
                solidEntities[iNumSkippedEntities]     = tr.ent->solid;
                iNumSkippedEntities++;

                if (iNumSkippedEntities >= MAX_SKIPPED_ENTITIES) {
                    gi.Error(ERR_DROP, "MAX_SKIPPED_ENTITIES hit in VehicleMove.\n");
                    return;
                }

                tr.ent->entity->setSolidType(SOLID_NOT);

                if (g_showvehiclemovedebug->integer) {
                    Com_Printf(
                        "Vehicle Skipped(MV): %s : %s\n",
                        tr.ent->entity->getClassname(),
                        tr.ent->entity->TargetName().c_str()
                    );
                }
            }

            if (bContinue) {
                continue;
            }

            if (flMoveFrac > tr.fraction) {
                flMoveFrac = tr.fraction - 0.1f;
            }

            vecStart = tr.endpos;
            vecEnd   = vecStart;
            vecEnd.z -= 64;

            if (g_showvehiclemovedebug->integer) {
                G_DebugBBox(tr.endpos, gp->vm.mins, gp->vm.maxs, 0, 1, 0, 1);
            }

            for (;;) {
                tr = G_Trace(
                    vecStart,
                    Vector(gp->vm.mins),
                    Vector(gp->vm.maxs),
                    vecEnd,
                    this,
                    edict->clipmask,
                    false,
                    "Vehicle::MoveVehicle"
                );

                if ((tr.fraction != 1.0f && tr.plane.normal[2] < 0.7f) || tr.allsolid) {
                    flMoveFrac = 0.0f;
                }

                if (!tr.ent || !tr.ent->entity || tr.ent->entity == world) {
                    break;
                }

                tr.ent->entity->CheckGround();

                if (!tr.ent->entity->groundentity
                    || (tr.ent->entity->groundentity != edict
                        && (!m_pCollisionEntity || tr.ent->entity->groundentity->entity != m_pCollisionEntity))) {
                    Event *event = new Event(EV_Touch);
                    event->AddEntity(this);
                    tr.ent->entity->ProcessEvent(event);

                    event = new Event(EV_Touch);
                    event->AddEntity(tr.ent->entity);
                    ProcessEvent(event);

                    if (tr.ent->entity->IsSubclassOfSentient()) {
                        bHitPerson = true;
                    }

                    if (g_showvehiclemovedebug->integer) {
                        Com_Printf(
                            "Vehicle Hit(MV2): %s : %s\n",
                            tr.ent->entity->getClassname(),
                            tr.ent->entity->TargetName().c_str()
                        );
                    }

                    // Added in OPM
                    //  (see the comment above)
                    if (!bHitPerson || !tr.ent->entity->IsDead()) {
                        break;
                    }
                }

                pSkippedEntities[iNumSkippedEntities]  = tr.ent->entity;
                iContentsEntities[iNumSkippedEntities] = tr.ent->r.contents;
                solidEntities[iNumSkippedEntities]     = tr.ent->solid;
                iNumSkippedEntities++;

                if (iNumSkippedEntities >= MAX_SKIPPED_ENTITIES) {
                    gi.Error(ERR_DROP, "MAX_SKIPPED_ENTITIES hit in VehicleMove.\n");
                    return;
                }

                tr.ent->entity->setSolidType(SOLID_NOT);
                if (g_showvehiclemovedebug->integer) {
                    Com_Printf(
                        "Vehicle Skipped(MV2): %s : %s\n",
                        tr.ent->entity->getClassname(),
                        tr.ent->entity->targetname.c_str()
                    );
                }
            }

            if (g_showvehiclemovedebug->integer) {
                G_DebugBBox(tr.endpos, gp->vm.mins, gp->vm.maxs, 0, 0, 1, 1);
            }

            if ((!tr.ent || !tr.ent->entity->IsSubclassOfProjectile()) && driver.ent
                && driver.ent->IsSubclassOfPlayer()) {
                if (fSpeed > 0) {
                    if (i) {
                        if (i == 2 && turnimpulse >= 0.0f) {
                            turnimpulse -= level.frametime * 800;
                        }
                    } else if (turnimpulse <= 0) {
                        turnimpulse += level.frametime * 800;
                    }
                } else {
                    if (i) {
                        if (i == 2 && turnimpulse <= 0.0f) {
                            turnimpulse += level.frametime * 800;
                        }
                    } else if (turnimpulse >= 0) {
                        turnimpulse -= level.frametime * 800;
                    }
                }
            }

            if (flMoveFrac < 0.1f) {
                bDoPush = false;
            }
        }

        if (bDoPush) {
            SlidePush(Vector(0, 0, 64));
            SlidePush(vecDelta * flMoveFrac);
            SlidePush(Vector(0, 0, -128));

            bDoGravity = true;
            if (!bHitPerson) {
                velocity *= flMoveFrac;
            }
        } else if (!bHitPerson) {
            velocity *= 0.5;
        }

        if (solidDriver != SOLID_NOT) {
            driver.ent->setSolidType(solidDriver);
            driver.ent->setContents(contentsDriver);
        }
    }

    if (bDoGravity) {
        velocity[2] -= sv_gravity->value * level.frametime;

        tr = G_Trace(
            origin,
            mins,
            maxs,
            origin + Vector(0, 0, velocity[2] * level.frametime),
            this,
            edict->clipmask,
            qtrue,
            "Vehicle::MoveVehicle"
        );

        if (tr.fraction < 1) {
            velocity[2] = 0;
        }

        setOrigin(tr.endpos);
    }

    for (i = 0; i < iNumSkippedEntities; i++) {
        pSkippedEntities[i]->setSolidType(solidEntities[i]);
        pSkippedEntities[i]->setContents(iContentsEntities[i]);
    }

    for (chain = teamchain; chain; chain = chain->teamchain) {
        chain->setLocalOrigin(chain->localorigin);
        chain->setAngles(chain->localangles);
    }
}

/*
====================
Vehicle::Think
====================
*/
void Vehicle::Think(void)
{
    flags |= FL_POSTTHINK;
}

/*
====================
Vehicle::Postthink
====================
*/
void Vehicle::Postthink(void)
{
    float         turn;
    Vector        i;
    Vector        j;
    Vector        k;
    Vector        temp;
    Vector        roll;
    Vector        acceleration;
    Vector        atmp;
    Vector        atmp2;
    Vector        aup;
    VehicleBase  *v;
    VehicleBase  *last;
    float         drivespeed;
    float         dot;
    Vector        primal_angles = angles;
    Vector        primal_origin = origin;
    Vector        vTmp;
    Vector        vAddedAngles;
    Vector        n_angles;
    orientation_t orient;

    if (!g_vehicle->integer || (edict->s.renderfx & RF_DONTDRAW)) {
        return;
    }

    if (m_pCollisionEntity) {
        setSolidType(SOLID_NOT);
        m_pCollisionEntity->Solid();
    }

    prev_velocity = velocity;

    if (m_vOldMins != vec_zero && m_vOldMaxs != vec_zero) {
        mins = m_vOldMins;
        maxs = m_vOldMaxs;
    } else if (mins != vec_zero || maxs != vec_zero) {
        m_vOldMins = mins;
        m_vOldMaxs = maxs;
    }

    SetSlotsNonSolid();
    if (m_bAnimMove) { // 2.0: use vehicle's animation to move
        AnimMoveVehicle();
    } else {
        if (!m_bMovementLocked) {
            FactorOutAnglesOffset();
            FactorOutOriginOffset();
        }
        MoveVehicle();
    }

    SetSlotsSolid();

    m_bThinkCalled = true;

    if (m_bAnimMove) {
        moveimpulse = velocity.length() * level.frametime;
        turnimpulse = avelocity[1] * level.frametime;
    } else if (m_bAutoPilot) {
        AutoPilot();
    } else {
        if (!driver.ent || !driver.ent->IsSubclassOfPlayer()) {
            acceleration = velocity;

            if (acceleration.length() < 0.1f) {
                velocity = vec_zero;
            }
        }

        moveimpulse *= 0.825f;
        turnimpulse *= 0.825f;
    }

    currentspeed = moveimpulse;
    turnangle    = turnangle * 0.25f + turnimpulse;
    turnangle    = Q_clamp_float(turnangle, -maxturnrate, maxturnrate);

    if (level.inttime <= 1200) {
        prev_origin = origin;
    } else {
        real_velocity     = origin - prev_origin;
        prev_origin       = origin;
        prev_acceleration = real_acceleration;
        real_acceleration = real_velocity - prev_velocity;
        prev_velocity     = real_velocity;
        acceleration      = real_acceleration - prev_acceleration;
    }

    UpdateSkidAngle();
    UpdateBones();
    UpdateShaderOffset();
    UpdateTires();
    UpdateNormals();

    angles.AngleVectorsLeft(&i, &j, &k);

    turn = level.frametime * turnangle;

    velocity[0] *= 0.925f;
    velocity[1] *= 0.925f;

    velocity = Vector(orientation[0]) * currentspeed;
    temp     = orientation[0];
    temp.z   = 0;

    drivespeed = velocity * temp;
    drivespeed = Q_clamp_float(drivespeed, -speed, speed);
    n_angles   = temp * drivespeed;

    velocity   = n_angles;
    velocity.z = drivespeed * jumpimpulse;
    avelocity *= 0.05f;

    if (steerinplace && drivespeed < 350) {
        drivespeed = 350;
    }

    avelocity.y += turn * drivespeed;
    angles += avelocity * level.frametime;

    dot = acceleration * orientation[0];

    UpdateSound();

    i = orientation[0];
    j = orientation[1];
    k = orientation[2];

    CalculateAnglesOffset(acceleration);
    CalculateOriginOffset();

    last_origin = origin;
    vTmp        = (angles - primal_angles) * level.frametime;

    if (vTmp.x > 180 || vTmp.x < -180) {
        vTmp.x = 0;
    }
    if (vTmp.y > 180 || vTmp.y < -180) {
        vTmp.y = 0;
    }
    if (vTmp.z > 180 || vTmp.z < -180) {
        vTmp.z = 0;
    }

    if (vTmp.x > -1 || vTmp.x < 1) {
        vTmp.x = 0;
    }
    if (vTmp.y > -1 || vTmp.y < 1) {
        vTmp.y = 0;
    }
    if (vTmp.z > -1 || vTmp.z < 1) {
        vTmp.z = 0;
    }

    avelocity = vAddedAngles;

    if (!m_bAnimMove && !m_bMovementLocked) {
        FactorInOriginOffset();
        FactorInAnglesOffset(&vAddedAngles);
    }

    n_angles = m_vOriginOffset + m_vOriginOffset2;
    CalculateAnimationData(vAddedAngles, m_vOriginOffset + m_vOriginOffset2);

    if (m_pCollisionEntity) {
        Vector vaDelta;

        SetSlotsNonSolid();

        vaDelta[0] = angledist(angles[0] - m_pCollisionEntity->angles[0]);
        vaDelta[1] = angledist(angles[1] - m_pCollisionEntity->angles[1]);
        vaDelta[2] = angledist(angles[2] - m_pCollisionEntity->angles[2]);

        m_pCollisionEntity->Solid();

        G_PushMove(m_pCollisionEntity, origin - primal_origin, vaDelta);
        G_TouchTriggers(m_pCollisionEntity);

        SetSlotsSolid();

        m_pCollisionEntity->setOrigin(origin);
        m_pCollisionEntity->setAngles(angles);
    }

    SetupVehicleSoundEntities();
    UpdateDriverSlot(0);

    for (int slot = 0; slot < MAX_PASSENGERS; slot++) {
        UpdatePassengerSlot(slot);
    }

    atmp = angles - primal_angles;

    for (int slot = 0; slot < MAX_TURRETS; slot++) {
        UpdateTurretSlot(slot);
    }

    if (g_showvehicleentrypoints->integer) {
        for (int slot = 0; slot < MAX_PASSENGERS; slot++) {
            if (Passengers[slot].enter_boneindex >= 0) {
                QueryPassengerSlotPosition(slot, (float *)&temp);

                G_DebugCircle(temp, 10, 1, 0, 0, 1, true);
            }
        }

        for (int slot = 0; slot < MAX_TURRETS; slot++) {
            if (Turrets[slot].enter_boneindex >= 0) {
                QueryTurretSlotPosition(slot, (float *)&temp);

                G_DebugCircle(temp, 10, 0, 1, 0, 1, true);
            }
        }

        if (driver.enter_boneindex >= 0) {
            QueryDriverSlotPosition(0, (float *)&temp);

            G_DebugCircle(temp, 10, 0, 0, 1, 1, true);
        }
    }

    if (g_showvehicleslotpoints->integer) {
        for (int slot = 0; slot < MAX_PASSENGERS; slot++) {
            if (Passengers[slot].boneindex >= 0) {
                GetTagPositionAndOrientation(Passengers[slot].boneindex, &orient);
                G_DebugCircle(orient.origin, 10.0, 1.0f, 0.5f, 0.5f, 1.0f, true);

                G_DebugLine(orient.origin, orient.origin + Vector(orient.axis[0]) * 32, 1, 0, 0, 1);
                G_DebugLine(orient.origin, orient.origin + Vector(orient.axis[1]) * 32, 0, 1, 0, 1);
                G_DebugLine(orient.origin, orient.origin + Vector(orient.axis[2]) * 32, 0, 0, 1, 1);
            }
        }

        for (int slot = 0; slot < MAX_TURRETS; slot++) {
            if (Turrets[slot].boneindex >= 0) {
                GetTagPositionAndOrientation(Turrets[slot].boneindex, &orient);
                G_DebugCircle(orient.origin, 10.0, 0.5f, 1.0f, 1.0f, 1.0f, true);

                G_DebugLine(orient.origin, orient.origin + Vector(orient.axis[0]) * 32, 1, 0, 0, 1);
                G_DebugLine(orient.origin, orient.origin + Vector(orient.axis[1]) * 32, 0, 1, 0, 1);
                G_DebugLine(orient.origin, orient.origin + Vector(orient.axis[2]) * 32, 0, 0, 1, 1);
            }
        }

        if (driver.boneindex >= 0) {
            GetTagPositionAndOrientation(driver.boneindex, &orient);
            G_DebugCircle(orient.origin, 10.0, 0.5f, 0.5f, 1.0f, 1.0f, true);

            G_DebugLine(orient.origin, orient.origin + Vector(orient.axis[0]) * 32, 1, 0, 0, 1);
            G_DebugLine(orient.origin, orient.origin + Vector(orient.axis[1]) * 32, 0, 1, 0, 1);
            G_DebugLine(orient.origin, orient.origin + Vector(orient.axis[2]) * 32, 0, 0, 1, 1);
        }
    }

    if (g_showvehicletags->integer) {
        int numtags;
        int tagnum;

        numtags = gi.TIKI_NumTags(edict->tiki);
        for (tagnum = 0; tagnum < numtags; tagnum++) {
            const char *name = gi.Tag_NameForNum(edict->tiki, tagnum);
            if (!strncmp(name, "tag", 3)) {
                GetTagPositionAndOrientation(tagnum, &orient);
                G_DebugCircle(orient.origin, 10.0, 1, 1, 1, 1, true);

                G_DebugLine(orient.origin, orient.origin + Vector(orient.axis[0]) * 32, 1, 0, 0, 1);
                G_DebugLine(orient.origin, orient.origin + Vector(orient.axis[1]) * 32, 0, 1, 0, 1);
                G_DebugLine(orient.origin, orient.origin + Vector(orient.axis[2]) * 32, 0, 0, 1, 1);
            }
        }
    }

    for (last = this; last->vlink; last = v) {
        v = last->vlink;

        v->setOrigin(origin + i * v->offset.x + j * v->offset.y + k * v->offset.z);
        v->avelocity = avelocity;
        v->velocity  = velocity;

        v->angles[ROLL]  = angles[ROLL];
        v->angles[YAW]   = angles[YAW];
        v->angles[PITCH] = (float)((int)(v->angles[PITCH] + drivespeed / 4.f) % 360);
        v->setAngles(v->angles);
    }

    CheckWater();
    WorldEffects();

    if (m_pCollisionEntity) {
        setSolidType(SOLID_NOT);
        m_pCollisionEntity->Solid();
    } else {
        setSolidType(SOLID_BBOX);
        setContents(CONTENTS_UNKNOWN2);
    }
}

/*
====================
Vehicle::VehicleTouched
====================
*/
void Vehicle::VehicleTouched(Event *ev)
{
    Entity *other;
    float   dot;
    float   speed;
    Vector  delta;
    Vector  dir;
    Event  *event;

    other = ev->GetEntity(1);
    if (other == driver.ent) {
        return;
    }

    if (other == world) {
        return;
    }

    delta = other->origin - last_origin;
    delta.normalize();

    dot = velocity * orientation[0];
    if (dot > 0) {
        if (delta * orientation[0] < 0) {
            return;
        }
    } else {
        if (delta * orientation[0] > 0) {
            return;
        }
    }

    if (!other->IsSubclassOfVehicleTurretGun()) {
        speed = velocity.length();
        if (speed > 10) {
            Sound(m_sSoundSet + "vehicle_crash");
            dir = delta * (1 / speed);

            event = new Event(EV_Damage);
            if (lastdriver.ent && lastdriver.ent->IsSubclassOfPlayer()) {
                event->AddEntity(lastdriver.ent);
            } else {
                event->AddEntity(world);
            }

            event->AddFloat(speed);
            event->AddEntity(this);
            event->AddVector(origin);
            event->AddVector(dir);
            event->AddVector(vec_zero);
            event->AddFloat(speed);
            event->AddInteger(0);
            event->AddInteger(MOD_VEHICLE);
            event->AddInteger(-1);
            other->PostEvent(event, 0);
        }
    }
}

void Vehicle::SetProjectileVulnerable(Event *ev)
{
    if (ev->NumArgs() > 1) {
        ScriptError("Too many arguments");
    }

    if (ev->NumArgs() > 0) {
        m_iProjectileHitsRemaining = ev->GetInteger(1);
        if (m_iProjectileHitsRemaining < 0) {
            ScriptError("Negative arguments illegal");
        }
    } else {
        m_iProjectileHitsRemaining = 0;
    }
}

void Vehicle::DoProjectileVulnerability(Entity *pProjectile, Entity *pOwner, meansOfDeath_t meansOfDeath)
{
    Event *event;
    Vector delta;

    if (m_iProjectileHitsRemaining > 1) {
        m_iProjectileHitsRemaining--;
        return;
    }

    event = new Event(EV_Damage);
    delta = origin - pProjectile->origin;

    event->AddEntity(pProjectile);
    event->AddFloat(health * 2);
    event->AddEntity(pOwner);
    event->AddVector(origin);
    event->AddVector(delta);
    event->AddVector(vec_zero);
    event->AddFloat(0);
    event->AddInteger(0);
    event->AddInteger(meansOfDeath);
    event->AddInteger(-1);
    PostEvent(event, 0);
}

/*
====================
Vehicle::VehicleBlocked
====================
*/
void Vehicle::VehicleBlocked(Event *ev)
{
    return;
    /*
    Entity* other;
    float		speed;
    float    damage;
    Vector   delta;
    Vector   newvel;
    Vector	dir;

    if (!velocity[0] && !velocity[1])
        return;

    other = ev->GetEntity(1);
    if (other == driver.ent)
    {
        return;
    }
    if (other->isSubclassOf(VehicleBase))
    {
        delta = other->origin - origin;
        delta.normalize();

        newvel = vec_zero - (velocity)+(other->velocity * 0.25);
        if (newvel * delta < 0)
        {
            velocity = newvel;
            delta = velocity - other->velocity;
            damage = delta.length() / 4;
        }
        else
        {
            return;
        }
    }
    else if ((velocity.length() < 350))
    {
        other->velocity += velocity * 1.25f;
        other->velocity[2] += 100;
        damage = velocity.length() / 4;
    }
    else
    {
        damage = other->health + 1000;
    }

    // Gib 'em outright
    speed = fabs(velocity.length());
    dir = velocity * (1 / speed);
    other->Damage(this, lastdriver.ent, damage, origin, dir, vec_zero, speed, 0, MOD_VEHICLE, -1, -1, 1.0f);
*/
}

/*
====================
Vehicle::WorldEffects
====================
*/
void Vehicle::WorldEffects(void)
{
    /*
    //
    // Check for earthquakes
    //
    if (groundentity && (level.earthquake_magnitude > 0.0f)) {
        velocity += Vector(
            level.earthquake_magnitude * EARTHQUAKE_STRENGTH * G_CRandom(),
            level.earthquake_magnitude * EARTHQUAKE_STRENGTH * G_CRandom(),
            level.earthquake_magnitude * 1.5f * G_Random()
        );
    }

    //
    // check for lava
    //
    if (watertype & CONTENTS_LAVA) {
        Damage(world, world, 20 * waterlevel, origin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_LAVA);
    }
*/
}

/*
====================
Vehicle::UpdateVariables
====================
*/
void Vehicle::UpdateVariables(
    Vector *acceleration, Vector *vpn, Vector *vup, Vector *vright, Vector *t_vpn, Vector *t_vup, Vector *t_vright
)
{}

/*
====================
Vehicle::TorqueLookup
====================
*/
float Vehicle::TorqueLookup(int rpm)
{
    if (rpm < 5000) {
        return 190;
    } else if (rpm < 6000) {
        return (float)(190 * (6000 - rpm)) / 1000.f;
    } else {
        return 0;
    }
}

/*
====================
Vehicle::SpawnTurret
====================
*/
void Vehicle::SpawnTurret(Event *ev)
{
    VehicleTurretGun *pTurret;
    int               slot;

    pTurret = new VehicleTurretGun();
    pTurret->SetBaseOrientation(orientation, NULL);
    pTurret->setModel(ev->GetString(2));

    slot = ev->GetInteger(1);
    AttachTurretSlot(slot, pTurret, vec_zero, NULL);

    pTurret->SetVehicleOwner(this);
    pTurret->PostEvent(EV_TakeDamage, EV_POSTSPAWN);
    UpdateTurretSlot(slot);

    pTurret->ProcessPendingEvents();
}

/*
====================
Vehicle::EventModifyDrive
====================
*/
void Vehicle::EventModifyDrive(Event *ev)
{
    if (!level.Spawned()) {
        ScriptError("ModifyDrive used improperly... (used before the level is spawned)");
    }

    if (!m_bAutoPilot || !this->m_pCurPath) {
        ScriptError("ModifyDrive used when not driving!");
    }

    if (ev->NumArgs() < 1 || ev->NumArgs() > 3) {
        ScriptError("wrong number of arguments");
    }

    switch (ev->NumArgs()) {
    case 3:
        m_fLookAhead = ev->GetFloat(3);
    case 2:
        m_fIdealAccel = ev->GetFloat(2);
    case 1:
        m_fIdealSpeed = ev->GetFloat(1);
        m_fMaxSpeed   = m_fIdealSpeed;
        break;
    }
}

/*
====================
Vehicle::EventModel
====================
*/
void Vehicle::EventModel(Event *ev)
{
    SetModelEvent(ev);
}

/*
====================
Vehicle::TouchStuff
====================
*/
void Vehicle::TouchStuff(vmove_t *vm)
{
    int        i, j;
    gentity_t *other;
    Event     *event;

    if (driver.ent) {
        G_TouchTriggers(driver.ent);
    }

    for (int i = 0; i < MAX_PASSENGERS; i++) {
        if (Passengers[i].ent) {
            G_TouchTriggers(Passengers[i].ent);
        }
    }

    for (int i = 0; i < MAX_TURRETS; i++) {
        if (Turrets[i].ent) {
            G_TouchTriggers(Turrets[i].ent);
        }
    }

    if (getMoveType() != MOVETYPE_NOCLIP) {
        G_TouchTriggers(this);
    }

    for (i = 0; i < vm->numtouch; i++) {
        other = &g_entities[vm->touchents[i]];

        for (j = 0; j < i; j++) {
            gentity_t *ge = &g_entities[j];

            if (ge == other) {
                break;
            }
        }

        if (j != i) {
            // duplicated
            continue;
        }

        // Don't bother touching the world
        if ((!other->entity) || (other->entity == world)) {
            continue;
        }

        event = new Event(EV_Touch);
        event->AddEntity(this);
        other->entity->ProcessEvent(event);

        event = new Event(EV_Touch);
        event->AddEntity(other->entity);
        ProcessEvent(event);
    }
}

/*
====================
Vehicle::EventNextDrive
====================
*/
void Vehicle::EventNextDrive(Event *ev)
{
    SimpleEntity *path;
    float        *i_fTmp;
    float         o_fTmp[4];
    Vector        org1;
    Vector        org2;

    path = ev->GetSimpleEntity(1);

    if (!m_bAutoPilot) {
        ScriptError("Cannot Set Next Path because Not Currently Driving a Path.");
    }

    if (!m_pCurPath || m_pCurPath->m_iPoints == 0) {
        ScriptError("Cannot Set Next Path because Current Path is Empty.");
    }

    if (!m_pNextPath) {
        m_pNextPath = new cVehicleSpline;
    }

    SetupPath(m_pNextPath, path);

    i_fTmp = m_pCurPath->GetByNode(m_pCurPath->m_iPoints, NULL);
    org1   = (i_fTmp + 1);
    i_fTmp = m_pNextPath->GetByNode(0.0f, NULL);
    org2   = (i_fTmp + 1);

    o_fTmp[0] = (org2 - org1).length();
    VectorClear(o_fTmp + 1);

    m_pNextPath->UniformAdd(o_fTmp);
    m_iNextPathStartNode = m_pCurPath->Append(m_pNextPath);
}

/*
====================
Vehicle::EventLockMovement
====================
*/
void Vehicle::EventLockMovement(Event *ev)
{
    m_bMovementLocked = true;
}

/*
====================
Vehicle::EventUnlockMovement
====================
*/
void Vehicle::EventUnlockMovement(Event *ev)
{
    m_bMovementLocked = false;
}

/*
====================
Vehicle::EventRemoveOnDeath
====================
*/
void Vehicle::EventRemoveOnDeath(Event *ev)
{
    m_bRemoveOnDeath = ev->GetBoolean(1);
}

/*
====================
Vehicle::EventSetExplosionModel
====================
*/
void Vehicle::EventSetExplosionModel(Event *ev)
{
    m_sExplosionModel = ev->GetString(1);
}

/*
====================
Vehicle::EventSetCollisionModel
====================
*/
void Vehicle::EventSetCollisionModel(Event *ev)
{
    Entity *pColEnt = ev->GetEntity(1);

    if (!pColEnt) {
        ScriptError("Trying to set a collision model with a NULL entity.");
    }

    if (m_pCollisionEntity) {
        m_pCollisionEntity->PostEvent(EV_Remove, EV_VEHICLE);
    }

    m_pCollisionEntity = new VehicleCollisionEntity(this);
    m_pCollisionEntity->setModel(pColEnt->model);

    if (!m_pCollisionEntity->model.length() || *m_pCollisionEntity->model != '*') {
        // Re-post the event with the correct time
        m_pCollisionEntity->CancelEventsOfType(EV_Remove);
        m_pCollisionEntity->PostEvent(EV_Remove, EV_VEHICLE);
        m_pCollisionEntity = NULL;

        ScriptError("Model for Entity not of a valid type. Must be B-Model.");
    }

    m_pCollisionEntity->setOrigin(origin);
    m_pCollisionEntity->setAngles(angles);
}

/*
====================
Vehicle::EventGetCollisionModel
====================
*/
void Vehicle::EventGetCollisionModel(Event *ev)
{
    ev->AddEntity(m_pCollisionEntity);
}

/*
====================
Vehicle::EventSetSoundParameters
====================
*/
void Vehicle::EventSetSoundParameters(Event *ev)
{
    m_fSoundMinSpeed = ev->GetFloat(1);
    m_fSoundMinPitch = ev->GetFloat(2);
    m_fSoundMaxSpeed = ev->GetFloat(3);
    m_fSoundMaxPitch = ev->GetFloat(4);
}

/*
====================
Vehicle::EventSetVolumeParameters
====================
*/
void Vehicle::EventSetVolumeParameters(Event *ev)
{
    m_fVolumeMinSpeed = ev->GetFloat(1);
    m_fVolumeMinPitch = ev->GetFloat(2);
    m_fVolumeMaxSpeed = ev->GetFloat(3);
    m_fVolumeMaxPitch = ev->GetFloat(4);
}

/*
====================
Vehicle::UpdateSound
====================
*/
void Vehicle::UpdateSound(void)
{
    float pitch;
    float volume;

    if (level.time < m_fNextSoundState) {
        return;
    }

    // Calculate the pitch based on the vehicle's speed
    pitch = (velocity.length() - m_fSoundMinSpeed) / (m_fSoundMaxSpeed - m_fSoundMinSpeed);

    if (pitch > 1.0f) {
        pitch = 1.0f;
    } else if (pitch < 0.0f) {
        pitch = 0.0f;
    }

    pitch = m_fSoundMinPitch + (m_fSoundMaxPitch - m_fSoundMinPitch) * pitch;

    volume = (velocity.length() - m_fVolumeMinSpeed) / (m_fVolumeMaxSpeed - m_fVolumeMinSpeed);

    if (volume > 1.0f) {
        volume = 1.0f;
    } else if (volume < 0.0f) {
        volume = 0.0f;
    }

    volume = this->m_fVolumeMinPitch + (this->m_fVolumeMaxPitch - this->m_fVolumeMinPitch) * volume;

    switch (m_eSoundState) {
    case ST_OFF:
        StopLoopSound();
        TurnOffVehicleSoundEntities();
        m_fNextSoundState = level.time;

        if (driver.ent || m_bAutoPilot) {
            m_eSoundState = ST_OFF_TRANS_IDLE;
        }
        break;

    case ST_OFF_TRANS_IDLE:
        m_fNextSoundState = level.time;
        m_eSoundState     = ST_IDLE;
        Sound(m_sSoundSet + "snd_on");
        LoopSound(m_sSoundSet + "snd_idle");
        break;

    case ST_IDLE_TRANS_OFF:
        m_fNextSoundState = level.time;
        m_eSoundState     = ST_OFF;
        Sound(m_sSoundSet + "snd_off");
        StopLoopSound();
        break;

    case ST_IDLE:
        m_fNextSoundState = level.time;

        if (driver.ent || m_bAutoPilot) {
            if (fabs(DotProduct(orientation[0], velocity)) > 10.0f) {
                m_eSoundState = ST_IDLE_TRANS_RUN;
            }
        } else {
            m_eSoundState = ST_IDLE_TRANS_OFF;
        }

        LoopSound(m_sSoundSet + "snd_idle");

        TurnOffVehicleSoundEntities();
        break;

    case ST_IDLE_TRANS_RUN:
        m_fNextSoundState = level.time;
        m_eSoundState     = ST_RUN;

        Sound(m_sSoundSet + "snd_revup");
        LoopSound(m_sSoundSet + "snd_run", -1.0f, -1.0f, -1.0f, pitch);
        break;

    case ST_RUN:
        m_fNextSoundState = level.time;

        if (fabs(DotProduct(orientation[0], velocity)) < 10.0f) {
            m_eSoundState = ST_RUN_TRANS_IDLE;
        }

        TurnOnVehicleSoundEntities();
        LoopSound(m_sSoundSet + "snd_run", volume, -1.0f, -1.0f, pitch);
        break;

    case ST_RUN_TRANS_IDLE:
        m_fNextSoundState = level.time;
        m_eSoundState     = ST_IDLE;

        Sound(m_sSoundSet + "snd_revdown");
        LoopSound(m_sSoundSet + "snd_idle");
        break;

    default:
        m_fNextSoundState = level.time;
        m_eSoundState     = ST_OFF;
        break;
    }
}

/*
====================
Vehicle::SetupVehicleSoundEntities
====================
*/
void Vehicle::SetupVehicleSoundEntities(void)
{
    int    i;
    Vector a;
    Vector b;
    Vector c;
    Vector start;

    angles.AngleVectorsLeft(&a, &b, &c);

    // place the sound entities in the vehicle wheels
    for (i = 0; i < MAX_CORNERS; i++) {
        if (!m_pVehicleSoundEntities[i]) {
            m_pVehicleSoundEntities[i] = new VehicleSoundEntity(this);
        }

        start = origin + a * Corners[i][0] + b * Corners[i][1] + c * Corners[i][2];
        m_pVehicleSoundEntities[i]->setOrigin(start);
    }
}

/*
====================
Vehicle::TurnOnVehicleSoundEntities
====================
*/
void Vehicle::TurnOnVehicleSoundEntities(void)
{
    for (int i = 0; i < MAX_CORNERS; i++) {
        if (!m_pVehicleSoundEntities[i]) {
            m_pVehicleSoundEntities[i] = new VehicleSoundEntity(this);
        }

        m_pVehicleSoundEntities[i]->Start();
    }
}

/*
====================
Vehicle::TurnOffVehicleSoundEntities
====================
*/
void Vehicle::TurnOffVehicleSoundEntities(void)
{
    for (int i = 0; i < MAX_CORNERS; i++) {
        if (!m_pVehicleSoundEntities[i]) {
            m_pVehicleSoundEntities[i] = new VehicleSoundEntity(this);
        }

        m_pVehicleSoundEntities[i]->Stop();
    }
}

/*
====================
Vehicle::RemoveVehicleSoundEntities
====================
*/
void Vehicle::RemoveVehicleSoundEntities(void)
{
    for (int i = 0; i < MAX_CORNERS; i++) {
        if (!m_pVehicleSoundEntities[i]) {
            continue;
        }

        m_pVehicleSoundEntities[i]->PostEvent(EV_Remove, EV_VEHICLE);
    }
}

/*
====================
Vehicle::KickSuspension
====================
*/
void Vehicle::KickSuspension(Vector vDirection, float fForce)
{
    VectorNormalizeFast(vDirection);

    m_fForwardForce += vDirection * orientation[1] * fForce;
    m_fLeftForce += vDirection * orientation[0] * fForce;
}

/*
====================
Vehicle::EventDamage
====================
*/
void Vehicle::EventDamage(Event *ev)
{
    Entity *pEnt;
    Vector  vDirection;
    float   fForce;
    int     i;

    if (!IsDamagedBy(ev->GetEntity(3))) {
        return;
    }

    pEnt = ev->GetEntity(1);
    if (pEnt && pEnt == driver.ent) {
        return;
    }

    Event *event = new Event(EV_Damage, ev->NumArgs());

    vDirection = ev->GetVector(5);
    fForce     = ev->GetFloat(7);
    KickSuspension(vDirection, fForce);

    for (i = 1; i <= ev->NumArgs(); i++) {
        if (i == 7) {
            event->AddFloat(0);
        } else {
            event->AddValue(ev->GetValue(i));
        }
    }

    if (driver.ent && driver.ent->IsSubclassOfPlayer()) {
        Player *player = static_cast<Player *>(driver.ent.Pointer());
        Vector  dir    = ev->GetVector(1);
        float   dir_yaw;
        float   camera_yaw;

        if (player->camera) {
            dir_yaw    = dir.toYaw();
            camera_yaw = player->camera->angles[1];
        } else {
            dir_yaw    = dir.toYaw();
            camera_yaw = player->GetVAngles()[1];
        }

        player->damage_yaw = AngleSubtract(camera_yaw, dir_yaw) + 180.5;
    }

    DamageEvent(event);
    delete event;
}

/*
====================
Vehicle::FactorInOriginOffset
====================
*/
void Vehicle::FactorInOriginOffset(void)
{
    origin += m_vOriginOffset;
    setOrigin(origin);
}

/*
====================
Vehicle::FactorOutOriginOffset
====================
*/
void Vehicle::FactorOutOriginOffset(void)
{
    origin -= m_vOriginOffset;
    setOrigin(origin);
}

/*
====================
Vehicle::CalculateOriginOffset
====================
*/
void Vehicle::CalculateOriginOffset(void)
{
    int    index;
    Vector vTireAvg;
    Vector vMissHit;
    Vector temp;
    int    iNum = 0;
    Vector acceleration;
    Vector oldoffset;

    oldoffset = m_vOriginOffset;
    m_vOriginOffset += m_vOriginOffset2;
    m_vOriginOffset2 = vec_zero;

    for (index = 0; index < MAX_CORNERS; index++) {
        if (m_bTireHit[index]) {
            temp = m_vTireEnd[index];
            vTireAvg += origin - temp;
            iNum++;
        } else {
            temp     = Corners[index];
            vMissHit = temp;
        }
    }

    if (iNum == 3) {
        temp = m_vNormalSum * (1.0f / m_iNumNormals);
        ProjectPointOnPlane(acceleration, vMissHit, temp);
        vTireAvg += acceleration;
    } else if (iNum == 4) {
        vTireAvg *= 0.25f;
        MatrixTransformVector(m_vOriginCornerOffset, orientation, acceleration);
        vTireAvg -= acceleration;
        m_vOriginOffset2 += vTireAvg;
    }

    m_vOriginOffset2 += vec_zero;
    FactorInSkidOrigin();

    Vector vTmp = real_acceleration - prev_acceleration;

    m_fDownForce = vTmp[2] * m_fZCoef;
    m_fDownForce = Q_clamp_float(m_fDownForce, m_fZMin, m_fZMax);

    m_fUpForce = -m_vOriginOffset[2] * m_fBouncyCoef + m_fUpForce;
    m_fUpForce *= m_fSpringyCoef;
    m_vOriginOffset[2] += (m_fDownForce + m_fUpForce) * level.frametime * 12;

    if (m_vOriginOffset[2] < m_fZMin) {
        m_vOriginOffset[2] = m_fZMin;
    }

    m_vOriginOffset -= m_vOriginOffset2;

    if (!isfinite(m_vOriginOffset[0]) || !isfinite(m_vOriginOffset[1]) || !isfinite(m_vOriginOffset[2])) {
        m_vOriginOffset = oldoffset;
    }
}

/*
====================
Vehicle::UpdateTires
====================
*/
void Vehicle::UpdateTires(void)
{
    int     index;
    trace_t trace;
    Vector  a;
    Vector  b;
    Vector  c;
    Vector  vTmp;
    Vector  t_mins;
    Vector  t_maxs;
    Vector  start;
    Vector  end;
    Vector  boxoffset;
    Entity *pSkippedEntities[MAX_SKIPPED_ENTITIES];
    int     iContentsEntities[MAX_SKIPPED_ENTITIES];
    solid_t solidEntities[MAX_SKIPPED_ENTITIES];
    int     iNumSkippedEntities;
    int     iNumSkipped = 0;

    t_mins = mins * 0.25f;
    t_maxs = maxs * 0.25f;

    if (real_velocity.length() <= 0.5f && m_iLastTiresUpdate != -1 && m_iLastTiresUpdate + 1000 > level.inttime) {
        return;
    }

    m_iLastTiresUpdate = level.inttime;

    vTmp.y = angles.y + m_fSkidAngle;
    AngleVectors(vTmp, a, b, c);

    // Temporary make slots non-solid for G_Trace
    SetSlotsNonSolid();

    do {
        iNumSkippedEntities = 0;

        for (index = 0; index < MAX_CORNERS; index++) {
            boxoffset = Corners[index];
            start     = origin + a * boxoffset[0] + b * boxoffset[1] + c * boxoffset[2];
            end       = start + Vector(0, 0, -400);

            trace = G_Trace(start, t_mins, t_maxs, end, this, MASK_VEHICLE_TIRES, false, "Vehicle::PostThink Corners");

            if (g_showvehiclemovedebug->integer) {
                G_DebugLine(start, end, 1, 1, 1, 1);
                G_DebugLine(start, trace.endpos, 1, 0, 0, 1);
            }

            if (trace.ent && trace.ent->entity && trace.ent->entity->isSubclassOf(VehicleCollisionEntity)) {
                // save the entity
                pSkippedEntities[iNumSkipped]  = trace.ent->entity;
                iContentsEntities[iNumSkipped] = trace.ent->r.contents;
                solidEntities[iNumSkipped]     = trace.ent->solid;
                iNumSkipped++;

                if (iNumSkipped >= MAX_SKIPPED_ENTITIES) {
                    gi.Error(ERR_DROP, "MAX_SKIPPED_ENTITIES hit in VehicleMove.\n");
                    return;
                }

                trace.ent->entity->setSolidType(SOLID_NOT);
                iNumSkippedEntities++;
            }

            if (trace.fraction == 1.0) {
                m_bTireHit[index] = false;
            } else {
                m_vTireEnd[index] = trace.endpos;
                m_bTireHit[index] = true;
            }
        }
    } while (iNumSkippedEntities != 0);

    for (index = 0; index < iNumSkipped; index++) {
        pSkippedEntities[index]->setSolidType(solidEntities[index]);
        pSkippedEntities[index]->setContents(iContentsEntities[index]);
    }

    // Turn slots back into a solid state
    SetSlotsSolid();
}

/*
====================
Vehicle::UpdateNormals
====================
*/
void Vehicle::UpdateNormals(void)
{
    Vector vDist1;
    Vector vDist2;
    Vector vCross;
    Vector temp;
    Vector pitch;
    Vector i;
    Vector j;

    if (real_velocity.length() <= 0.5 && m_iLastTiresUpdate != -1 && m_iLastTiresUpdate + 1000 > level.inttime) {
        return;
    }

    angles.AngleVectorsLeft(&i, &j, NULL);

    j             = vec_zero - j;
    m_vNormalSum  = vec_zero;
    m_iNumNormals = 0;

    if (m_bTireHit[0] && m_bTireHit[1] && m_bTireHit[2]) {
        vDist1 = m_vTireEnd[1] - m_vTireEnd[0];
        vDist2 = m_vTireEnd[1] - m_vTireEnd[2];

        CrossProduct(vDist1, vDist2, vCross);
        vCross.normalize();
        m_vNormalSum += vCross;

        m_iNumNormals++;
    }

    if (m_bTireHit[1] && m_bTireHit[2] && m_bTireHit[3]) {
        vDist1 = m_vTireEnd[2] - m_vTireEnd[1];
        vDist2 = m_vTireEnd[2] - m_vTireEnd[3];

        CrossProduct(vDist1, vDist2, vCross);
        vCross.normalize();
        m_vNormalSum += vCross;

        m_iNumNormals++;
    }

    if (m_bTireHit[2] && m_bTireHit[3] && m_bTireHit[0]) {
        vDist1 = m_vTireEnd[3] - m_vTireEnd[0];
        vDist2 = m_vTireEnd[3] - m_vTireEnd[2];

        CrossProduct(vDist1, vDist2, vCross);
        vCross.normalize();
        m_vNormalSum += vCross;

        m_iNumNormals++;
    }

    if (m_bTireHit[3] && m_bTireHit[0] && m_bTireHit[1]) {
        vDist1 = m_vTireEnd[0] - m_vTireEnd[3];
        vDist2 = m_vTireEnd[0] - m_vTireEnd[1];

        CrossProduct(vDist1, vDist2, vCross);
        vCross.normalize();
        m_vNormalSum += vCross;

        m_iNumNormals++;
    }

    if (m_iNumNormals > 1) {
        float x, z;

        temp = m_vNormalSum / m_iNumNormals;
        i    = temp.CrossProduct(temp, j);

        pitch = i;
        x     = pitch.toPitch();

        temp  = m_vNormalSum / m_iNumNormals;
        pitch = temp.CrossProduct(temp, i);
        z     = pitch.toPitch();

        angles[0] = x;
        angles[2] = z;
    }
}

/*
====================
Vehicle::UpdateBones
====================
*/
void Vehicle::UpdateBones(void)
{
    float fNewTurnAngle = AngleNormalize180(turnangle - m_fSkidAngle);
    fNewTurnAngle       = Q_clamp_float(fNewTurnAngle, -maxturnrate, maxturnrate);

    SetControllerAngles(0, Vector(0, fNewTurnAngle, 0));
    SetControllerAngles(1, Vector(0, fNewTurnAngle, 0));
}

/*
====================
Vehicle::UpdateShaderOffset
====================
*/
void Vehicle::UpdateShaderOffset(void)
{
    m_fShaderOffset -= real_velocity * orientation[0] / 4 * level.frametime;
    edict->s.shader_time = m_fShaderOffset;
}

/*
====================
Vehicle::UpdateDriverSlot
====================
*/
void Vehicle::UpdateDriverSlot(int iSlot)
{
    orientation_t orient;

    if (!(driver.flags & SLOT_BUSY) || !driver.ent) {
        return;
    }

    if (driver.boneindex != -1) {
        GetTag(driver.boneindex, &orient);
        if (driver.ent->IsSubclassOfActor()) {
            driver.ent->setOriginEvent(orient.origin);
        } else {
            driver.ent->setOrigin(orient.origin);
        }

        if (drivable) {
            driver.ent->avelocity = avelocity;
            driver.ent->velocity  = velocity;
            driver.ent->setAngles(angles);
        }
    } else {
        Vector forward = orientation[0];
        Vector left    = orientation[1];
        Vector up      = orientation[2];

        if (driver.ent->IsSubclassOfActor()) {
            driver.ent->setOriginEvent(
                origin + forward * driveroffset[0] + left * driveroffset[1] + up * driveroffset[2]
            );
        } else {
            driver.ent->setOrigin(origin + forward * driveroffset[0] + left * driveroffset[1] + up * driveroffset[2]);
        }

        if (drivable) {
            driver.ent->avelocity = avelocity;
            driver.ent->velocity  = velocity;
            driver.ent->setAngles(angles);
        }
    }
}

/*
====================
Vehicle::UpdatePassengerSlot
====================
*/
void Vehicle::UpdatePassengerSlot(int iSlot)
{
    orientation_t orient;

    if (!(Passengers[iSlot].flags & SLOT_BUSY) || !Passengers[iSlot].ent) {
        return;
    }

    if (Passengers[iSlot].boneindex != -1) {
        GetTag(Passengers[iSlot].boneindex, &orient);
        if (Passengers[iSlot].ent->IsSubclassOfActor()) {
            Passengers[iSlot].ent->setOriginEvent(orient.origin);
        } else {
            Passengers[iSlot].ent->setOrigin(orient.origin);
        }

        Passengers[iSlot].ent->avelocity = avelocity;
        Passengers[iSlot].ent->velocity  = velocity;

        if (!Passengers[iSlot].ent->IsSubclassOfActor() || !((Actor *)Passengers[iSlot].ent.Pointer())->m_Enemy) {
            Vector newAngles;

            MatrixToEulerAngles(orient.axis, newAngles);
            Passengers[iSlot].ent->setAngles(newAngles);
        }
    } else {
        if (Passengers[iSlot].ent->IsSubclassOfActor()) {
            Passengers[iSlot].ent->setOriginEvent(origin);
        } else {
            Passengers[iSlot].ent->setOrigin(origin);
        }

        Passengers[iSlot].ent->avelocity = avelocity;
        Passengers[iSlot].ent->velocity  = velocity;

        if (!Passengers[iSlot].ent->IsSubclassOfActor() || ((Actor *)Passengers[iSlot].ent.Pointer())->m_Enemy) {
            Passengers[iSlot].ent->setAngles(angles);
        }
    }
}

/*
====================
Vehicle::UpdateTurretSlot
====================
*/
void Vehicle::UpdateTurretSlot(int iSlot)
{
    orientation_t orient;

    if (!(Turrets[iSlot].flags & SLOT_BUSY) || !Turrets[iSlot].ent) {
        return;
    }

    if (Turrets[iSlot].boneindex != -1) {
        GetTag(Turrets[iSlot].boneindex, &orient);
        if (Turrets[iSlot].ent->IsSubclassOfActor()) {
            Turrets[iSlot].ent->setOriginEvent(orient.origin);
        } else {
            Turrets[iSlot].ent->setOrigin(orient.origin);
        }

        Turrets[iSlot].ent->avelocity = avelocity;
        Turrets[iSlot].ent->velocity  = velocity;

        if (Turrets[iSlot].ent->IsSubclassOfVehicleTurretGun()) {
            VehicleTurretGun *vtg = static_cast<VehicleTurretGun *>(Turrets[iSlot].ent.Pointer());
            vtg->SetBaseOrientation(orient.axis, NULL);
        }
    } else {
        Vector forward = orientation[0];
        Vector left    = orientation[1];
        Vector up      = orientation[2];

        if (Turrets[iSlot].ent->IsSubclassOfActor()) {
            Turrets[iSlot].ent->setOriginEvent(origin);
        } else {
            Turrets[iSlot].ent->setOrigin(origin);
        }

        Turrets[iSlot].ent->avelocity = avelocity;
        Turrets[iSlot].ent->velocity  = velocity;

        if (Turrets[iSlot].ent->IsSubclassOfVehicleTurretGun()) {
            VehicleTurretGun *vtg = static_cast<VehicleTurretGun *>(Turrets[iSlot].ent.Pointer());
            vtg->SetBaseOrientation(this->orientation, NULL);
        }
    }
}

/*
====================
Vehicle::EventStopAtEnd
====================
*/
void Vehicle::EventStopAtEnd(Event *ev)
{
    if (!m_pCurPath) {
        ScriptError("Tried to Stop at end of path on a vehicle who is not driving a path!");
    }

    m_fStopStartDistance = GetPathPosition(m_pCurPath, m_iCurNode);
    m_fStopStartSpeed    = moveimpulse;
    m_fStopEndDistance   = *m_pCurPath->GetByNode(m_pCurPath->m_iPoints, NULL);
    m_bStopEnabled       = true;
}

/*
====================
Vehicle::GetPathPosition
====================
*/
float Vehicle::GetPathPosition(cVehicleSpline *pPath, int iNode)
{
    float *vTmp;
    vec3_t vPrev;
    vec3_t vCur;
    vec3_t vTotal;
    Vector vDelta;
    float  fTotal;
    float  fCoef;

    vTmp = pPath->GetByNode(iNode, NULL);
    VectorCopy(vTmp + 1, vCur);

    if (g_showvehiclemovedebug->integer) {
        G_DebugString(vCur, 3.0f, 1.0f, 1.0f, 1.0f, "%f", vTmp[0]);
    }

    vTmp = pPath->GetByNode(iNode - 1, NULL);
    VectorCopy(vTmp + 1, vPrev);

    if (g_showvehiclemovedebug->integer) {
        G_DebugString(vPrev, 3.0f, 1.0f, 1.0f, 1.0f, "%f", vTmp[0]);
    }

    VectorSubtract(vCur, vPrev, vTotal);
    fTotal      = VectorLength(vTotal);
    m_vIdealDir = vTotal;
    VectorNormalize(m_vIdealDir);
    angles.AngleVectorsLeft(&vDelta, NULL, NULL);

    fCoef = ProjectLineOnPlane(vDelta, DotProduct(vDelta, origin), vPrev, vCur, NULL);

    if (g_showvehiclemovedebug->integer) {
        G_DebugBBox(vPrev, Vector(-32, -32, -32), Vector(32, 32, 32), 0, 1, 1, 1);
        G_DebugBBox(vCur, Vector(-32, -32, -32), Vector(32, 32, 32), 1, 1, 0, 1);
        G_DebugArrow(vCur, m_vIdealDir * -1.0, (1.0 - fCoef) * fTotal, 0, 1, 0, 1);
        G_DebugArrow(vPrev, m_vIdealDir, fCoef * fTotal, 0, 0, 1, 1);
    }

    return *pPath->GetByNode(iNode - (1.0 - fCoef), NULL);
}

/*
====================
Vehicle::EventSkidding
====================
*/
void Vehicle::EventSkidding(Event *ev)
{
    if (ev->NumArgs() == 1) {
        m_bEnableSkidding = ev->GetInteger(1);
    } else {
        m_bEnableSkidding = true;
    }

    ProcessEvent(EV_Vehicle_ContinueSkidding);
}

/*
====================
Vehicle::UpdateSkidAngle
====================
*/
void Vehicle::UpdateSkidAngle(void)
{
    if (m_bEnableSkidding) {
        if (g_showvehiclemovedebug && g_showvehiclemovedebug->integer) {
            Com_Printf("Skidding!\n");
        }

        m_fSkidLeftForce += velocity.length() / 150.0f * turnangle;
        m_fSkidRightForce += -m_fSkidAngle * 0.2;
        m_fSkidRightForce *= 0.3f;
        m_fSkidAngle     = m_fSkidAngle + (m_fSkidLeftForce + m_fSkidRightForce) * 22.0f * level.frametime;
        m_vSkidOrigin[0] = -fabs(m_fSkidAngle);
    } else {
        m_fSkidAngle = 0;
    }
}

/*
====================
Vehicle::FactorInSkidOrigin
====================
*/
void Vehicle::FactorInSkidOrigin(void)
{
    Vector vNewOrigin;

    vNewOrigin[0] = orientation[0][0] * m_vSkidOrigin[0] + orientation[1][0] * m_vSkidOrigin[1]
                  + orientation[2][0] * m_vSkidOrigin[2];
    vNewOrigin[1] = orientation[0][1] * m_vSkidOrigin[0] + orientation[1][1] * m_vSkidOrigin[1]
                  + orientation[2][1] * m_vSkidOrigin[2];
    vNewOrigin[2] = orientation[0][2] * m_vSkidOrigin[0] + orientation[1][2] * m_vSkidOrigin[1]
                  + orientation[2][2] * m_vSkidOrigin[2];

    m_vOriginOffset2 += vNewOrigin;
}

/*
====================
Vehicle::EventContinueSkidding
====================
*/
void Vehicle::EventContinueSkidding(Event *ev)
{
    if (m_bEnableSkidding) {
        if (HasAnim("skidding")) {
            NewAnim("skidding", EV_Vehicle_ContinueSkidding, 7, 0.000001f);
        } else {
            assert(!"Vehicle without skidding animation.");
        }
    } else {
        if (HasAnim("idle")) {
            NewAnim("idle", 0, 7, 0.000001f);
        } else {
            assert(!"Vehicle without idle animation.");
        }
    }
}

/*
====================
Vehicle::FactorInAnglesOffset
====================
*/
void Vehicle::FactorInAnglesOffset(Vector *vAddedAngles)
{
    (*vAddedAngles) = m_vAnglesOffset;
    vAddedAngles->y += m_fSkidAngle;
}

/*
====================
Vehicle::FactorOutAnglesOffset
====================
*/
void Vehicle::FactorOutAnglesOffset(void) {}

/*
====================
Vehicle::CalculateAnglesOffset
====================
*/
void Vehicle::CalculateAnglesOffset(Vector acceleration)
{
    if (level.inttime <= 1200) {
        // leave some time before allowing to shake
        // so all tanks spawn peacefully in the level
        return;
    }

    m_fForwardForce += DotProduct(orientation[0], acceleration) * m_fYawCoef;
    m_fBackForce = -m_vAnglesOffset[0] * m_fBouncyCoef + m_fBackForce;
    m_fBackForce *= m_fSpringyCoef;

    m_vAnglesOffset[0] += m_fForwardForce + m_fBackForce * 12.0 * level.frametime;
    m_vAnglesOffset[0] = Q_clamp_float(m_vAnglesOffset[0], m_fYawMin, m_fYawMax);

    m_fForwardForce = 0;
    m_fLeftForce += acceleration * orientation[1] * m_fRollCoef;
    m_fRightForce = -m_vAnglesOffset[2] * m_fBouncyCoef + m_fRightForce;
    m_fRightForce *= m_fSpringyCoef;

    m_vAnglesOffset[2] += 12.0 * (m_fLeftForce + m_fRightForce) * level.frametime;
    m_vAnglesOffset[2] = Q_clamp_float(m_vAnglesOffset[2], m_fRollMin, m_fRollMax);

    m_fLeftForce = 0;
}

/*
====================
Vehicle::CalculateAnimationData
====================
*/
void Vehicle::CalculateAnimationData(Vector vAngles, Vector vOrigin)
{
    float fLeft    = fEpsilon();
    float fRight   = fEpsilon();
    float fForward = fEpsilon();
    float fBack    = fEpsilon();
    float fLow     = fEpsilon();
    float fHigh    = fEpsilon();

    if (vAngles[0] < 0.0) {
        fBack = vAngles[0] / m_fYawMin;
    } else if (vAngles[0] > 0.0) {
        fForward = vAngles[0] / m_fYawMax;
    }

    if (vAngles[2] < 0.0) {
        fRight = vAngles[2] / m_fRollMin;
    } else if (vAngles[2] > 0.0) {
        fLeft = vAngles[2] / m_fRollMax;
    }

    if (vOrigin[2] < 0.0) {
        fBack = vOrigin[2] / m_fZMin;
    } else if (vOrigin[2] > 0.0) {
        fForward = vOrigin[2] / m_fZMax;
    }

    if (!m_bAnimMove) {
        NewAnim("idle", NULL, 0, 1.0);
        NewAnim("lean_left", NULL, 3, fLeft);
        NewAnim("lean_right", NULL, 4, fRight);
        NewAnim("lean_forward", NULL, 1, fForward);
        NewAnim("lean_back", NULL, 2, fBack);
        NewAnim("high", NULL, 6, fLow);
        NewAnim("low", NULL, 5, fHigh);
    }
}

/*
====================
Vehicle::EventVehicleAnim
====================
*/
void Vehicle::EventVehicleAnim(Event *ev)
{
    float weight;

    if (ev->NumArgs() > 1) {
        weight = ev->GetFloat(2);
    } else {
        weight = 1.0f;
    }

    NewAnim(ev->GetString(1), EV_Vehicle_VehicleAnimDone, 8, weight);
}

/*
====================
Vehicle::EventVehicleAnimDone
====================
*/
void Vehicle::EventVehicleAnimDone(Event *ev)
{
    Unregister(STRING_VEHICLEANIMDONE);
}

void Vehicle::EventVehicleMoveAnim(Event *ev)
{
    str anim_name;

    anim_name = ev->GetString(1);
    if (!HasAnim(anim_name)) {
        return;
    }

    m_bAnimMove = true;

    StopAnimating(0);
    StopAnimating(3);
    StopAnimating(4);
    StopAnimating(1);
    StopAnimating(2);
    StopAnimating(6);
    StopAnimating(5);
    StopAnimating(8);

    NewAnim(anim_name, EV_Vehicle_VehicleMoveAnimDone);
}

void Vehicle::EventVehicleMoveAnimDone(Event *ev)
{
    Unregister(STRING_ANIMDONE);
    moveimpulse = 0;
    turnimpulse = 0;
    m_bAnimMove = false;
}

void Vehicle::EventDamageSounds(Event *ev)
{
    if (ev->NumArgs() == 1) {
        m_bDamageSounds = ev->GetInteger(1);
    } else {
        m_bDamageSounds = true;
    }
}

void Vehicle::EventRunSounds(Event *ev)
{
    if (ev->NumArgs() == 1) {
        m_bRunSounds = ev->GetInteger(1);
    } else {
        m_bRunSounds = true;
    }
}

void Vehicle::Remove(Event *ev)
{
    int i;

    for (i = 0; i < MAX_TURRETS; i++) {
        Entity *pTurret = Turrets[i].ent;
        if (!pTurret) {
            continue;
        }

        pTurret->PostEvent(EV_Remove, 0);
        Turrets[i].ent = NULL;
    }

    Entity::Remove(ev);
}

/*
====================
Vehicle::IsDamagedBy

Returns whether or not the vehicle is damaged by the specified entity.
====================
*/
bool Vehicle::IsDamagedBy(Entity *ent)
{
    int i;

    if (FindDriverSlotByEntity(ent) != -1) {
        return false;
    }

    if (FindPassengerSlotByEntity(ent) != -1) {
        return false;
    }

    if (FindTurretSlotByEntity(ent) != -1) {
        return false;
    }

    for (i = 0; i < MAX_TURRETS; i++) {
        TurretGun *pTurret;

        if (!Turrets[i].ent) {
            continue;
        }

        pTurret = static_cast<TurretGun *>(Turrets[i].ent.Pointer());
        if (pTurret->IsSubclassOfTurretGun() && pTurret->owner == ent) {
            return false;
        }
    }

    return true;
}

/*
====================
Vehicle::DriverAdded
====================
*/
void Vehicle::DriverAdded() {}

void Vehicle::EventSetMaxUseAngle(Event *ev)
{
    m_fMaxUseAngle = ev->GetFloat(1);
}

void Vehicle::EventCanUse(Event *ev)
{
    Entity *entity = ev->GetEntity(1);

    if (driver.ent || !m_fMaxUseAngle || !Turrets[0].ent) {
        ev->AddInteger(1);
        return;
    }

    if (!entity) {
        ev->AddInteger(0);
        return;
    }

    Vector vForward;
    Vector vDir;

    AngleVectors(Turrets[0].ent->angles, vForward, NULL, NULL);

    vDir = Turrets[0].ent->origin - entity->origin;
    VectorNormalize(vDir);

    if (fabs(GetAngleBetweenVectors2D(vForward, vDir)) > m_fMaxUseAngle) {
        ev->AddInteger(false);
    } else {
        ev->AddInteger(true);
    }
}

int Vehicle::GetProjectileHitsRemaining() const {
    return m_iProjectileHitsRemaining;
}

/*
====================
Vehicle::Archive
====================
*/
void Vehicle::Archive(Archiver& arc)
{
    VehicleBase::Archive(arc);

    arc.ArchiveFloat(&maxturnrate);
    arc.ArchiveFloat(&currentspeed);
    arc.ArchiveFloat(&turnangle);
    arc.ArchiveFloat(&turnimpulse);
    arc.ArchiveFloat(&moveimpulse);
    arc.ArchiveFloat(&jumpimpulse);
    arc.ArchiveFloat(&speed);
    arc.ArchiveFloat(&conesize);
    arc.ArchiveFloat(&maxtracedist);
    arc.ArchiveString(&weaponName);
    arc.ArchiveString(&vehicleName);
    arc.ArchiveVector(&last_origin);
    arc.ArchiveVector(&seatangles);
    arc.ArchiveVector(&seatoffset);
    arc.ArchiveVector(&driveroffset);

    arc.ArchiveVector(&Corners[0]);
    arc.ArchiveVector(&Corners[1]);
    arc.ArchiveVector(&Corners[2]);
    arc.ArchiveVector(&Corners[3]);

    arc.ArchiveBoolean(&drivable);
    arc.ArchiveBoolean(&pathDrivable); // Added in 2.30
    arc.ArchiveBoolean(&locked);
    arc.ArchiveBoolean(&hasweapon);
    arc.ArchiveBoolean(&showweapon);
    arc.ArchiveBoolean(&steerinplace);
    arc.ArchiveBoolean(&jumpable);

    arc.ArchiveBoolean(&m_bMovementLocked);
    arc.ArchiveBoolean(&m_bAnimMove);                // Added in 2.0
    arc.ArchiveBoolean(&m_bDamageSounds);            // Added in 2.0
    arc.ArchiveBoolean(&m_bRunSounds);               // Added in 2.0
    arc.ArchiveInteger(&m_iProjectileHitsRemaining); // Added in 2.30

    driver.Archive(arc);
    lastdriver.Archive(arc);

    for (int i = 0; i < MAX_PASSENGERS; i++) {
        Passengers[i].Archive(arc);
    }

    for (int i = 0; i < MAX_TURRETS; i++) {
        Turrets[i].Archive(arc);
    }

    arc.ArchiveInteger(&numPassengers);
    arc.ArchiveInteger(&numTurrets);
    arc.ArchiveInteger(&moveresult);
    arc.ArchiveInteger(&isBlocked);
    arc.ArchiveInteger(&m_iFrameCtr);
    arc.ArchiveInteger(&m_iGear);
    arc.ArchiveInteger(&m_iRPM);
    arc.ArchiveInteger(&m_iNextPathStartNode);

    if (!arc.Saving()) {
        m_iLastTiresUpdate = -1;

        int tempInt;
        arc.ArchiveInteger(&tempInt);

        if (tempInt) {
            m_pAlternatePath = new cVehicleSpline;
        } else {
            m_pAlternatePath = NULL;
        }
    } else {
        int tempInt = m_pAlternatePath != NULL;
        arc.ArchiveInteger(&tempInt);
    }

    if (m_pAlternatePath) {
        m_pAlternatePath->Archive(arc);
    }

    arc.ArchiveInteger(&m_iAlternateNode);

    if (!arc.Saving()) {
        int tempInt;
        arc.ArchiveInteger(&tempInt);

        if (tempInt) {
            m_pCurPath = new cVehicleSpline;
        } else {
            m_pCurPath = NULL;
        }
    } else {
        int tempInt = m_pCurPath != NULL;
        arc.ArchiveInteger(&tempInt);
    }

    if (m_pCurPath) {
        m_pCurPath->Archive(arc);
    }

    arc.ArchiveInteger(&m_iCurNode);

    if (!arc.Saving()) {
        int tempInt;
        arc.ArchiveInteger(&tempInt);

        if (tempInt) {
            m_pNextPath = new cVehicleSpline;
        } else {
            m_pNextPath = NULL;
        }
    } else {
        int tempInt = m_pNextPath != NULL;
        arc.ArchiveInteger(&tempInt);
    }

    if (m_pNextPath) {
        m_pNextPath->Archive(arc);
    }

    arc.ArchiveFloat(&maxturnrate);
    arc.ArchiveFloat(&currentspeed);
    arc.ArchiveFloat(&turnangle);
    arc.ArchiveFloat(&turnimpulse);
    arc.ArchiveFloat(&moveimpulse);
    arc.ArchiveFloat(&prev_moveimpulse);
    arc.ArchiveFloat(&jumpimpulse);
    arc.ArchiveFloat(&speed);
    arc.ArchiveFloat(&conesize);
    arc.ArchiveFloat(&maxtracedist);
    arc.ArchiveFloat(&airspeed);
    arc.ArchiveFloat(&m_fGearRatio[0]);
    arc.ArchiveFloat(&m_fGearRatio[1]);
    arc.ArchiveFloat(&m_fGearRatio[2]);
    arc.ArchiveFloat(&m_fGearRatio[3]);
    arc.ArchiveFloat(&m_fGearRatio[4]);
    arc.ArchiveFloat(&m_fGearRatio[5]);
    arc.ArchiveFloat(&m_fMass);
    arc.ArchiveFloat(&m_fFrontMass);
    arc.ArchiveFloat(&m_fBackMass);
    arc.ArchiveFloat(&m_fWheelBase);
    arc.ArchiveFloat(&m_fWheelFrontLoad);
    arc.ArchiveFloat(&m_fWheelFrontInnerLoad);
    arc.ArchiveFloat(&m_fWheelFrontOutterLoad);
    arc.ArchiveFloat(&m_fWheelFrontDist);
    arc.ArchiveFloat(&m_fWheelFrontSuspension);
    arc.ArchiveFloat(&m_fWheelBackLoad);
    arc.ArchiveFloat(&m_fWheelBackInnerLoad);
    arc.ArchiveFloat(&m_fWheelBackOutterLoad);
    arc.ArchiveFloat(&m_fWheelBackDist);
    arc.ArchiveFloat(&m_fWheelBackSuspension);
    arc.ArchiveFloat(&m_fCGHeight);
    arc.ArchiveFloat(&m_fBankAngle);
    arc.ArchiveFloat(&m_fTread);
    arc.ArchiveFloat(&m_fTrackWidth);
    arc.ArchiveFloat(&m_fTireFriction);
    arc.ArchiveFloat(&m_fDrag);
    arc.ArchiveFloat(&m_fTireRadius);
    arc.ArchiveFloat(&m_fFrontBrakes);
    arc.ArchiveFloat(&m_fBackBrakes);
    arc.ArchiveFloat(&m_fRollingResistance);
    arc.ArchiveFloat(&m_fTireRotationalSpeed);
    arc.ArchiveFloat(&m_fFrontBrakingForce);
    arc.ArchiveFloat(&m_fBackBrakingForce);
    arc.ArchiveFloat(&m_fBrakingPerformance);
    arc.ArchiveFloat(&m_fLastTurn);
    arc.ArchiveFloat(&m_fTangForce);
    arc.ArchiveFloat(&m_fInertia);
    arc.ArchiveFloat(&m_fDifferentialRatio);
    arc.ArchiveFloat(&m_fGearEfficiency);
    arc.ArchiveFloat(&m_fMaxTraction);
    arc.ArchiveFloat(&m_fTractionForce);
    arc.ArchiveFloat(&m_fAccelerator);
    arc.ArchiveFloat(&m_fTorque);
    arc.ArchiveFloat(&m_fDownForce);
    arc.ArchiveFloat(&m_fUpForce);
    arc.ArchiveFloat(&m_fLeftForce);
    arc.ArchiveFloat(&m_fRightForce);
    arc.ArchiveFloat(&m_fForwardForce);
    arc.ArchiveFloat(&m_fBackForce);
    arc.ArchiveFloat(&m_fBouncyCoef);
    arc.ArchiveFloat(&m_fSpringyCoef);
    arc.ArchiveFloat(&m_fYawMin);
    arc.ArchiveFloat(&m_fYawMax);
    arc.ArchiveFloat(&m_fRollMin);
    arc.ArchiveFloat(&m_fRollMax);
    arc.ArchiveFloat(&m_fZMin);
    arc.ArchiveFloat(&m_fZMax);
    arc.ArchiveFloat(&m_fYawCoef);
    arc.ArchiveFloat(&m_fRollCoef);
    arc.ArchiveFloat(&m_fZCoef);
    arc.ArchiveFloat(&m_fShaderOffset);
    arc.ArchiveFloat(&m_fSoundMinSpeed);
    arc.ArchiveFloat(&m_fSoundMinPitch);
    arc.ArchiveFloat(&m_fSoundMaxSpeed);
    arc.ArchiveFloat(&m_fSoundMaxPitch);
    arc.ArchiveFloat(&m_fVolumeMinSpeed);
    arc.ArchiveFloat(&m_fVolumeMinPitch);
    arc.ArchiveFloat(&m_fVolumeMaxSpeed);
    arc.ArchiveFloat(&m_fVolumeMaxPitch);
    arc.ArchiveFloat(&m_fStopStartDistance);
    arc.ArchiveFloat(&m_fStopStartSpeed);
    arc.ArchiveFloat(&m_fStopEndDistance);
    arc.ArchiveFloat(&m_fSkidAngle);
    arc.ArchiveFloat(&m_fSkidLeftForce);
    arc.ArchiveFloat(&m_fSkidRightForce);
    arc.ArchiveString(&weaponName);
    arc.ArchiveString(&m_sAnimationSet);
    arc.ArchiveString(&m_sSoundSet);
    arc.ArchiveString(&m_sExplosionModel);
    arc.ArchiveVector(&last_origin);
    arc.ArchiveVector(&seatangles);
    arc.ArchiveVector(&driveroffset);
    arc.ArchiveVector(&Corners[0]);
    arc.ArchiveVector(&Corners[1]);
    arc.ArchiveVector(&Corners[2]);
    arc.ArchiveVector(&Corners[3]);
    arc.ArchiveVector(&v_angle);
    arc.ArchiveVector(&yaw_forward);
    arc.ArchiveVector(&yaw_left);
    arc.ArchiveVector(&ground_normal);
    arc.ArchiveVector(&base_angles);
    arc.ArchiveVector(&prev_velocity);
    arc.ArchiveVector(&real_velocity);
    arc.ArchiveVector(&prev_origin);
    arc.ArchiveVector(&real_acceleration);
    arc.ArchiveVector(&prev_acceleration);
    arc.ArchiveVector(&m_vOldMins);
    arc.ArchiveVector(&m_vOldMaxs);
    arc.ArchiveVector(&m_vCG);
    arc.ArchiveVector(&m_vAngularAcceleration);
    arc.ArchiveVector(&m_vAngularVelocity);
    arc.ArchiveVector(&m_vAngles);
    arc.ArchiveVector(&m_vFrontNormal);
    arc.ArchiveVector(&m_vBackNormal);
    arc.ArchiveVector(&m_vFrontAngles);
    arc.ArchiveVector(&m_vBackAngles);
    arc.ArchiveVector(&m_vBaseNormal);
    arc.ArchiveVector(&m_vBaseAngles);
    arc.ArchiveVector(&m_vPrevNormal);
    arc.ArchiveVector(&m_vResistance);
    arc.ArchiveVector(&m_vWheelForce);
    arc.ArchiveVector(&m_vTangForce);
    arc.ArchiveVector(&m_vForce);
    arc.ArchiveVector(&m_vAcceleration);
    arc.ArchiveVector(&m_vOriginOffset);
    arc.ArchiveVector(&m_vOriginOffset2);
    arc.ArchiveVector(&m_vOriginCornerOffset);
    arc.ArchiveVector(&m_vAnglesOffset);
    arc.ArchiveVector(&m_vSaveAngles);
    arc.ArchiveVector(&m_vSkidOrigin);
    arc.ArchiveBoolean(&drivable);
    arc.ArchiveBoolean(&locked);
    arc.ArchiveBoolean(&hasweapon);
    arc.ArchiveBoolean(&showweapon);
    arc.ArchiveBoolean(&steerinplace);
    arc.ArchiveBoolean(&jumpable);
    arc.ArchiveBoolean(&m_bWheelSpinning);
    arc.ArchiveBoolean(&m_bIsSkidding);
    arc.ArchiveBoolean(&m_bIsBraking);
    arc.ArchiveBoolean(&m_bBackSlipping);
    arc.ArchiveBoolean(&m_bFrontSlipping);
    arc.ArchiveBoolean(&m_bAutomatic);
    arc.ArchiveBoolean(&m_bThinkCalled);
    arc.ArchiveBoolean(&m_bRemoveOnDeath);
    arc.ArchiveBoolean(&m_bStopEnabled);
    arc.ArchiveBoolean(&m_bEnableSkidding);

    arc.ArchiveVec3(vs.origin);
    arc.ArchiveVec3(vs.velocity);
    arc.ArchiveInteger(&vs.groundEntityNum);
    arc.ArchiveBoolean(&vs.walking);
    arc.ArchiveBoolean(&vs.groundPlane);
    G_ArchiveTrace(arc, &vs.groundTrace);
    arc.ArchiveInteger(&vs.entityNum);
    arc.ArchiveVec2(vs.desired_dir);
    arc.ArchiveBoolean(&vs.hit_obstacle);
    arc.ArchiveVec3(vs.hit_origin);
    arc.ArchiveVec3(vs.obstacle_normal);
    arc.ArchiveBoolean(&vs.useGravity);

    if (!arc.Saving()) {
        m_sMoveGrid = new cMoveGrid(3, 3, 1);
    }

    m_sMoveGrid->Archive(arc);

    arc.ArchiveFloat(&m_fIdealSpeed);
    arc.ArchiveFloat(&m_fMaxSpeed);       // Added in 2.30
    arc.ArchiveBool(&m_bBounceBackwards); // Added in 2.30
    arc.ArchiveVector(&m_vIdealPosition);
    arc.ArchiveVector(&m_vIdealDir);
    arc.ArchiveFloat(&m_fIdealAccel);
    arc.ArchiveFloat(&m_fIdealDistance);
    arc.ArchiveFloat(&m_fLookAhead);
    arc.ArchiveBool(&m_bAutoPilot);
    arc.ArchiveSafePointer(&m_pCollisionEntity);
    ArchiveEnum(m_eSoundState, SOUND_STATE);
    arc.ArchiveFloat(&m_fNextSoundState);
    arc.ArchiveSafePointer(&m_pVehicleSoundEntities[0]);
    arc.ArchiveSafePointer(&m_pVehicleSoundEntities[1]);
    arc.ArchiveSafePointer(&m_pVehicleSoundEntities[2]);
    arc.ArchiveSafePointer(&m_pVehicleSoundEntities[3]);

    // Added in 2.30
    arc.ArchiveFloat(&m_fMaxUseAngle);
    arc.ArchiveBool(&m_bBounceStayFullSpeed);
}

/*
====================
Vehicle::GetSoundSet
====================
*/
str Vehicle::GetSoundSet(void)
{
    return m_sSoundSet;
}

/*
====================
Vehicle::getName
====================
*/
const str& Vehicle::getName() const
{
    return vehicleName;
}

/*
====================
Vehicle::isLocked
====================
*/
qboolean Vehicle::isLocked(void)
{
    return locked;
}

/*
====================
Vehicle::Lock
====================
*/
void Vehicle::Lock(void)
{
    locked = true;
}

/*
====================
Vehicle::UnLock
====================
*/
void Vehicle::UnLock(void)
{
    locked = false;
}

/*
====================
Vehicle::GetCollisionEntity
====================
*/
VehicleCollisionEntity *Vehicle::GetCollisionEntity(void)
{
    return m_pCollisionEntity;
}

CLASS_DECLARATION(Vehicle, DrivableVehicle, "script_drivablevehicle") {
    {&EV_Damage, &Entity::DamageEvent    },
    {&EV_Killed, &DrivableVehicle::Killed},
    {NULL,       NULL                    }
};

/*
====================
DrivableVehicle::DrivableVehicle
====================
*/
DrivableVehicle::DrivableVehicle()
{
    AddWaitTill(STRING_DEATH);

    drivable = true;
    setMoveType(MOVETYPE_VEHICLE);
    flags |= FL_POSTTHINK | FL_THINK;
}

/*
====================
DrivableVehicle::Killed
====================
*/
void DrivableVehicle::Killed(Event *ev)
{
    Entity      *ent;
    Entity      *attacker;
    Vector       dir;
    Event       *event;
    const char  *name;
    VehicleBase *last;
    int          i;

    deadflag = DEAD_DEAD;
    if (!m_bRemoveOnDeath) {
        Unregister(STRING_DEATH);
        return;
    }

    takedamage = DAMAGE_NO;
    setSolidType(SOLID_NOT);
    hideModel();

    attacker = ev->GetEntity(1);

    //
    // kill the driver.ent
    //
    if (driver.ent) {
        Vector      dir;
        SentientPtr sent;
        Event      *event;

        velocity = vec_zero;
        sent     = static_cast<Sentient *>(driver.ent.Pointer());
        event    = new Event(EV_Use);
        event->AddEntity(sent);
        ProcessEvent(event);
        dir = sent->origin - origin;
        dir[2] += 64;
        dir.normalize();
        sent->Damage(this, this, sent->health * 2, origin, dir, vec_zero, 50, 0, MOD_VEHICLE);
    }

    //
    // kill all passengers
    //
    for (i = 0; i < MAX_PASSENGERS; i++) {
        if (Passengers[i].ent) {
            Vector      dir;
            SentientPtr sent;
            Event      *event;

            velocity = vec_zero;
            sent     = static_cast<Sentient *>(Passengers[i].ent.Pointer());
            event    = new Event(EV_Use);
            event->AddEntity(sent);
            ProcessEvent(event);
            dir = sent->origin - origin;
            dir[2] += 64;
            dir.normalize();
            sent->Damage(this, this, sent->health * 2, origin, dir, vec_zero, 50, 0, MOD_VEHICLE);
        }
    }

    //
    // remove all turrets
    //
    for (i = 0; i < MAX_TURRETS; i++) {
        if (Turrets[i].ent) {
            Turrets[i].ent->PostEvent(EV_Remove, EV_VEHICLE);
        }
    }

    if (flags & FL_DIE_EXPLODE) {
        CreateExplosion(origin, 150 * edict->s.scale, this, this, this);
    }

    if (flags & FL_DIE_GIBS) {
        setSolidType(SOLID_NOT);
        hideModel();

        CreateGibs(this, -150, edict->s.scale, 3);
    }
    //
    // kill all my wheels
    //
    last = this;
    while (last->vlink) {
        last->vlink->PostEvent(EV_Remove, EV_VEHICLE);
        last = last->vlink;
    }

    //
    // kill the killtargets
    //
    name = KillTarget();
    if (name && strcmp(name, "")) {
        for (ent = G_FindTarget(NULL, name); ent; ent = G_FindTarget(ent, name)) {
            ent->PostEvent(EV_Remove, EV_VEHICLE);
        }
    }

    //
    // fire targets
    //
    name = Target();
    if (name && strcmp(name, "")) {
        for (ent = G_FindTarget(NULL, name); ent; ent = G_FindTarget(ent, name)) {
            event = new Event(EV_Activate);
            event->AddEntity(attacker);
            ent->ProcessEvent(event);
        }
    }

    PostEvent(EV_Remove, EV_VEHICLE);
    Unregister(STRING_DEATH);
}
