git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/trunk@6306 178a84e3-b1eb-0310-8ba1-8eac791a3b58
1496 lines
57 KiB
C++
1496 lines
57 KiB
C++
// $Id$
|
|
//
|
|
// SuperTuxKart - a fun racing game with go-kart
|
|
// Copyright (C) 2004-2005 Steve Baker <sjbaker1@airmail.net>
|
|
// Copyright (C) 2006 SuperTuxKart-Team, Joerg Henrichs, Steve Baker
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 3
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
#include "karts/kart.hpp"
|
|
|
|
#include <math.h>
|
|
#include <iostream>
|
|
|
|
#include "audio/music_manager.hpp"
|
|
#include "audio/sfx_manager.hpp"
|
|
#include "audio/sfx_base.hpp"
|
|
#include "config/user_config.hpp"
|
|
#include "graphics/camera.hpp"
|
|
#include "graphics/material_manager.hpp"
|
|
#include "graphics/nitro.hpp"
|
|
#include "graphics/shadow.hpp"
|
|
#include "graphics/skid_marks.hpp"
|
|
#include "graphics/slip_stream.hpp"
|
|
#include "graphics/smoke.hpp"
|
|
#include "graphics/water_splash.hpp"
|
|
#include "modes/world.hpp"
|
|
#include "io/file_manager.hpp"
|
|
#include "items/item_manager.hpp"
|
|
#include "karts/controller/end_controller.hpp"
|
|
#include "karts/kart_model.hpp"
|
|
#include "karts/kart_properties_manager.hpp"
|
|
#include "network/race_state.hpp"
|
|
#include "network/network_manager.hpp"
|
|
#include "physics/btKart.hpp"
|
|
#include "physics/btUprightConstraint.hpp"
|
|
#include "physics/physics.hpp"
|
|
#include "race/history.hpp"
|
|
#include "tracks/track.hpp"
|
|
#include "utils/constants.hpp"
|
|
|
|
#if defined(WIN32) && !defined(__CYGWIN__)
|
|
// Disable warning for using 'this' in base member initializer list
|
|
# pragma warning(disable:4355)
|
|
#endif
|
|
|
|
/** The kart constructor.
|
|
* \param ident The identifier for the kart model to use.
|
|
* \param position The position (or rank) for this kart (between 1 and
|
|
* number of karts). This is used to determine the start position.
|
|
* \param init_transform The initial position and rotation for this kart.
|
|
*/
|
|
Kart::Kart (const std::string& ident, int position,
|
|
const btTransform& init_transform)
|
|
: TerrainInfo(1),
|
|
Moveable(), EmergencyAnimation(this), m_powerup(this)
|
|
|
|
#if defined(WIN32) && !defined(__CYGWIN__)
|
|
# pragma warning(1:4355)
|
|
#endif
|
|
{
|
|
m_kart_properties = kart_properties_manager->getKart(ident);
|
|
assert(m_kart_properties != NULL);
|
|
// We have to take a copy of the kart model, since otherwise
|
|
// the animations will be mixed up (i.e. different instances of
|
|
// the same model will set different animation frames).
|
|
// Technically the mesh in m_kart_model needs to be grab'ed and
|
|
// released when the kart is deleted, but since the original
|
|
// kart_model is stored in the kart_properties all the time,
|
|
// there is no risk of a mesh being deleted to early.
|
|
m_kart_model = m_kart_properties->getKartModelCopy();
|
|
m_initial_position = position;
|
|
m_race_position = position;
|
|
m_collected_energy = 0;
|
|
m_finished_race = false;
|
|
m_finish_time = 0.0f;
|
|
m_shadow_enabled = false;
|
|
m_shadow = NULL;
|
|
m_smoke_system = NULL;
|
|
m_water_splash_system = NULL;
|
|
m_nitro = NULL;
|
|
m_slipstream = NULL;
|
|
m_slipstream_time = 0.0f;
|
|
m_skidmarks = NULL;
|
|
m_camera = NULL;
|
|
m_controller = NULL;
|
|
m_saved_controller = NULL;
|
|
|
|
m_view_blocked_by_plunger = 0;
|
|
|
|
// Initialize custom sound vector (TODO: add back when properly done)
|
|
// m_custom_sounds.resize(SFXManager::NUM_CUSTOMS);
|
|
|
|
// Set position and heading:
|
|
m_reset_transform = init_transform;
|
|
m_speed = 0.0f;
|
|
m_wheel_rotation = 0;
|
|
|
|
// Create SFXBase for each custom sound (TODO: add back when properly done)
|
|
/*
|
|
for (int n = 0; n < SFXManager::NUM_CUSTOMS; n++)
|
|
{
|
|
int id = m_kart_properties->getCustomSfxId((SFXManager::CustomSFX)n);
|
|
|
|
// If id == -1 the custom sound was not defined in the .irrkart config file
|
|
if (id != -1)
|
|
{
|
|
m_custom_sounds[n] = sfx_manager->newSFX(id);
|
|
}
|
|
}*/
|
|
|
|
m_engine_sound = sfx_manager->createSoundSource(m_kart_properties->getEngineSfxType());
|
|
m_beep_sound = sfx_manager->createSoundSource( "beep" );
|
|
m_crash_sound = sfx_manager->createSoundSource( "crash" );
|
|
m_goo_sound = sfx_manager->createSoundSource( "goo" );
|
|
m_skid_sound = sfx_manager->createSoundSource( "skid" );
|
|
m_terrain_sound = NULL;
|
|
m_previous_terrain_sound = NULL;
|
|
|
|
if(!m_engine_sound)
|
|
{
|
|
fprintf(stdout, "Error: Could not allocate a sfx object for the kart. Further errors may ensue!\n");
|
|
}
|
|
|
|
loadData();
|
|
float length = m_kart_properties->getSlipstreamLength();
|
|
|
|
Vec3 p0(-getKartWidth()*0.5f, 0, -getKartLength()*0.5f );
|
|
Vec3 p1(-getKartWidth()*0.5f, 0, -getKartLength()*0.5f-length);
|
|
Vec3 p2( getKartWidth()*0.5f, 0, -getKartLength()*0.5f-length);
|
|
Vec3 p3( getKartWidth()*0.5f, 0, -getKartLength()*0.5f );
|
|
m_slipstream_original_quad = new Quad(p0, p1, p2, p3);
|
|
m_slipstream_quad = new Quad(p0, p1, p2, p3);
|
|
|
|
reset();
|
|
} // Kart
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Saves the old controller in m_saved_controller and stores a new
|
|
* controller. The save controller is needed in case of a reset.
|
|
* \param controller The new controller to use (atm it's always an
|
|
* end controller).
|
|
*/
|
|
void Kart::setController(Controller *controller)
|
|
{
|
|
assert(m_saved_controller==NULL);
|
|
m_saved_controller = m_controller;
|
|
m_controller = controller;
|
|
} // setController
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Returns a transform that will align an object with the kart: the heading
|
|
* and the pitch will be set appropriately. A custom pitch value can be
|
|
* specified in order to overwrite the terrain pitch (which would be used
|
|
* otherwise).
|
|
* \param customPitch Pitch value to overwrite the terrain pitch.
|
|
*/
|
|
btTransform Kart::getKartHeading(const float customPitch)
|
|
{
|
|
btTransform trans = getTrans();
|
|
|
|
float pitch = (customPitch == -1 ? getTerrainPitch(getHeading()) : customPitch);
|
|
|
|
btMatrix3x3 m;
|
|
m.setEulerYPR(-getHeading(), pitch, 0.0f);
|
|
trans.setBasis(m);
|
|
|
|
return trans;
|
|
} // getKartHeading
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Creates the physical representation of this kart. Atm it uses the actual
|
|
* extention of the kart model to determine the size of the collision body.
|
|
*/
|
|
void Kart::createPhysics()
|
|
{
|
|
// First: Create the chassis of the kart
|
|
// -------------------------------------
|
|
float kart_width = getKartWidth();
|
|
float kart_length = getKartLength();
|
|
float kart_height = getKartHeight();
|
|
|
|
btBoxShape *shape = new btBoxShape(btVector3(0.5f*kart_width,
|
|
0.5f*kart_height,
|
|
0.5f*kart_length));
|
|
btTransform shiftCenterOfGravity;
|
|
shiftCenterOfGravity.setIdentity();
|
|
// Shift center of gravity downwards, so that the kart
|
|
// won't topple over too easy.
|
|
shiftCenterOfGravity.setOrigin(m_kart_properties->getGravityCenterShift());
|
|
m_kart_chassis.addChildShape(shiftCenterOfGravity, shape);
|
|
|
|
// Set mass and inertia
|
|
// --------------------
|
|
float mass=getMass();
|
|
|
|
// Position the chassis
|
|
// --------------------
|
|
btTransform trans;
|
|
trans.setIdentity();
|
|
createBody(mass, trans, &m_kart_chassis);
|
|
m_user_pointer.set(this);
|
|
m_body->setDamping(m_kart_properties->getChassisLinearDamping(),
|
|
m_kart_properties->getChassisAngularDamping() );
|
|
|
|
// Reset velocities
|
|
// ----------------
|
|
m_body->setLinearVelocity (btVector3(0.0f,0.0f,0.0f));
|
|
m_body->setAngularVelocity(btVector3(0.0f,0.0f,0.0f));
|
|
|
|
// Create the actual vehicle
|
|
// -------------------------
|
|
m_vehicle_raycaster =
|
|
new btDefaultVehicleRaycaster(World::getWorld()->getPhysics()->getPhysicsWorld());
|
|
m_tuning = new btKart::btVehicleTuning();
|
|
m_tuning->m_maxSuspensionTravelCm = m_kart_properties->getSuspensionTravelCM();
|
|
m_vehicle = new btKart(*m_tuning, m_body, m_vehicle_raycaster,
|
|
m_kart_properties->getTrackConnectionAccel());
|
|
|
|
// never deactivate the vehicle
|
|
m_body->setActivationState(DISABLE_DEACTIVATION);
|
|
m_vehicle->setCoordinateSystem(/*right: */ 0, /*up: */ 1, /*forward: */ 2);
|
|
|
|
// Add wheels
|
|
// ----------
|
|
float wheel_radius = m_kart_properties->getWheelRadius();
|
|
float suspension_rest = m_kart_properties->getSuspensionRest();
|
|
|
|
btVector3 wheel_direction(0.0f, -1.0f, 0.0f);
|
|
btVector3 wheel_axle(-1.0f, 0.0f, 0.0f);
|
|
|
|
for(unsigned int i=0; i<4; i++)
|
|
{
|
|
bool is_front_wheel = i<2;
|
|
btWheelInfo& wheel = m_vehicle->addWheel(
|
|
m_kart_model->getWheelPhysicsPosition(i),
|
|
wheel_direction, wheel_axle, suspension_rest,
|
|
wheel_radius, *m_tuning, is_front_wheel);
|
|
wheel.m_suspensionStiffness = m_kart_properties->getSuspensionStiffness();
|
|
wheel.m_wheelsDampingRelaxation = m_kart_properties->getWheelDampingRelaxation();
|
|
wheel.m_wheelsDampingCompression = m_kart_properties->getWheelDampingCompression();
|
|
wheel.m_frictionSlip = m_kart_properties->getFrictionSlip();
|
|
wheel.m_rollInfluence = m_kart_properties->getRollInfluence();
|
|
}
|
|
// Obviously these allocs have to be properly managed/freed
|
|
btTransform t;
|
|
t.setIdentity();
|
|
m_uprightConstraint=new btUprightConstraint(this, t);
|
|
m_uprightConstraint->setLimit(m_kart_properties->getUprightTolerance());
|
|
m_uprightConstraint->setBounce(0.0f);
|
|
m_uprightConstraint->setMaxLimitForce(m_kart_properties->getUprightMaxForce());
|
|
m_uprightConstraint->setErp(1.0f);
|
|
m_uprightConstraint->setLimitSoftness(1.0f);
|
|
m_uprightConstraint->setDamping(0.0f);
|
|
World::getWorld()->getPhysics()->addKart(this);
|
|
|
|
} // createPhysics
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Starts the engine sound effect. Called once the track intro phase is over.
|
|
*/
|
|
void Kart::startEngineSFX()
|
|
{
|
|
if(m_engine_sound)
|
|
{
|
|
m_engine_sound->speed(0.6f);
|
|
m_engine_sound->setLoop(true);
|
|
m_engine_sound->play();
|
|
}
|
|
} // startEngineSFX
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** The destructor frees the memory of this kart, but note that the actual kart
|
|
* model is still stored in the kart_properties (m_kart_model variable), so
|
|
* it is not reloaded).
|
|
*/
|
|
Kart::~Kart()
|
|
{
|
|
// Delete all custom sounds (TODO: add back when properly done)
|
|
/*
|
|
for (int n = 0; n < SFXManager::NUM_CUSTOMS; n++)
|
|
{
|
|
if (m_custom_sounds[n] != NULL)
|
|
sfx_manager->deleteSFX(m_custom_sounds[n]);
|
|
}*/
|
|
|
|
sfx_manager->deleteSFX(m_engine_sound );
|
|
sfx_manager->deleteSFX(m_crash_sound );
|
|
sfx_manager->deleteSFX(m_skid_sound );
|
|
sfx_manager->deleteSFX(m_goo_sound );
|
|
if(m_terrain_sound) sfx_manager->deleteSFX(m_terrain_sound);
|
|
if(m_previous_terrain_sound) sfx_manager->deleteSFX(m_previous_terrain_sound);
|
|
if(m_smoke_system) delete m_smoke_system;
|
|
if(m_water_splash_system) delete m_water_splash_system;
|
|
if(m_nitro) delete m_nitro;
|
|
if(m_slipstream) delete m_slipstream;
|
|
|
|
delete m_shadow;
|
|
|
|
if(m_skidmarks) delete m_skidmarks ;
|
|
|
|
World::getWorld()->getPhysics()->removeKart(this);
|
|
delete m_vehicle;
|
|
delete m_tuning;
|
|
delete m_vehicle_raycaster;
|
|
delete m_uprightConstraint;
|
|
for(int i=0; i<m_kart_chassis.getNumChildShapes(); i++)
|
|
{
|
|
delete m_kart_chassis.getChildShape(i);
|
|
}
|
|
delete m_slipstream_original_quad;
|
|
delete m_slipstream_quad;
|
|
delete m_kart_model;
|
|
} // ~Kart
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Returns true if the kart is 'resting', i.e. (nearly) not moving.
|
|
*/
|
|
bool Kart::isInRest() const
|
|
{
|
|
return fabs(m_body->getLinearVelocity ().y())<0.2;
|
|
} // isInRest
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Multiplies the velocity of the kart by a factor f (both linear
|
|
* and angular). This is used by anvils, which suddenly slow down the kart
|
|
* when they are attached.
|
|
*/
|
|
void Kart::adjustSpeed(float f)
|
|
{
|
|
m_body->setLinearVelocity(m_body->getLinearVelocity()*f);
|
|
m_body->setAngularVelocity(m_body->getAngularVelocity()*f);
|
|
} // adjustSpeed
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** This method is to be called every time the mass of the kart is updated,
|
|
* which includes attaching an anvil to the kart (and detaching).
|
|
*/
|
|
void Kart::updatedWeight()
|
|
{
|
|
// getMass returns the mass increased by the attachment
|
|
btVector3 inertia;
|
|
float m=getMass();
|
|
m_kart_chassis.calculateLocalInertia(m, inertia);
|
|
m_body->setMassProps(m, inertia);
|
|
} // updatedWeight
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Reset before a new race. It will remove all attachments, and
|
|
* puts the kart back at its original start position.
|
|
*/
|
|
void Kart::reset()
|
|
{
|
|
EmergencyAnimation::reset();
|
|
|
|
if (m_camera)
|
|
{
|
|
m_camera->reset();
|
|
m_camera->setInitialTransform();
|
|
}
|
|
|
|
// Stop any animations currently being played.
|
|
m_kart_model->setAnimation(KartModel::AF_DEFAULT);
|
|
// If the controller was replaced (e.g. replaced by end controller),
|
|
// restore the original controller.
|
|
if(m_saved_controller)
|
|
{
|
|
m_controller = m_saved_controller;
|
|
m_saved_controller = NULL;
|
|
}
|
|
m_kart_model->setAnimation(KartModel::AF_DEFAULT);
|
|
m_view_blocked_by_plunger = 0.0;
|
|
m_attachment->clear();
|
|
m_nitro->setCreationRate(0.0f);
|
|
m_powerup.reset();
|
|
|
|
m_race_position = m_initial_position;
|
|
m_finished_race = false;
|
|
m_finish_time = 0.0f;
|
|
m_zipper_time_left = 0.0f;
|
|
m_collected_energy = 0;
|
|
m_has_started = false;
|
|
m_wheel_rotation = 0;
|
|
m_bounce_back_time = 0.0f;
|
|
m_skidding = 1.0f;
|
|
m_time_last_crash = 0.0f;
|
|
m_current_speed_fraction = 1.0f;
|
|
m_slipstream_mode = SS_NONE;
|
|
m_last_material = NULL;
|
|
if(m_terrain_sound)
|
|
{
|
|
sfx_manager->deleteSFX(m_terrain_sound);
|
|
}
|
|
if(m_previous_terrain_sound)
|
|
{
|
|
sfx_manager->deleteSFX(m_previous_terrain_sound);
|
|
}
|
|
|
|
m_terrain_sound = NULL;
|
|
m_previous_terrain_sound = NULL;
|
|
|
|
if(m_engine_sound)
|
|
m_engine_sound->stop();
|
|
|
|
m_controls.m_steer = 0.0f;
|
|
m_controls.m_accel = 0.0f;
|
|
m_controls.m_brake = false;
|
|
m_controls.m_nitro = false;
|
|
m_controls.m_drift = false;
|
|
m_controls.m_fire = false;
|
|
m_controls.m_look_back = false;
|
|
m_slipstream->reset();
|
|
m_vehicle->deactivateZipper();
|
|
|
|
// Set the brakes so that karts don't slide downhill
|
|
for(int i=0; i<4; i++) m_vehicle->setBrake(5.0f, i);
|
|
|
|
setTrans(m_reset_transform);
|
|
|
|
applyEngineForce (0.0f);
|
|
|
|
Moveable::reset();
|
|
if(m_skidmarks) m_skidmarks->reset();
|
|
for(int j=0; j<m_vehicle->getNumWheels(); j++)
|
|
{
|
|
m_vehicle->updateWheelTransform(j, true);
|
|
}
|
|
|
|
TerrainInfo::update(getXYZ());
|
|
|
|
// Reset is also called when the kart is created, at which time
|
|
// m_controller is not yet defined, so this has to be tested here.
|
|
if(m_controller)
|
|
m_controller->reset();
|
|
|
|
} // reset
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Sets that this kart has finished the race and finishing time. It also
|
|
* notifies the race_manager about the race completion for this kart.
|
|
* \param time The finishing time for this kart. It can either be the
|
|
* actual time when the kart finished (in which case time() =
|
|
* world->getTime()), or the estimated time in case that all
|
|
* player kart have finished the race and all AI karts get
|
|
* an estimated finish time set.
|
|
*/
|
|
void Kart::finishedRace(float time)
|
|
{
|
|
// m_finished_race can be true if e.g. an AI kart was set to finish
|
|
// because the race was over (i.e. estimating the finish time). If
|
|
// this kart then crosses the finish line (with the end controller)
|
|
// it would trigger a race end again.
|
|
if(m_finished_race) return;
|
|
m_finished_race = true;
|
|
m_finish_time = time;
|
|
m_controller->finishedRace(time);
|
|
race_manager->kartFinishedRace(this, time);
|
|
|
|
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_NORMAL_RACE ||
|
|
race_manager->getMinorMode() == RaceManager::MINOR_MODE_TIME_TRIAL)
|
|
{
|
|
// in modes that support it, start end animation
|
|
setController(new EndController(this, m_controller->getPlayer()));
|
|
if(m_race_position<=0.5f*race_manager->getNumberOfKarts() ||
|
|
m_race_position==1)
|
|
m_kart_model->setAnimation(KartModel::AF_WIN_START);
|
|
else
|
|
m_kart_model->setAnimation(KartModel::AF_LOSE_START);
|
|
|
|
// Not all karts have a camera
|
|
if (m_camera) m_camera->setMode(Camera::CM_FINAL);
|
|
|
|
RaceGUIBase* m = World::getWorld()->getRaceGUI();
|
|
if(m)
|
|
{
|
|
m->addMessage((getPosition() == 1 ? _("You won the race!") : _("You finished the race!")) ,
|
|
this, 2.0f, 60);
|
|
}
|
|
}
|
|
else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_FOLLOW_LEADER)
|
|
{
|
|
// start end animation
|
|
setController(new EndController(this, m_controller->getPlayer()));
|
|
if(m_race_position<=2)
|
|
m_kart_model->setAnimation(KartModel::AF_WIN_START);
|
|
else if(m_race_position>=0.7f*race_manager->getNumberOfKarts())
|
|
m_kart_model->setAnimation(KartModel::AF_LOSE_START);
|
|
|
|
// Not all karts have a camera
|
|
if (m_camera) m_camera->setMode(Camera::CM_REVERSE);
|
|
|
|
RaceGUIBase* m = World::getWorld()->getRaceGUI();
|
|
if(m)
|
|
{
|
|
m->addMessage((getPosition() == 2 ? _("You won the race!") : _("You finished the race!")) ,
|
|
this, 2.0f, 60);
|
|
}
|
|
}
|
|
else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES)
|
|
{
|
|
}
|
|
|
|
} // finishedRace
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Called when an item is collected. It will either adjust the collected
|
|
* energy, or update the attachment or powerup for this kart.
|
|
* \param item The item that was hit.
|
|
* \param add_info Additional info, used in networking games to force
|
|
* a specific item to be used (instead of a random item) to keep
|
|
* all karts in synch.
|
|
*/
|
|
void Kart::collectedItem(Item *item, int add_info)
|
|
{
|
|
float old_energy = m_collected_energy;
|
|
const Item::ItemType type = item->getType();
|
|
|
|
switch (type)
|
|
{
|
|
case Item::ITEM_BANANA:
|
|
m_attachment->hitBanana(item, add_info);
|
|
break;
|
|
case Item::ITEM_NITRO_SMALL:
|
|
m_collected_energy += m_kart_properties->getNitroSmallContainer();
|
|
break;
|
|
case Item::ITEM_NITRO_BIG:
|
|
m_collected_energy += m_kart_properties->getNitroBigContainer();
|
|
break;
|
|
case Item::ITEM_BONUS_BOX :
|
|
{
|
|
m_powerup.hitBonusBox(*item, add_info);
|
|
break;
|
|
}
|
|
case Item::ITEM_BUBBLEGUM:
|
|
// slow down
|
|
m_body->setLinearVelocity(m_body->getLinearVelocity()*0.3f);
|
|
m_goo_sound->position(getXYZ());
|
|
m_goo_sound->play();
|
|
// Play appropriate custom character sound
|
|
playCustomSFX(SFXManager::CUSTOM_GOO);
|
|
break;
|
|
default : break;
|
|
} // switch TYPE
|
|
|
|
// Attachments and powerups are stored in the corresponding
|
|
// functions (hit{Red,Green}Item), so only coins need to be
|
|
// stored here.
|
|
if(network_manager->getMode()==NetworkManager::NW_SERVER &&
|
|
(type==Item::ITEM_NITRO_BIG || type==Item::ITEM_NITRO_SMALL) )
|
|
{
|
|
race_state->itemCollected(getWorldKartId(), item->getItemId());
|
|
}
|
|
|
|
if ( m_collected_energy > MAX_ITEMS_COLLECTED )
|
|
m_collected_energy = MAX_ITEMS_COLLECTED;
|
|
m_controller->collectedItem(*item, add_info, old_energy);
|
|
|
|
} // collectedItem
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Simulates gears by adjusting the force of the engine. It also takes the
|
|
* effect of the zipper into account.
|
|
*/
|
|
float Kart::getActualWheelForce()
|
|
{
|
|
float zipperF=(m_zipper_time_left>0.0f) ? stk_config->m_zipper_force : 0.0f;
|
|
const std::vector<float>& gear_ratio=m_kart_properties->getGearSwitchRatio();
|
|
for(unsigned int i=0; i<gear_ratio.size(); i++)
|
|
{
|
|
if(m_speed <= m_kart_properties->getMaxSpeed()*gear_ratio[i])
|
|
{
|
|
return getMaxPower()*m_kart_properties->getGearPowerIncrease()[i]
|
|
+zipperF;
|
|
}
|
|
}
|
|
return getMaxPower()+zipperF;
|
|
|
|
} // getActualWheelForce
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** The kart is on ground if all 4 wheels touch the ground, and if no special
|
|
* animation (rescue, explosion etc.) is happening).
|
|
*/
|
|
bool Kart::isOnGround() const
|
|
{
|
|
return (m_vehicle->getNumWheelsOnGround() == m_vehicle->getNumWheels()
|
|
&& !playingEmergencyAnimation());
|
|
} // isOnGround
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** The kart is near the ground, but not necesarily on it (small jumps). This
|
|
* is used to determine when to switch off the upright constraint, so that
|
|
* explosions can be more violent, while still
|
|
*/
|
|
bool Kart::isNearGround() const
|
|
{
|
|
if(getHoT()==Track::NOHIT)
|
|
return false;
|
|
else
|
|
return ((getXYZ().getY() - getHoT()) < stk_config->m_near_ground);
|
|
} // isNearGround
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Updates the kart in each time step. It updates the physics setting,
|
|
* particle effects, camera position, etc.
|
|
* \param dt Time step size.
|
|
*/
|
|
void Kart::update(float dt)
|
|
{
|
|
// Update the position and other data taken from the physics
|
|
Moveable::update(dt);
|
|
|
|
if(!history->replayHistory())
|
|
m_controller->update(dt);
|
|
if(m_camera)
|
|
m_camera->update(dt);
|
|
// if its view is blocked by plunger, decrease remaining time
|
|
if(m_view_blocked_by_plunger > 0) m_view_blocked_by_plunger -= dt;
|
|
|
|
// Store the actual kart controls at the start of update in the server
|
|
// state. This makes it easier to reset some fields when they are not used
|
|
// anymore (e.g. controls.fire).
|
|
if(network_manager->getMode()==NetworkManager::NW_SERVER)
|
|
{
|
|
race_state->storeKartControls(*this);
|
|
}
|
|
|
|
// On a client fiering is done upon receiving the command from the server.
|
|
if ( m_controls.m_fire && network_manager->getMode()!=NetworkManager::NW_CLIENT
|
|
&& !playingEmergencyAnimation())
|
|
{
|
|
// use() needs to be called even if there currently is no collecteable
|
|
// since use() can test if something needs to be switched on/off.
|
|
m_powerup.use() ;
|
|
m_controls.m_fire = false;
|
|
}
|
|
|
|
// When really on air, free fly, when near ground, try to glide / adjust for landing
|
|
// If zipped, be stable, so ramp+zipper can allow nice jumps without scripting the fly
|
|
if(!isNearGround() && !(m_zipper_time_left > 0.0f))
|
|
m_uprightConstraint->setLimit(M_PI);
|
|
else
|
|
m_uprightConstraint->setLimit(m_kart_properties->getUprightTolerance());
|
|
|
|
// TODO: hiker said this probably will be moved to btKart or so when updating bullet engine.
|
|
// Neutralize any yaw change if the kart leaves the ground, so the kart falls more or less
|
|
// straight after jumping, but still allowing some "boat shake" (roll and pitch).
|
|
// Otherwise many non perfect jumps end in a total roll over or a serious change of
|
|
// direction, sometimes 90 or even full U turn (real but less fun for a karting game).
|
|
// As side effect steering becames a bit less responsive (any wheel on air), but not too bad.
|
|
if(!isOnGround()) {
|
|
btVector3 speed = m_body->getAngularVelocity();
|
|
speed.setX(speed.getX() * 0.95f);
|
|
speed.setY(speed.getY() * 0.25f); // or 0.0f for sharp neutralization of yaw
|
|
speed.setZ(speed.getZ() * 0.95f);
|
|
m_body->setAngularVelocity(speed);
|
|
// This one keeps the kart pointing "100% as launched" instead,
|
|
// like in ski jump sports, too boring but also works.
|
|
//m_body->setAngularVelocity(btVector3(0,0,0));
|
|
}
|
|
|
|
m_zipper_time_left = m_zipper_time_left>0.0f ? m_zipper_time_left-dt : 0.0f;
|
|
|
|
//m_wheel_rotation gives the rotation around the X-axis, and since velocity's
|
|
//timeframe is the delta time, we don't have to multiply it with dt.
|
|
m_wheel_rotation += m_speed*dt / m_kart_properties->getWheelRadius();
|
|
m_wheel_rotation=fmodf(m_wheel_rotation, 2*M_PI);
|
|
|
|
EmergencyAnimation::update(dt);
|
|
|
|
m_attachment->update(dt);
|
|
|
|
//smoke drawing control point
|
|
if ( UserConfigParams::m_graphical_effects )
|
|
{
|
|
m_smoke_system->update(dt);
|
|
m_water_splash_system->update(dt);
|
|
m_nitro->update(dt);
|
|
m_slipstream->update(dt);
|
|
} // UserConfigParams::m_graphical_effects
|
|
|
|
updatePhysics(dt);
|
|
|
|
/* (TODO: add back when properly done)
|
|
for (int n = 0; n < SFXManager::NUM_CUSTOMS; n++)
|
|
{
|
|
if (m_custom_sounds[n] != NULL) m_custom_sounds[n]->position ( getXYZ() );
|
|
}
|
|
*/
|
|
|
|
m_beep_sound->position ( getXYZ() );
|
|
m_engine_sound->position ( getXYZ() );
|
|
m_crash_sound->position ( getXYZ() );
|
|
m_skid_sound->position ( getXYZ() );
|
|
|
|
// Check if a kart is (nearly) upside down and not moving much --> automatic rescue
|
|
if((fabs(getRoll())>60*DEGREE_TO_RAD && fabs(getSpeed())<3.0f) )
|
|
{
|
|
forceRescue();
|
|
}
|
|
|
|
btTransform trans=getTrans();
|
|
// Add a certain epsilon (0.3) to the height of the kart. This avoids
|
|
// problems of the ray being cast from under the track (which happened
|
|
// e.g. on tux tollway when jumping down from the ramp, when the chassis
|
|
// partly tunnels through the track). While tunneling should not be
|
|
// happening (since Z velocity is clamped), the epsilon is left in place
|
|
// just to be on the safe side (it will not hit the chassis itself).
|
|
Vec3 pos_plus_epsilon = trans.getOrigin()+btVector3(0,0.3f,0);
|
|
|
|
// Make sure that the ray doesn't hit the kart. This is done by
|
|
// resetting the collision filter group, so that this collision
|
|
// object is ignored during raycasting.
|
|
short int old_group = 0;
|
|
if(m_body->getBroadphaseHandle())
|
|
{
|
|
old_group = m_body->getBroadphaseHandle()->m_collisionFilterGroup;
|
|
m_body->getBroadphaseHandle()->m_collisionFilterGroup = 0;
|
|
}
|
|
TerrainInfo::update(pos_plus_epsilon);
|
|
if(m_body->getBroadphaseHandle())
|
|
{
|
|
m_body->getBroadphaseHandle()->m_collisionFilterGroup = old_group;
|
|
}
|
|
const Material* material=TerrainInfo::getMaterial();
|
|
if (getHoT()==Track::NOHIT) // kart falling off the track
|
|
{
|
|
// let kart fall a bit before rescuing
|
|
const Vec3 *min, *max;
|
|
World::getWorld()->getTrack()->getAABB(&min, &max);
|
|
if(min->getY() - getXYZ().getY() > 17)
|
|
forceRescue();
|
|
}
|
|
|
|
// Sometimes the material can be 0. This can happen if a kart is above
|
|
// another kart (e.g. mass collision, or one kart falling on another
|
|
// kart). Bullet does not have any triangle information in this case,
|
|
// and so material can not be set. In this case it is simply ignored
|
|
// since it can't hurt (material is only used for friction, zipper and
|
|
// rescue, so those things are not triggered till the kart is on the
|
|
// track again)
|
|
else if(material)
|
|
{
|
|
// If a terrain specific sfx is already being played, when a new
|
|
// terrain is entered, an old sfx should be finished (once, not
|
|
// looped anymore of course). The m_terrain_sound is then copied
|
|
// to a m_previous_terrain_sound, for which looping is disabled.
|
|
// In case that three sfx needed to be played (i.e. a previous is
|
|
// playing, a current is playing, and a new terrain with sfx is
|
|
// entered), the oldest (previous) sfx is stopped and deleted.
|
|
if(m_last_material!=material)
|
|
{
|
|
// First stop any previously playing terrain sound
|
|
// and remove it, sp that m_previous_terrain_sound
|
|
// can be used again.
|
|
if(m_previous_terrain_sound)
|
|
{
|
|
sfx_manager->deleteSFX(m_previous_terrain_sound);
|
|
}
|
|
m_previous_terrain_sound = m_terrain_sound;
|
|
if(m_previous_terrain_sound)
|
|
m_previous_terrain_sound->setLoop(false);
|
|
|
|
const std::string s = material->getSFXName();
|
|
if(s!="")
|
|
{
|
|
m_terrain_sound = sfx_manager->createSoundSource(s);
|
|
m_terrain_sound->play();
|
|
m_terrain_sound->setLoop(true);
|
|
}
|
|
else
|
|
m_terrain_sound = NULL;
|
|
}
|
|
if(m_previous_terrain_sound &&
|
|
m_previous_terrain_sound->getStatus()==SFXManager::SFX_STOPED)
|
|
{
|
|
// We don't modify the position of m_previous_terrain_sound
|
|
// anymore, so that it keeps on playing at the place where the
|
|
// kart left the material.
|
|
sfx_manager->deleteSFX(m_previous_terrain_sound);
|
|
m_previous_terrain_sound = NULL;
|
|
}
|
|
if(m_terrain_sound)
|
|
{
|
|
m_terrain_sound->position(getXYZ());
|
|
material->setSFXSpeed(m_terrain_sound, m_speed);
|
|
}
|
|
m_last_material = material;
|
|
|
|
if (material->isReset() && isOnGround()) forceRescue();
|
|
else if(material->isZipper() && isOnGround()) handleZipper();
|
|
else
|
|
{
|
|
// Normal driving on terrain. Adjust for maximum terrain speed
|
|
// Gradually adjust the fraction of the maximum kart speed to
|
|
// the amount specified for the terrain.
|
|
// The actual capping happens in updatePhysics
|
|
if(m_current_speed_fraction<=material->getMaxSpeedFraction())
|
|
m_current_speed_fraction = material->getMaxSpeedFraction();
|
|
else
|
|
m_current_speed_fraction -= dt/material->getSlowDownTime();
|
|
}
|
|
} // if there is material
|
|
|
|
// Check if any item was hit.
|
|
item_manager->checkItemHit(this);
|
|
if(m_kart_properties->hasSkidmarks())
|
|
m_skidmarks->update(dt);
|
|
|
|
// Remove the shadow if the kart is not on the ground (if a kart
|
|
// is rescued isOnGround might still be true, since the kart rigid
|
|
// body was removed from the physics, but still retain the old
|
|
// values for the raycasts).
|
|
if( (!isOnGround() || playingEmergencyAnimation()) && m_shadow_enabled)
|
|
{
|
|
m_shadow_enabled = false;
|
|
m_shadow->disableShadow();
|
|
}
|
|
if(!m_shadow_enabled && isOnGround() && !playingEmergencyAnimation())
|
|
{
|
|
m_shadow->enableShadow();
|
|
m_shadow_enabled = true;
|
|
}
|
|
} // update
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Sets zipper time, and apply one time additional speed boost.
|
|
*/
|
|
void Kart::handleZipper()
|
|
{
|
|
// Ignore a zipper that's activated while braking
|
|
if(m_controls.m_brake) return;
|
|
m_zipper_time_left = stk_config->m_zipper_time;
|
|
|
|
btVector3 v = m_body->getLinearVelocity();
|
|
float current_speed = v.length();
|
|
float speed = std::min(current_speed+stk_config->m_zipper_speed_gain,
|
|
getMaxSpeedOnTerrain() *
|
|
(1 + stk_config->m_zipper_max_speed_fraction));
|
|
|
|
m_vehicle->activateZipper(speed);
|
|
// Play custom character sound (weee!)
|
|
playCustomSFX(SFXManager::CUSTOM_ZIPPER);
|
|
m_controller->handleZipper();
|
|
} // handleZipper
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Returned an additional engine power boost when using nitro.
|
|
* \param dt Time step size.
|
|
*/
|
|
float Kart::handleNitro(float dt)
|
|
{
|
|
if(!m_controls.m_nitro) return 0.0;
|
|
m_collected_energy -= dt * m_kart_properties->getNitroConsumption();
|
|
if(m_collected_energy<0)
|
|
{
|
|
m_collected_energy = 0;
|
|
return 0.0;
|
|
}
|
|
return m_kart_properties->getNitroPowerBoost() * getMaxPower();
|
|
|
|
} // handleNitro
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** This function manages slipstreaming. It adds up the time a kart was
|
|
* slipstreaming, and if a kart was slipstreaming long enough, it will
|
|
* add power to the kart for a certain amount of time.
|
|
*/
|
|
float Kart::handleSlipstream(float dt)
|
|
{
|
|
// Update this karts slipstream quad (even for low level AI which don't
|
|
// use slipstream, since even then player karts can get slipstream,
|
|
// and so have to compare with the modified slipstream quad.
|
|
m_slipstream_original_quad->transform(getTrans(), m_slipstream_quad);
|
|
|
|
// Low level AIs should not do any slipstreaming.
|
|
if(!getController()->isPlayerController() &&
|
|
race_manager->getDifficulty()==RaceManager::RD_EASY) return 0;
|
|
|
|
// First see if we are currently using accumulated slipstream credits:
|
|
// -------------------------------------------------------------------
|
|
if(m_slipstream_mode==SS_USE)
|
|
{
|
|
m_slipstream_time -= dt;
|
|
if(m_slipstream_time<0) m_slipstream_mode=SS_NONE;
|
|
m_slipstream->setIntensity(2.0f, NULL);
|
|
return m_kart_properties->getSlipstreamAddPower();
|
|
}
|
|
|
|
// If this kart is too slow for slipstreaming taking effect, do nothing
|
|
// --------------------------------------------------------------------
|
|
|
|
// Define this to get slipstream effect shown even when the karts are
|
|
// not moving. This is useful for debugging the graphics of SS-ing.
|
|
#undef DISPLAY_SLIPSTREAM_WITH_0_SPEED_FOR_DEBUGGING
|
|
#ifndef DISPLAY_SLIPSTREAM_WITH_0_SPEED_FOR_DEBUGGING
|
|
if(getSpeed()<m_kart_properties->getSlipstreamMinSpeed())
|
|
{
|
|
m_slipstream->setIntensity(0, NULL);
|
|
return 0;
|
|
}
|
|
#endif
|
|
// Then test if this kart is in the slipstream range of another kart:
|
|
// ------------------------------------------------------------------
|
|
World *world = World::getWorld();
|
|
unsigned int num_karts = world->getNumKarts();
|
|
bool is_sstreaming = false;
|
|
m_slipstream_target = NULL;
|
|
// Note that this loop can not be simply replaced with a shorter loop
|
|
// using only the karts with a better position - since a kart might
|
|
// be a lap behind
|
|
for(unsigned int i=0; i<num_karts; i++)
|
|
{
|
|
m_slipstream_target= world->getKart(i);
|
|
// Don't test for slipstream with itself.
|
|
if(m_slipstream_target==this ||
|
|
m_slipstream_target->isEliminated() ) continue;
|
|
|
|
// If the kart we are testing against is too slow, no need to test
|
|
// slipstreaming. Note: We compare the speed of the other kart
|
|
// against the minimum slipstream speed kart of this kart - not
|
|
// entirely sure if this makes sense, but it makes it easier to
|
|
// give karts different slipstream properties.
|
|
#ifndef DISPLAY_SLIPSTREAM_WITH_0_SPEED_FOR_DEBUGGING
|
|
if(m_slipstream_target->getSpeed() <
|
|
m_kart_properties->getSlipstreamMinSpeed())
|
|
continue;
|
|
#endif
|
|
// Quick test: the kart must be not more than
|
|
// slipstream length+kart_length() away from the other kart
|
|
Vec3 delta = getXYZ() - m_slipstream_target->getXYZ();
|
|
float l = m_slipstream_target->m_kart_properties->getSlipstreamLength()
|
|
+ m_slipstream_target->getKartLength()*0.5f;
|
|
if(delta.length2_2d() > l*l) continue;
|
|
// Real test: if in slipstream quad of other kart
|
|
if(m_slipstream_target->m_slipstream_quad->pointInQuad(getXYZ()))
|
|
{
|
|
is_sstreaming = true;
|
|
break;
|
|
}
|
|
} // for i < num_karts
|
|
|
|
if(!is_sstreaming)
|
|
{
|
|
if(isSlipstreamReady())
|
|
{
|
|
// The first time slipstream is ready after collecting
|
|
// and you are leaving the slipstream area, you get a
|
|
// zipper bonus.
|
|
if(m_slipstream_mode==SS_COLLECT)
|
|
{
|
|
m_slipstream_mode = SS_USE;
|
|
handleZipper();
|
|
// And the time is set to a maximum (to avoid that e.g. AI
|
|
// karts slipstream for a long time and then get a very long
|
|
// bonus).
|
|
m_slipstream_time =
|
|
m_kart_properties->getSlipstreamCollectTime();
|
|
return m_kart_properties->getSlipstreamAddPower();
|
|
}
|
|
}
|
|
m_slipstream_time -=dt;
|
|
if(m_slipstream_time<0) m_slipstream_mode = SS_NONE;
|
|
m_slipstream->setIntensity(0, NULL);
|
|
return 0;
|
|
} // for i<number of karts
|
|
|
|
// Accumulate slipstream credits now
|
|
m_slipstream_time = m_slipstream_mode==SS_NONE ? dt
|
|
: m_slipstream_time+dt;
|
|
//printf("Collecting slipstream %f\n", m_slipstream_time);
|
|
if(isSlipstreamReady())
|
|
m_nitro->setCreationRate(3.0f);
|
|
m_slipstream->setIntensity(m_slipstream_time, m_slipstream_target);
|
|
|
|
m_slipstream_mode = SS_COLLECT;
|
|
if(m_slipstream_time>m_kart_properties->getSlipstreamCollectTime())
|
|
{
|
|
m_slipstream->setIntensity(1.0f, m_slipstream_target);
|
|
return 0; // see below about abusing m_zipper_time_left without zipper
|
|
//return m_kart_properties->getSlipstreamAddPower();
|
|
}
|
|
return 0;
|
|
|
|
} // handleSlipstream
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Returns true if enough slipstream credits have been accumulated
|
|
* to get a boost when leaving the slipstream area.
|
|
*/
|
|
bool Kart::isSlipstreamReady() const
|
|
{
|
|
return m_slipstream_time>m_kart_properties->getSlipstreamCollectTime();
|
|
} // isSlipstreamReady
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** This function is called when the race starts. Up to then all brakes are
|
|
braking (to avoid the kart from rolling downhill), but they need to be set
|
|
to zero (otherwise the brakes will be braking whenever no engine force
|
|
is set, i.e. the kart is not accelerating).
|
|
*/
|
|
void Kart::resetBrakes()
|
|
{
|
|
for(int i=0; i<4; i++) m_vehicle->setBrake(0.0f, i);
|
|
} // resetBrakes
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Called when the kart crashes against the track (k=NULL) or another kart.
|
|
* \params k Either a kart if a kart was hit, or NULL if the track was hit.
|
|
*/
|
|
void Kart::crashed(Kart *k)
|
|
{
|
|
m_controller->crashed();
|
|
/** If a kart is crashing against the track, the collision is often
|
|
* reported more than once, resulting in a machine gun effect, and too
|
|
* long disabling of the engine. Therefore, this reaction is disabled
|
|
* for 0.5 seconds after a crash.
|
|
*/
|
|
if(World::getWorld()->getTime()-m_time_last_crash < 0.5f) return;
|
|
|
|
m_time_last_crash = World::getWorld()->getTime();
|
|
// After a collision disable the engine for a short time so that karts
|
|
// can 'bounce back' a bit (without this the engine force will prevent
|
|
// karts from bouncing back, they will instead stuck towards the obstable).
|
|
if(m_bounce_back_time<=0.0f)
|
|
{
|
|
// In case that the sfx is longer than 0.5 seconds, only play it if
|
|
// it's not already playing.
|
|
if(m_crash_sound->getStatus() != SFXManager::SFX_PLAYING)
|
|
m_crash_sound->play();
|
|
|
|
m_bounce_back_time = 0.1f;
|
|
}
|
|
} // crashed
|
|
|
|
// -----------------------------------------------------------------------------
|
|
void Kart::beep()
|
|
{
|
|
// If the custom horn can't play (isn't defined) then play the default one
|
|
if (!playCustomSFX(SFXManager::CUSTOM_HORN))
|
|
m_beep_sound->play();
|
|
|
|
} // beep
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/*
|
|
playCustomSFX()
|
|
|
|
This function will play a particular character voice for this kart. It
|
|
returns whether or not a character voice sample exists for the particular
|
|
event. If there is no voice sample, a default can be played instead.
|
|
|
|
Use entries from the CustomSFX enumeration as a parameter (see
|
|
sfx_manager.hpp). eg. playCustomSFX(SFXManager::CUSTOM_CRASH)
|
|
|
|
Obviously we don't want a certain character voicing multiple phrases
|
|
simultaneously. It just sounds bad. There are two ways of avoiding this:
|
|
|
|
1. If there is already a voice sample playing for the character
|
|
don't play another until it is finished.
|
|
|
|
2. If there is already a voice sample playing for the character
|
|
stop the sample, and play the new one.
|
|
|
|
Currently we're doing #2.
|
|
|
|
rforder
|
|
|
|
*/
|
|
|
|
bool Kart::playCustomSFX(unsigned int type)
|
|
{
|
|
// (TODO: add back when properly done)
|
|
return false;
|
|
|
|
/*
|
|
bool ret = false;
|
|
|
|
// Stop all other character voices for this kart before playing a new one
|
|
// we don't want overlapping phrases coming from the same kart
|
|
for (unsigned int n = 0; n < SFXManager::NUM_CUSTOMS; n++)
|
|
{
|
|
if (m_custom_sounds[n] != NULL)
|
|
{
|
|
// If the sound we're trying to play is already playing
|
|
// don't stop it, we'll just let it finish.
|
|
if (type != n) m_custom_sounds[n]->stop();
|
|
}
|
|
}
|
|
|
|
if (type < SFXManager::NUM_CUSTOMS)
|
|
{
|
|
if (m_custom_sounds[type] != NULL)
|
|
{
|
|
ret = true;
|
|
//printf("Kart SFX: playing %s for %s.\n", sfx_manager->getCustomTagName(type), m_kart_properties->getIdent().c_str());
|
|
// If it's already playing, let it finish
|
|
if (m_custom_sounds[type]->getStatus() != SFXManager::SFX_PLAYING)
|
|
{
|
|
m_custom_sounds[type]->play();
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
*/
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
/** Updates the physics for this kart: computing the driving force, set
|
|
* steering, handles skidding, terrain impact on kart, ...
|
|
* \param dt Time step size.
|
|
*/
|
|
void Kart::updatePhysics(float dt)
|
|
{
|
|
// Check if accel is pressed for the first time. The actual timing
|
|
// is done in getStartupBoost - it returns 0 if the start was actually
|
|
// too slow to qualify for a boost.
|
|
if(!m_has_started && m_controls.m_accel)
|
|
{
|
|
m_has_started = true;
|
|
m_zipper_time_left = 5.0f;
|
|
float f = m_kart_properties->getStartupBoost();
|
|
m_vehicle->activateZipper(f);
|
|
|
|
}
|
|
|
|
m_bounce_back_time-=dt;
|
|
float engine_power = getActualWheelForce() + handleNitro(dt)
|
|
+ handleSlipstream(dt);
|
|
if(m_attachment->getType()==ATTACH_PARACHUTE) engine_power*=0.2f;
|
|
|
|
if(m_controls.m_accel) // accelerating
|
|
{ // For a short time after a collision disable the engine,
|
|
// so that the karts can bounce back a bit from the obstacle.
|
|
if(m_bounce_back_time>0.0f)
|
|
engine_power = 0.0f;
|
|
// let a player going backwards accelerate quickly (e.g. if a player hits a
|
|
// wall, he needs to be able to start again quickly after going backwards)
|
|
else if(m_speed < 0.0f)
|
|
engine_power *= 5.0f;
|
|
|
|
// Lose some traction when skidding, to balance the adventage
|
|
// Up to r5483 AIs were allowed to cheat in medium and high diff levels
|
|
if(m_controls.m_drift)
|
|
engine_power *= 0.5f;
|
|
|
|
applyEngineForce(engine_power*m_controls.m_accel);
|
|
|
|
// Either all or no brake is set, so test only one to avoid
|
|
// resetting all brakes most of the time.
|
|
if(m_vehicle->getWheelInfo(0).m_brake &&
|
|
!World::getWorld()->isStartPhase())
|
|
resetBrakes();
|
|
}
|
|
else
|
|
{ // not accelerating
|
|
if(m_controls.m_brake)
|
|
{ // check if the player is currently only slowing down or moving backwards
|
|
if(m_speed > 0.0f)
|
|
{ // going forward
|
|
applyEngineForce(0.f);
|
|
|
|
//apply the brakes
|
|
for(int i=0; i<4; i++) m_vehicle->setBrake(getBrakeFactor(), i);
|
|
m_skidding*= 1.08f;//skid a little when the brakes are hit (just enough to make the skiding sound)
|
|
if(m_skidding>m_kart_properties->getMaxSkid())
|
|
m_skidding=m_kart_properties->getMaxSkid();
|
|
}
|
|
else // m_speed < 0
|
|
{
|
|
resetBrakes();
|
|
// going backward, apply reverse gear ratio (unless he goes too fast backwards)
|
|
if ( -m_speed < getMaxSpeedOnTerrain()
|
|
*m_kart_properties->getMaxSpeedReverseRatio() )
|
|
{
|
|
// The backwards acceleration is artificially increased to
|
|
// allow players to get "unstuck" quicker if they hit e.g.
|
|
// a wall.
|
|
applyEngineForce(-engine_power*2.5f);
|
|
}
|
|
else // -m_speed >= max speed on this terrain
|
|
{
|
|
applyEngineForce(0.0f);
|
|
}
|
|
|
|
} // m_speed <00
|
|
}
|
|
else // !m_brake
|
|
{
|
|
// lift the foot from throttle, brakes with 10% engine_power
|
|
applyEngineForce(-m_controls.m_accel*engine_power*0.1f);
|
|
|
|
// If not giving power (forward or reverse gear), and speed is low
|
|
// we are "parking" the kart, so in battle mode we can ambush people, eg
|
|
if(abs(m_speed) < 5.0f) {
|
|
for(int i=0; i<4; i++) m_vehicle->setBrake(20.0f, i);
|
|
}
|
|
} // !m_brake
|
|
} // not accelerating
|
|
#ifdef ENABLE_JUMP
|
|
if(m_controls.jump && isOnGround())
|
|
{
|
|
//Vector3 impulse(0.0f, 0.0f, 10.0f);
|
|
// getVehicle()->getRigidBody()->applyCentralImpulse(impulse);
|
|
btVector3 velocity = m_body->getLinearVelocity();
|
|
velocity.setZ( m_kart_properties->getJumpVelocity() );
|
|
|
|
getBody()->setLinearVelocity( velocity );
|
|
|
|
}
|
|
#endif
|
|
if (isOnGround()){
|
|
if((fabs(m_controls.m_steer) > 0.001f) && m_controls.m_drift)
|
|
{
|
|
m_skidding += m_kart_properties->getSkidIncrease()
|
|
*dt/m_kart_properties->getTimeTillMaxSkid();
|
|
if(m_skidding>m_kart_properties->getMaxSkid())
|
|
m_skidding=m_kart_properties->getMaxSkid();
|
|
}
|
|
else if(m_skidding>1.0f)
|
|
{
|
|
m_skidding *= m_kart_properties->getSkidDecrease();
|
|
if(m_skidding<1.0f) m_skidding=1.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_skidding = 1.0f; // Lose any skid factor as soon as we fly
|
|
}
|
|
if(m_skidding>1.0f)
|
|
{
|
|
if(m_skid_sound->getStatus() != SFXManager::SFX_PLAYING &&
|
|
m_kart_properties->hasSkidmarks())
|
|
m_skid_sound->play();
|
|
}
|
|
else if(m_skid_sound->getStatus() == SFXManager::SFX_PLAYING)
|
|
{
|
|
m_skid_sound->stop();
|
|
}
|
|
float steering = getMaxSteerAngle() * m_controls.m_steer*m_skidding;
|
|
|
|
m_vehicle->setSteeringValue(steering, 0);
|
|
m_vehicle->setSteeringValue(steering, 1);
|
|
|
|
// Only compute the current speed if this is not the client. On a client the
|
|
// speed is actually received from the server.
|
|
if(network_manager->getMode()!=NetworkManager::NW_CLIENT)
|
|
m_speed = getVehicle()->getRigidBody()->getLinearVelocity().length();
|
|
|
|
// calculate direction of m_speed
|
|
const btTransform& chassisTrans = getVehicle()->getChassisWorldTransform();
|
|
btVector3 forwardW (
|
|
chassisTrans.getBasis()[0][2],
|
|
chassisTrans.getBasis()[1][2],
|
|
chassisTrans.getBasis()[2][2]);
|
|
|
|
if (forwardW.dot(getVehicle()->getRigidBody()->getLinearVelocity()) < btScalar(0.))
|
|
m_speed *= -1.f;
|
|
|
|
//cap at maximum velocity
|
|
float max_speed = getMaxSpeedOnTerrain();
|
|
if (m_zipper_time_left > 0.0f)
|
|
{
|
|
const float zipper_fade_time = 3.0f;
|
|
if (m_zipper_time_left > zipper_fade_time)
|
|
{
|
|
max_speed *= (1.0f + stk_config->m_zipper_max_speed_fraction);
|
|
}
|
|
else
|
|
{
|
|
max_speed *= (1.0f + (stk_config->m_zipper_max_speed_fraction * m_zipper_time_left * 1.0f/zipper_fade_time));
|
|
}
|
|
}
|
|
if ( m_speed > max_speed && isOnGround())
|
|
{
|
|
const float velocity_ratio = max_speed/m_speed;
|
|
m_speed = max_speed;
|
|
btVector3 velocity = m_body->getLinearVelocity();
|
|
|
|
velocity.setZ( velocity.getZ() * velocity_ratio );
|
|
velocity.setX( velocity.getX() * velocity_ratio );
|
|
velocity.setY( velocity.getY() * velocity_ratio ); // Up-down too
|
|
|
|
getVehicle()->getRigidBody()->setLinearVelocity( velocity );
|
|
|
|
}
|
|
|
|
// To avoid tunneling (which can happen on long falls), clamp the
|
|
// velocity in Y direction. Tunneling can happen if the Y velocity
|
|
// is larger than the maximum suspension travel (per frame), since then
|
|
// the wheel suspension can not stop/slow down the fall (though I am
|
|
// not sure if this is enough in all cases!). So the speed is limited
|
|
// to suspensionTravel / dt with dt = 1/60 (since this is the dt
|
|
// bullet is using).
|
|
// Only apply if near ground instead of purely based on speed avoiding
|
|
// the "parachute on top" look.
|
|
const Vec3 &v = m_body->getLinearVelocity();
|
|
if(/*isNearGround() &&*/ v.getY() < - m_kart_properties->getSuspensionTravelCM()*0.01f*60)
|
|
{
|
|
Vec3 v_clamped = v;
|
|
// clamp the speed to 99% of the maxium falling speed.
|
|
v_clamped.setY(-m_kart_properties->getSuspensionTravelCM()*0.01f*60 * 0.99f);
|
|
m_body->setLinearVelocity(v_clamped);
|
|
}
|
|
|
|
//at low velocity, forces on kart push it back and forth so we ignore this
|
|
if(fabsf(m_speed) < 0.2f) // quick'n'dirty workaround for bug 1776883
|
|
m_speed = 0;
|
|
|
|
// when going faster, use higher pitch for engine
|
|
if(m_engine_sound && sfx_manager->sfxAllowed())
|
|
{
|
|
if(isOnGround())
|
|
{
|
|
// Engine noise is based half in total speed, half in fake gears:
|
|
// With a sawtooth graph like /|/|/| we get 3 even spaced gears,
|
|
// ignoring the gear settings from stk_config, but providing a
|
|
// good enough brrrBRRRbrrrBRRR sound effect. Speed factor makes
|
|
// it a "staired sawtooth", so more acoustically rich.
|
|
float gears = 3.0f * fmod((float)(m_speed / max_speed), 0.333334f);
|
|
m_engine_sound->speed(0.6f +
|
|
(float)(m_speed / max_speed) * 0.35f +
|
|
gears * 0.35f);
|
|
}
|
|
else
|
|
{
|
|
// When flying, fixed value but not too high pitch
|
|
// This gives some variation (vs previous "on wheels" one)
|
|
m_engine_sound->speed(0.9f);
|
|
}
|
|
m_engine_sound->position(getXYZ());
|
|
}
|
|
#ifdef XX
|
|
printf("forward %f %f %f %f side %f %f %f %f angVel %f %f %f heading %f\n"
|
|
,m_vehicle->m_forwardImpulse[0]
|
|
,m_vehicle->m_forwardImpulse[1]
|
|
,m_vehicle->m_forwardImpulse[2]
|
|
,m_vehicle->m_forwardImpulse[3]
|
|
,m_vehicle->m_sideImpulse[0]
|
|
,m_vehicle->m_sideImpulse[1]
|
|
,m_vehicle->m_sideImpulse[2]
|
|
,m_vehicle->m_sideImpulse[3]
|
|
,m_body->getAngularVelocity().getX()
|
|
,m_body->getAngularVelocity().getY()
|
|
,m_body->getAngularVelocity().getZ()
|
|
,getHeading()
|
|
);
|
|
#endif
|
|
|
|
} // updatePhysics
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Attaches the right model, creates the physics and loads all special
|
|
* effects (particle systems etc.)
|
|
*/
|
|
void Kart::loadData()
|
|
{
|
|
m_kart_model->attachModel(&m_node);
|
|
// Attachment must be created after attachModel, since only then the
|
|
// scene node will exist (to which the attachment is added). But the
|
|
// attachment is needed in createPhysics (which gets the mass, which
|
|
// is dependent on the attachment).
|
|
m_attachment = new Attachment(this);
|
|
createPhysics();
|
|
|
|
// Attach Particle System
|
|
m_smoke_system = new Smoke(this);
|
|
m_water_splash_system = new WaterSplash(this);
|
|
m_nitro = new Nitro(this);
|
|
m_slipstream = new SlipStream(this);
|
|
|
|
if(m_kart_properties->hasSkidmarks())
|
|
m_skidmarks = new SkidMarks(*this);
|
|
|
|
m_shadow = new Shadow(m_kart_properties->getShadowTexture(),
|
|
m_node);
|
|
} // loadData
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Stores the current suspension length. This function is called from world
|
|
* after all karts are in resting position (see World::resetAllKarts), so
|
|
* that the default suspension rest length can be stored. This is then used
|
|
* later to move the wheels depending on actual suspension, so that when
|
|
* a kart is in rest, the wheels are at the position at which they were
|
|
* modelled.
|
|
*/
|
|
void Kart::setSuspensionLength()
|
|
{
|
|
for(unsigned int i=0; i<4; i++)
|
|
{
|
|
m_default_suspension_length[i] =
|
|
m_vehicle->getWheelInfo(i).m_raycastInfo.m_suspensionLength;
|
|
} // for i
|
|
} // setSuspensionLength
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Applies engine power to all the wheels that are traction capable,
|
|
* so other parts of code do not have to be adjusted to simulate different
|
|
* kinds of vehicles in the general case, only if they are trying to
|
|
* simulate traction control, diferentials or multiple independent electric
|
|
* engines, they will have to tweak the power in a per wheel basis.
|
|
*/
|
|
void Kart::applyEngineForce(float force)
|
|
{
|
|
// Split power to simulate a 4WD 40-60, other values possible
|
|
// FWD or RWD is a matter of putting a 0 and 1 in the right place
|
|
float frontForce = force*0.4f;
|
|
float rearForce = force*0.6f;
|
|
// Front wheels
|
|
for(unsigned int i=0; i<2; i++)
|
|
{
|
|
m_vehicle->applyEngineForce (frontForce, i);
|
|
}
|
|
// Rear wheels
|
|
for(unsigned int i=2; i<4; i++)
|
|
{
|
|
m_vehicle->applyEngineForce (rearForce, i);
|
|
}
|
|
} // applyEngineForce
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Updates the graphics model. Mainly set the graphical position to be the
|
|
* same as the physics position, but uses offsets to position and rotation
|
|
* for special gfx effects (e.g. skidding will turn the karts more). These
|
|
* variables are actually not used here atm, but are defined here and then
|
|
* used in Moveable.
|
|
* \param offset_xyz Offset to be added to the position.
|
|
* \param rotation Additional rotation.
|
|
*/
|
|
void Kart::updateGraphics(const Vec3& offset_xyz,
|
|
const btQuaternion& rotation)
|
|
{
|
|
float wheel_up_axis[4];
|
|
for(unsigned int i=0; i<4; i++)
|
|
{
|
|
// Set the suspension length
|
|
wheel_up_axis[i] = m_default_suspension_length[i]
|
|
- m_vehicle->getWheelInfo(i).m_raycastInfo.m_suspensionLength;
|
|
}
|
|
m_kart_model->update(m_wheel_rotation, getSteerPercent(), wheel_up_axis);
|
|
|
|
Vec3 center_shift = m_kart_properties->getGravityCenterShift();
|
|
float y = m_vehicle->getWheelInfo(0).m_chassisConnectionPointCS.getY()
|
|
- m_default_suspension_length[0]
|
|
- m_vehicle->getWheelInfo(0).m_wheelsRadius
|
|
- (m_kart_model->getWheelGraphicsRadius(0)
|
|
-m_kart_model->getWheelGraphicsPosition(0).getY() );
|
|
center_shift.setY(y);
|
|
|
|
if(m_smoke_system)
|
|
{
|
|
float f=0.0f;
|
|
if(getMaterial() && getMaterial()->hasSmoke() &&
|
|
fabsf(m_controls.m_steer) > 0.8 &&
|
|
isOnGround() )
|
|
f=250.0f;
|
|
m_smoke_system->setCreationRate((m_skidding-1)*f);
|
|
}
|
|
if(m_water_splash_system)
|
|
{
|
|
float f = getMaterial() && getMaterial()->hasWaterSplash() && isOnGround()
|
|
? sqrt(getSpeed())*40.0f
|
|
: 0.0f;
|
|
m_water_splash_system->setCreationRate(f);
|
|
}
|
|
if(m_nitro)
|
|
// fabs(speed) is important, otherwise the negative number will
|
|
// become a huge unsigned number in the particle scene node!
|
|
m_nitro->setCreationRate(m_controls.m_nitro && m_collected_energy>0
|
|
? (10.0f + fabsf(getSpeed())*20.0f) : 0);
|
|
|
|
// For testing purposes mis-use the nitro graphical effects to show
|
|
// then the slipstream becomes usable.
|
|
if(m_slipstream_mode == SS_USE)
|
|
m_nitro->setCreationRate(20.0f);
|
|
|
|
float speed_ratio = getSpeed()/getMaxSpeedOnTerrain();
|
|
float offset_heading = getSteerPercent()*m_kart_properties->getSkidVisual()
|
|
* speed_ratio * m_skidding*m_skidding;
|
|
Moveable::updateGraphics(center_shift,
|
|
btQuaternion(offset_heading, 0, 0));
|
|
} // updateGraphics
|
|
|
|
/* EOF */
|