git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/branches/hilnius@13689 178a84e3-b1eb-0310-8ba1-8eac791a3b58
2533 lines
93 KiB
C++
2533 lines
93 KiB
C++
//
|
|
// 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 <algorithm> // for min and max
|
|
|
|
#include <ICameraSceneNode.h>
|
|
#include <ISceneManager.h>
|
|
|
|
#include "audio/music_manager.hpp"
|
|
#include "audio/sfx_manager.hpp"
|
|
#include "audio/sfx_base.hpp"
|
|
#include "challenges/unlock_manager.hpp"
|
|
#include "config/user_config.hpp"
|
|
#include "graphics/camera.hpp"
|
|
#include "graphics/explosion.hpp"
|
|
#include "graphics/irr_driver.hpp"
|
|
#include "graphics/material_manager.hpp"
|
|
#include "graphics/particle_emitter.hpp"
|
|
#include "graphics/particle_kind.hpp"
|
|
#include "graphics/particle_kind_manager.hpp"
|
|
#include "graphics/shadow.hpp"
|
|
#include "graphics/skid_marks.hpp"
|
|
#include "graphics/slip_stream.hpp"
|
|
#include "graphics/stars.hpp"
|
|
#include "guiengine/scalable_font.hpp"
|
|
#include "karts/explosion_animation.hpp"
|
|
#include "karts/kart_gfx.hpp"
|
|
#include "karts/rescue_animation.hpp"
|
|
#include "modes/overworld.hpp"
|
|
#include "modes/world.hpp"
|
|
#include "io/file_manager.hpp"
|
|
#include "items/attachment.hpp"
|
|
#include "items/item_manager.hpp"
|
|
#include "items/projectile_manager.hpp"
|
|
#include "karts/controller/end_controller.hpp"
|
|
#include "karts/abstract_kart_animation.hpp"
|
|
#include "karts/kart_model.hpp"
|
|
#include "karts/kart_properties_manager.hpp"
|
|
#include "karts/max_speed.hpp"
|
|
#include "karts/skidding.hpp"
|
|
#include "modes/linear_world.hpp"
|
|
#include "network/network_world.hpp"
|
|
#include "network/network_manager.hpp"
|
|
#include "physics/btKart.hpp"
|
|
#include "physics/btKartRaycast.hpp"
|
|
#include "physics/btUprightConstraint.hpp"
|
|
#include "physics/physics.hpp"
|
|
#include "race/history.hpp"
|
|
#include "tracks/track.hpp"
|
|
#include "tracks/track_manager.hpp"
|
|
#include "utils/constants.hpp"
|
|
#include "utils/log.hpp" //TODO: remove after debugging is done
|
|
|
|
|
|
|
|
#if defined(WIN32) && !defined(__CYGWIN__) && !defined(__MINGW32__)
|
|
// Disable warning for using 'this' in base member initializer list
|
|
# pragma warning(disable:4355)
|
|
#endif
|
|
|
|
#if defined(WIN32) && !defined(__CYGWIN__) && !defined(__MINGW32__)
|
|
# define isnan _isnan
|
|
#else
|
|
# include <math.h>
|
|
#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, unsigned int world_kart_id,
|
|
int position, const btTransform& init_transform)
|
|
: AbstractKart(ident, world_kart_id, position, init_transform)
|
|
|
|
#if defined(WIN32) && !defined(__CYGWIN__) && !defined(__MINGW32__)
|
|
# pragma warning(1:4355)
|
|
#endif
|
|
{
|
|
m_max_speed = new MaxSpeed(this);
|
|
m_terrain_info = new TerrainInfo();
|
|
m_powerup = new Powerup(this);
|
|
m_vehicle = NULL;
|
|
m_initial_position = position;
|
|
m_race_position = position;
|
|
m_collected_energy = 0;
|
|
m_finished_race = false;
|
|
m_finish_time = 0.0f;
|
|
m_bubblegum_time = 0.0f;
|
|
m_bubblegum_torque = 0.0f;
|
|
m_invulnerable_time = 0.0f;
|
|
m_squash_time = 0.0f;
|
|
m_shadow_enabled = false;
|
|
|
|
m_shadow = NULL;
|
|
m_collision_particles = NULL;
|
|
m_slipstream = NULL;
|
|
m_skidmarks = NULL;
|
|
m_controller = NULL;
|
|
m_saved_controller = NULL;
|
|
m_flying = false;
|
|
m_sky_particles_emitter= NULL;
|
|
m_stars_effect = NULL;
|
|
m_jump_time = 0;
|
|
m_is_jumping = false;
|
|
m_min_nitro_time = 0.0f;
|
|
m_fire_clicked = 0;
|
|
|
|
m_view_blocked_by_plunger = 0;
|
|
m_has_caught_nolok_bubblegum = false;
|
|
|
|
// 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;
|
|
m_wheel_rotation_dt = 0;
|
|
|
|
m_kart_model->setKart(this);
|
|
|
|
// 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( "horn" );
|
|
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;
|
|
|
|
} // Kart
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** This is a second initialisation phase, necessary since in the constructor
|
|
* virtual functions are not called for any superclasses.
|
|
* \param type Type of the kart.
|
|
*/
|
|
void Kart::init(RaceManager::KartType type)
|
|
{
|
|
// In multiplayer mode, sounds are NOT positional
|
|
if (race_manager->getNumLocalPlayers() > 1)
|
|
{
|
|
if (type == RaceManager::KT_PLAYER)
|
|
{
|
|
// players have louder sounds than AIs
|
|
const float factor = std::min(1.0f, race_manager->getNumLocalPlayers()/2.0f);
|
|
m_goo_sound->volume( 1.0f / factor );
|
|
m_skid_sound->volume( 1.0f / factor );
|
|
m_crash_sound->volume( 1.0f / factor );
|
|
m_beep_sound->volume( 1.0f / factor );
|
|
}
|
|
else
|
|
{
|
|
m_goo_sound->volume( 1.0f / race_manager->getNumberOfKarts() );
|
|
m_skid_sound->volume( 1.0f / race_manager->getNumberOfKarts() );
|
|
m_crash_sound->volume( 1.0f / race_manager->getNumberOfKarts() );
|
|
m_beep_sound->volume( 1.0f / race_manager->getNumberOfKarts() );
|
|
}
|
|
}
|
|
|
|
if(!m_engine_sound)
|
|
{
|
|
Log::error("Kart","Could not allocate a sfx object for the kart. Further errors may ensue!\n");
|
|
}
|
|
|
|
|
|
bool animations = true;
|
|
const int anims = UserConfigParams::m_show_steering_animations;
|
|
if (anims == ANIMS_NONE)
|
|
{
|
|
animations = false;
|
|
}
|
|
else if (anims == ANIMS_PLAYERS_ONLY && type != RaceManager::KT_PLAYER)
|
|
{
|
|
animations = false;
|
|
}
|
|
loadData(type, animations);
|
|
|
|
m_kart_gfx = new KartGFX(this);
|
|
m_skidding = new Skidding(this,
|
|
m_kart_properties->getSkiddingProperties());
|
|
// Create the stars effect
|
|
m_stars_effect =
|
|
new Stars(getNode(),
|
|
core::vector3df(0.0f,
|
|
getKartModel()->getModel()
|
|
->getBoundingBox().MaxEdge.Y,
|
|
0.0f) );
|
|
|
|
reset();
|
|
} // init
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** 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 );
|
|
sfx_manager->deleteSFX(m_beep_sound );
|
|
delete m_kart_gfx;
|
|
if(m_terrain_sound) sfx_manager->deleteSFX(m_terrain_sound);
|
|
if(m_previous_terrain_sound) sfx_manager->deleteSFX(m_previous_terrain_sound);
|
|
if(m_collision_particles) delete m_collision_particles;
|
|
if(m_slipstream) delete m_slipstream;
|
|
if(m_sky_particles_emitter) delete m_sky_particles_emitter;
|
|
if(m_attachment) delete m_attachment;
|
|
if (m_stars_effect) delete m_stars_effect;
|
|
|
|
delete m_shadow;
|
|
|
|
if(m_skidmarks) delete m_skidmarks ;
|
|
|
|
// Ghost karts don't have a body
|
|
if(m_body)
|
|
{
|
|
World::getWorld()->getPhysics()->removeKart(this);
|
|
delete m_vehicle;
|
|
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_skidding;
|
|
delete m_max_speed;
|
|
delete m_terrain_info;
|
|
delete m_powerup;
|
|
|
|
if(m_controller)
|
|
delete m_controller;
|
|
if(m_saved_controller)
|
|
delete m_saved_controller;
|
|
} // ~Kart
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Reset before a new race. It will remove all attachments, and
|
|
* puts the kart back at its original start position.
|
|
*/
|
|
void Kart::reset()
|
|
{
|
|
if (m_flying)
|
|
{
|
|
m_flying = false;
|
|
stopFlying();
|
|
}
|
|
|
|
// Add karts back in case that they have been removed (i.e. in battle
|
|
// mode) - but only if they actually have a body (e.g. ghost karts
|
|
// don't have one).
|
|
if(m_body)
|
|
World::getWorld()->getPhysics()->addKart(this);
|
|
|
|
m_min_nitro_time = 0.0f;
|
|
|
|
// Reset star effect in case that it is currently being shown.
|
|
m_stars_effect->reset();
|
|
m_max_speed->reset();
|
|
m_powerup->reset();
|
|
|
|
// Reset animations and wheels
|
|
m_kart_model->reset();
|
|
|
|
// 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_attachment->clear();
|
|
m_kart_gfx->reset();
|
|
m_skidding->reset();
|
|
|
|
|
|
if (m_collision_particles)
|
|
m_collision_particles->setCreationRateAbsolute(0.0f);
|
|
|
|
m_race_position = m_initial_position;
|
|
m_finished_race = false;
|
|
m_eliminated = false;
|
|
m_finish_time = 0.0f;
|
|
m_bubblegum_time = 0.0f;
|
|
m_bubblegum_torque = 0.0f;
|
|
m_invulnerable_time = 0.0f;
|
|
m_squash_time = 0.0f;
|
|
m_node->setScale(core::vector3df(1.0f, 1.0f, 1.0f));
|
|
m_collected_energy = 0;
|
|
m_has_started = false;
|
|
m_wheel_rotation = 0;
|
|
m_bounce_back_time = 0.0f;
|
|
m_time_last_crash = 0.0f;
|
|
m_speed = 0.0f;
|
|
m_current_lean = 0.0f;
|
|
m_view_blocked_by_plunger = 0.0f;
|
|
m_bubblegum_time = 0.0f;
|
|
m_bubblegum_torque = 0.0f;
|
|
m_has_caught_nolok_bubblegum = false;
|
|
m_is_jumping = false;
|
|
|
|
// In case that the kart was in the air, in which case its
|
|
// linear damping is 0
|
|
m_body->setDamping(m_kart_properties->getChassisLinearDamping(),
|
|
m_kart_properties->getChassisAngularDamping() );
|
|
|
|
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.reset();
|
|
m_slipstream->reset();
|
|
if(m_vehicle)
|
|
{
|
|
m_vehicle->reset();
|
|
}
|
|
// Randomize wheel rotation if needed
|
|
if (m_kart_properties->hasRandomWheels() && m_vehicle && m_kart_model)
|
|
{
|
|
scene::ISceneNode** graphic_wheels = m_kart_model->getWheelNodes();
|
|
// FIXME Hardcoded i < 4 comes from the arrays in KartModel
|
|
for (int i = 0; i < m_vehicle->getNumWheels() && i < 4; i++)
|
|
{
|
|
// Physics
|
|
btWheelInfo& wheel = m_vehicle->getWheelInfo(i);
|
|
wheel.m_rotation = btScalar(rand() % 360);
|
|
// And graphics
|
|
core::vector3df wheel_rotation(wheel.m_rotation, 0, 0);
|
|
if (graphic_wheels[i])
|
|
graphic_wheels[i]->setRotation(wheel_rotation);
|
|
} // for wheels
|
|
} // if random wheel rotation
|
|
|
|
setTrans(m_reset_transform);
|
|
|
|
applyEngineForce (0.0f);
|
|
|
|
AbstractKart::reset();
|
|
if (m_skidmarks)
|
|
{
|
|
m_skidmarks->reset();
|
|
const Track *track =
|
|
track_manager->getTrack( race_manager->getTrackName() );
|
|
m_skidmarks->adjustFog(track->isFogEnabled() );
|
|
}
|
|
|
|
|
|
m_terrain_info->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();
|
|
|
|
// 3 strikes mode can hide the wheels
|
|
scene::ISceneNode** wheels = getKartModel()->getWheelNodes();
|
|
if(wheels[0]) wheels[0]->setVisible(true);
|
|
if(wheels[1]) wheels[1]->setVisible(true);
|
|
if(wheels[2]) wheels[2]->setVisible(true);
|
|
if(wheels[3]) wheels[3]->setVisible(true);
|
|
|
|
} // reset
|
|
|
|
// -----------------------------------------------------------------------------
|
|
void Kart::increaseMaxSpeed(unsigned int category, float add_speed,
|
|
float engine_force, float duration,
|
|
float fade_out_time)
|
|
{
|
|
m_max_speed->increaseMaxSpeed(category, add_speed, engine_force, duration,
|
|
fade_out_time);
|
|
} // increaseMaxSpeed
|
|
|
|
// -----------------------------------------------------------------------------
|
|
void Kart::setSlowdown(unsigned int category, float max_speed_fraction,
|
|
float fade_in_time)
|
|
{
|
|
m_max_speed->setSlowdown(category, max_speed_fraction, fade_in_time);
|
|
} // setSlowdown
|
|
|
|
// -----------------------------------------------------------------------------
|
|
float Kart::getCurrentMaxSpeed() const
|
|
{
|
|
return m_max_speed->getCurrentMaxSpeed();
|
|
} // getCurrentMaxSpeed
|
|
// -----------------------------------------------------------------------------
|
|
float Kart::getSpeedIncreaseTimeLeft(unsigned int category) const
|
|
{
|
|
return m_max_speed->getSpeedIncreaseTimeLeft(category);
|
|
} // getSpeedIncreaseTimeLeft
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Returns the current material the kart is on. */
|
|
const Material *Kart::getMaterial() const
|
|
{
|
|
return m_terrain_info->getMaterial();
|
|
} // getMaterial
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Returns the previous material the kart was one (which might be
|
|
* the same as getMaterial() ). */
|
|
const Material *Kart::getLastMaterial() const
|
|
{
|
|
return m_terrain_info->getLastMaterial();
|
|
} // getLastMaterial
|
|
// -----------------------------------------------------------------------------
|
|
/** Returns the pitch of the terrain depending on the heading. */
|
|
float Kart::getTerrainPitch(float heading) const
|
|
{
|
|
return m_terrain_info->getTerrainPitch(heading);
|
|
} // getTerrainPitch
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Returns the height of the terrain. we're currently above */
|
|
float Kart::getHoT() const
|
|
{
|
|
return m_terrain_info->getHoT();
|
|
} // getHoT
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Sets the powerup this kart has collected.
|
|
* \param t Type of the powerup.
|
|
* \param n Number of powerups collected.
|
|
*/
|
|
void Kart::setPowerup(PowerupManager::PowerupType t, int n)
|
|
{
|
|
m_powerup->set(t, n);
|
|
} // setPowerup
|
|
|
|
// -----------------------------------------------------------------------------
|
|
int Kart::getNumPowerup() const
|
|
{
|
|
return m_powerup->getNum();
|
|
} // getNumPowerup
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** 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
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Sets the position in race this kart has .
|
|
* The position in this race for this kart (1<=p<=n)
|
|
*/
|
|
void Kart::setPosition(int p)
|
|
{
|
|
m_controller->setPosition(p);
|
|
m_race_position = p;
|
|
} // setPosition
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Sets that the view is blocked by a plunger. The duration depends on
|
|
* the difficulty, see KartPorperties getPlungerInFaceTime.
|
|
*/
|
|
void Kart::blockViewWithPlunger()
|
|
{
|
|
// Avoid that a plunger extends the plunger time
|
|
if(m_view_blocked_by_plunger<=0 && !isShielded())
|
|
m_view_blocked_by_plunger =
|
|
m_kart_properties->getPlungerInFaceTime();
|
|
if(isShielded())
|
|
{
|
|
decreaseShieldTime(0.0f); //decrease the default amount of time
|
|
Log::verbose("Kart", "Decreasing shield, because of removing the plunger. \n");
|
|
}
|
|
} // blockViewWithPlunger
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** 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 custom_pitch Pitch value to overwrite the terrain pitch. A value of
|
|
* -1 indicates that the custom_pitch value should not be used, instead
|
|
* the actual pitch of the terrain is to be used.
|
|
*/
|
|
btTransform Kart::getAlignedTransform(const float custom_pitch)
|
|
{
|
|
btTransform trans = getTrans();
|
|
|
|
float pitch = (custom_pitch == -1 ? getTerrainPitch(getHeading())
|
|
: custom_pitch);
|
|
|
|
btMatrix3x3 m;
|
|
m.setEulerZYX(pitch, getHeading()+m_skidding->getVisualSkidRotation(),
|
|
0.0f);
|
|
trans.setBasis(m);
|
|
|
|
return trans;
|
|
} // getAlignedTransform
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** 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();
|
|
|
|
// improve physics for tall karts
|
|
if (kart_height > kart_length*0.6f)
|
|
{
|
|
kart_height = kart_length*0.6f;
|
|
}
|
|
|
|
btCollisionShape *shape;
|
|
const Vec3 &bevel = m_kart_properties->getBevelFactor();
|
|
if(bevel.getX() || bevel.getY() || bevel.getZ())
|
|
{
|
|
Vec3 orig_factor(1, 1, 1-bevel.getZ());
|
|
Vec3 bevel_factor(1.0f-bevel.getX(),
|
|
1.0f-bevel.getY(),
|
|
1.0f );
|
|
btConvexHullShape *hull = new btConvexHullShape();
|
|
for(int x=-1; x<=1; x+=2)
|
|
{
|
|
for(int y=-1; y<=1; y+=2)
|
|
{
|
|
for(int z=-1; z<=1; z+=2)
|
|
{
|
|
Vec3 p(x*getKartModel()->getWidth()*0.5f,
|
|
y*getKartModel()->getHeight()*0.5f,
|
|
z*getKartModel()->getLength()*0.5f);
|
|
|
|
hull->addPoint(p*orig_factor);
|
|
hull->addPoint(p*bevel_factor);
|
|
} // for z
|
|
} // for y
|
|
} // for x
|
|
|
|
// This especially enables proper drawing of the point cloud
|
|
hull->initializePolyhedralFeatures();
|
|
shape = hull;
|
|
} // bevel.getX()!=0
|
|
else
|
|
{
|
|
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 = m_kart_properties->getMass();
|
|
|
|
// Position the chassis
|
|
// --------------------
|
|
btTransform trans;
|
|
trans.setIdentity();
|
|
createBody(mass, trans, &m_kart_chassis,
|
|
m_kart_properties->getRestitution());
|
|
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 btKartRaycaster(World::getWorld()->getPhysics()->getPhysicsWorld(),
|
|
stk_config->m_smooth_normals &&
|
|
World::getWorld()->getTrack()->smoothNormals());
|
|
m_vehicle = new btKart(m_body, m_vehicle_raycaster, this);
|
|
|
|
// never deactivate the vehicle
|
|
m_body->setActivationState(DISABLE_DEACTIVATION);
|
|
|
|
// 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);
|
|
|
|
btKart::btVehicleTuning tuning;
|
|
tuning.m_maxSuspensionTravelCm =
|
|
m_kart_properties->getSuspensionTravelCM();
|
|
tuning.m_maxSuspensionForce =
|
|
m_kart_properties->getMaxSuspensionForce();
|
|
|
|
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, 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
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void Kart::flyUp()
|
|
{
|
|
m_flying = true;
|
|
Moveable::flyUp();
|
|
}
|
|
|
|
void Kart::flyDown()
|
|
{
|
|
if (isNearGround())
|
|
{
|
|
stopFlying();
|
|
m_flying = false;
|
|
}
|
|
else
|
|
{
|
|
Moveable::flyDown();
|
|
}
|
|
} // flyUp
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Starts the engine sound effect. Called once the track intro phase is over.
|
|
*/
|
|
void Kart::startEngineSFX()
|
|
{
|
|
if(!m_engine_sound)
|
|
return;
|
|
|
|
// In multiplayer mode, sounds are NOT positional (because we have
|
|
// multiple listeners) so the engine sounds of all AIs is constantly
|
|
// heard. So reduce volume of all sounds.
|
|
if (race_manager->getNumLocalPlayers() > 1)
|
|
{
|
|
const int np = race_manager->getNumLocalPlayers();
|
|
const int nai = race_manager->getNumberOfKarts() - np;
|
|
|
|
// player karts twice as loud as AIs toghether
|
|
const float players_volume = (np * 2.0f) / (np*2.0f + np);
|
|
|
|
if (m_controller->isPlayerController())
|
|
m_engine_sound->volume( players_volume / np );
|
|
else
|
|
m_engine_sound->volume( (1.0f - players_volume) / nai );
|
|
}
|
|
|
|
m_engine_sound->speed(0.6f);
|
|
m_engine_sound->setLoop(true);
|
|
m_engine_sound->play();
|
|
} // startEngineSFX
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** 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::updateWeight()
|
|
{
|
|
float mass = m_kart_properties->getMass() + m_attachment->weightAdjust();
|
|
|
|
btVector3 inertia;
|
|
m_kart_chassis.calculateLocalInertia(mass, inertia);
|
|
m_body->setMassProps(mass, inertia);
|
|
} // updateWeight
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** 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);
|
|
m_kart_model->finishedRace();
|
|
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(),
|
|
m_controller));
|
|
if (m_controller->isPlayerController()) // if player is on this computer
|
|
{
|
|
GameSlot *slot = unlock_manager->getCurrentSlot();
|
|
const Challenge *challenge = slot->getCurrentChallenge();
|
|
// In case of a GP challenge don't make the end animation depend
|
|
// on if the challenge is fulfilled
|
|
if(challenge && !challenge->getData()->isGrandPrix())
|
|
{
|
|
if(challenge->getData()->isChallengeFulfilled())
|
|
m_kart_model->setAnimation(KartModel::AF_WIN_START);
|
|
else
|
|
m_kart_model->setAnimation(KartModel::AF_LOSE_START);
|
|
|
|
}
|
|
else 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);
|
|
|
|
RaceGUIBase* m = World::getWorld()->getRaceGUI();
|
|
if(m)
|
|
{
|
|
m->addMessage((getPosition() == 1 ? _("You won the race!") : _("You finished the race!")) ,
|
|
this, 2.0f);
|
|
}
|
|
}
|
|
}
|
|
else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_FOLLOW_LEADER)
|
|
{
|
|
// start end animation
|
|
setController(new EndController(this, m_controller->getPlayer(),
|
|
m_controller));
|
|
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);
|
|
|
|
RaceGUIBase* m = World::getWorld()->getRaceGUI();
|
|
if(m)
|
|
{
|
|
m->addMessage((getPosition() == 2 ? _("You won the race!") : _("You finished the race!")) ,
|
|
this, 2.0f);
|
|
}
|
|
}
|
|
else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES)
|
|
{
|
|
setController(new EndController(this, m_controller->getPlayer(),
|
|
m_controller));
|
|
}
|
|
|
|
} // 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); // selects the powerup
|
|
break;
|
|
}
|
|
case Item::ITEM_BUBBLEGUM:
|
|
m_has_caught_nolok_bubblegum = (item->getEmitter() != NULL &&
|
|
item->getEmitter()->getIdent() == "nolok");
|
|
|
|
// slow down
|
|
m_bubblegum_time = m_kart_properties->getBubblegumTime();
|
|
m_bubblegum_torque = (rand()%2)
|
|
? m_kart_properties->getBubblegumTorque()
|
|
: -m_kart_properties->getBubblegumTorque();
|
|
m_max_speed->setSlowdown(MaxSpeed::MS_DECREASE_BUBBLE,
|
|
m_kart_properties->getBubblegumSpeedFraction(),
|
|
m_kart_properties->getBubblegumFadeInTime(),
|
|
m_bubblegum_time);
|
|
m_goo_sound->position(getXYZ());
|
|
m_goo_sound->play();
|
|
// Play appropriate custom character sound
|
|
playCustomSFX(SFXManager::CUSTOM_GOO);
|
|
break;
|
|
default : break;
|
|
} // switch TYPE
|
|
|
|
if ( m_collected_energy > m_kart_properties->getNitroMax())
|
|
m_collected_energy = m_kart_properties->getNitroMax();
|
|
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 add_force = m_max_speed->getCurrentAdditionalEngineForce();
|
|
assert(!isnan(add_force));
|
|
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])
|
|
{
|
|
assert(!isnan(m_kart_properties->getMaxPower()));
|
|
assert(!isnan(m_kart_properties->getGearPowerIncrease()[i]));
|
|
return m_kart_properties->getMaxPower()
|
|
*m_kart_properties->getGearPowerIncrease()[i]
|
|
+add_force;
|
|
}
|
|
}
|
|
assert(!isnan(m_kart_properties->getMaxPower()));
|
|
return m_kart_properties->getMaxPower()+add_force;
|
|
|
|
} // 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 ((int)m_vehicle->getNumWheelsOnGround() == m_vehicle->getNumWheels()
|
|
&& !getKartAnimation());
|
|
} // isOnGround
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** The kart is near the ground, but not necessarily 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(m_terrain_info->getHoT()==Track::NOHIT)
|
|
return false;
|
|
else
|
|
return ((getXYZ().getY() - m_terrain_info->getHoT())
|
|
< stk_config->m_near_ground);
|
|
} // isNearGround
|
|
|
|
// ------------------------------------------------------------------------
|
|
/**
|
|
* Enables a kart shield protection for a certain amount of time.
|
|
*/
|
|
void Kart::setShieldTime(float t)
|
|
{
|
|
if(isShielded())
|
|
{
|
|
getAttachment()->setTimeLeft(t);
|
|
}
|
|
}
|
|
// ------------------------------------------------------------------------
|
|
/**
|
|
* Returns true if the kart is protected by a shield.
|
|
*/
|
|
bool Kart::isShielded() const
|
|
{
|
|
if(getAttachment() != NULL)
|
|
return getAttachment()->getType() == Attachment::ATTACH_BUBBLEGUM_SHIELD;
|
|
else
|
|
return false;
|
|
} // isShielded
|
|
|
|
// ------------------------------------------------------------------------
|
|
/**
|
|
*Returns the remaining time the kart is protected by a shield.
|
|
*/
|
|
float Kart::getShieldTime() const
|
|
{
|
|
if(isShielded())
|
|
return getAttachment()->getTimeLeft();
|
|
else
|
|
return 0.0f;
|
|
} // getShieldTime
|
|
|
|
// ------------------------------------------------------------------------
|
|
/**
|
|
* Decreases the kart's shield time.
|
|
* \param t The time substracted from the shield timer. If t == 0.0f, the default amout of time is substracted.
|
|
*/
|
|
void Kart::decreaseShieldTime(float t)
|
|
{
|
|
if(isShielded())
|
|
{
|
|
getAttachment()->setTimeLeft( getAttachment()->getTimeLeft() - t );
|
|
if(t == 0.0f)
|
|
{
|
|
getAttachment()->setTimeLeft( getAttachment()->getTimeLeft()
|
|
- stk_config->m_bubblegum_shield_time);
|
|
}
|
|
|
|
}
|
|
//Let the kart drop a bubble gum, if the shield was not damaged.
|
|
//This is the default, whenever a powerup is used by a kart.
|
|
//It is turned off, if the shield was reduced below zero by a hit. (Or by intently damaging the shield.)
|
|
if(!isShielded())
|
|
m_bubble_drop = false;
|
|
|
|
} // decreaseShieldTime
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Shows the star effect for a certain time.
|
|
* \param t Time to show the star effect for.
|
|
*/
|
|
void Kart::showStarEffect(float t)
|
|
{
|
|
m_stars_effect->showFor(t);
|
|
} // showStarEffect
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void Kart::eliminate()
|
|
{
|
|
if (!getKartAnimation())
|
|
{
|
|
World::getWorld()->getPhysics()->removeKart(this);
|
|
}
|
|
if (m_stars_effect)
|
|
{
|
|
m_stars_effect->reset();
|
|
m_stars_effect->update(1);
|
|
}
|
|
|
|
m_eliminated = true;
|
|
|
|
m_node->setVisible(false);
|
|
} // eliminate
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** 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)
|
|
{
|
|
if ( UserConfigParams::m_graphical_effects )
|
|
{
|
|
// update star effect (call will do nothing if stars are not activated)
|
|
m_stars_effect->update(dt);
|
|
}
|
|
|
|
if(m_squash_time>=0)
|
|
{
|
|
m_squash_time-=dt;
|
|
// If squasing time ends, reset the model
|
|
if(m_squash_time<=0)
|
|
{
|
|
m_node->setScale(core::vector3df(1.0f, 1.0f, 1.0f));
|
|
}
|
|
} // if squashed
|
|
|
|
if (m_bubblegum_time > 0.0f)
|
|
{
|
|
m_bubblegum_time -= dt;
|
|
if (m_bubblegum_time <= 0.0f)
|
|
{
|
|
m_bubblegum_torque = 0.0f;
|
|
}
|
|
}
|
|
|
|
// Update the position and other data taken from the physics
|
|
Moveable::update(dt);
|
|
|
|
if(!history->replayHistory())
|
|
m_controller->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;
|
|
//unblock the view if kart just became shielded
|
|
if(isShielded())
|
|
m_view_blocked_by_plunger = 0.0f;
|
|
// Decrease remaining invulnerability time
|
|
if(m_invulnerable_time>0)
|
|
{
|
|
m_invulnerable_time-=dt;
|
|
}
|
|
|
|
m_slipstream->update(dt);
|
|
|
|
if (!m_flying)
|
|
{
|
|
// 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_max_speed->getSpeedIncreaseTimeLeft(MaxSpeed::MS_INCREASE_ZIPPER)<=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));
|
|
// When the kart is jumping, linear damping reduces the falling speed
|
|
// of a kart so much that it can appear to be in slow motion. So
|
|
// disable linear damping if a kart is in the air
|
|
m_body->setDamping(0, m_kart_properties->getChassisAngularDamping());
|
|
}
|
|
else
|
|
{
|
|
m_body->setDamping(m_kart_properties->getChassisLinearDamping(),
|
|
m_kart_properties->getChassisAngularDamping());
|
|
}
|
|
|
|
//m_wheel_rotation gives the rotation around the X-axis
|
|
m_wheel_rotation_dt = m_speed*dt / m_kart_properties->getWheelRadius();
|
|
m_wheel_rotation += m_wheel_rotation_dt;
|
|
m_wheel_rotation = fmodf(m_wheel_rotation, 2*M_PI);
|
|
|
|
if(m_kart_animation)
|
|
m_kart_animation->update(dt);
|
|
|
|
m_attachment->update(dt);
|
|
|
|
m_kart_gfx->update(dt);
|
|
if (m_collision_particles) m_collision_particles->update(dt);
|
|
|
|
updatePhysics(dt);
|
|
|
|
if(!m_controls.m_fire) m_fire_clicked = 0;
|
|
|
|
if(m_controls.m_fire && !m_fire_clicked && !m_kart_animation)
|
|
{
|
|
// 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() ;
|
|
World::getWorld()->onFirePressed(getController());
|
|
m_bubble_drop = true;
|
|
m_fire_clicked = 1;
|
|
}
|
|
|
|
/* (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(World::getWorld()->getTrack()->isAutoRescueEnabled() &&
|
|
!getKartAnimation() && fabs(getRoll())>60*DEGREE_TO_RAD &&
|
|
fabs(getSpeed())<3.0f )
|
|
{
|
|
new RescueAnimation(this, /*is_auto_rescue*/true);
|
|
}
|
|
|
|
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;
|
|
}
|
|
m_terrain_info->update(pos_plus_epsilon);
|
|
if(m_body->getBroadphaseHandle())
|
|
{
|
|
m_body->getBroadphaseHandle()->m_collisionFilterGroup = old_group;
|
|
}
|
|
handleMaterialGFX();
|
|
const Material* material=m_terrain_info->getMaterial();
|
|
if (!material) // 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 && !m_flying &&
|
|
!getKartAnimation())
|
|
new RescueAnimation(this);
|
|
}
|
|
else
|
|
{
|
|
handleMaterialSFX(material);
|
|
if (material->isDriveReset() && isOnGround())
|
|
new RescueAnimation(this);
|
|
else if(material->isZipper() && isOnGround())
|
|
{
|
|
handleZipper(material);
|
|
showZipperFire();
|
|
}
|
|
else
|
|
{
|
|
m_max_speed->setSlowdown(MaxSpeed::MS_DECREASE_TERRAIN,
|
|
material->getMaxSpeedFraction(),
|
|
material->getSlowDownTime() );
|
|
#ifdef DEBUG
|
|
if(UserConfigParams::m_material_debug)
|
|
{
|
|
Log::info("Kart","%s\tfraction %f\ttime %f.\n",
|
|
material->getTexFname().c_str(),
|
|
material->getMaxSpeedFraction(),
|
|
material->getSlowDownTime() );
|
|
}
|
|
#endif
|
|
}
|
|
} // if there is material
|
|
|
|
// Check if any item was hit.
|
|
// check it if we're not in a network world, or if we're on the server (when network mode is on)
|
|
if (!NetworkWorld::getInstance()->isRunning() || NetworkManager::getInstance()->isServer())
|
|
ItemManager::get()->checkItemHit(this);
|
|
|
|
static video::SColor pink(255, 255, 133, 253);
|
|
static video::SColor green(255, 61, 87, 23);
|
|
|
|
// draw skidmarks if relevant (we force pink skidmarks on when hitting a bubblegum)
|
|
if(m_kart_properties->getSkiddingProperties()->hasSkidmarks())
|
|
{
|
|
m_skidmarks->update(dt,
|
|
m_bubblegum_time > 0,
|
|
(m_bubblegum_time > 0 ? (m_has_caught_nolok_bubblegum ? &green : &pink) : NULL) );
|
|
}
|
|
|
|
const bool emergency = getKartAnimation()!=NULL;
|
|
|
|
if (emergency)
|
|
{
|
|
m_view_blocked_by_plunger = 0.0f;
|
|
if (m_flying)
|
|
{
|
|
stopFlying();
|
|
m_flying = false;
|
|
}
|
|
}
|
|
|
|
// 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())
|
|
{
|
|
const Material *m = getMaterial();
|
|
const Material *last_m = getLastMaterial();
|
|
|
|
// A jump starts only the kart isn't already jumping, is on a new
|
|
// (or no) texture.
|
|
if(!m_is_jumping && last_m && last_m!=m )
|
|
{
|
|
float v = getVelocity().getY();
|
|
float force = World::getWorld()->getTrack()->getGravity();;
|
|
// Velocity / force is the time it takes to reach the peak
|
|
// of the jump (i.e. when vertical speed becomes 0). Assuming
|
|
// that jump start height and end height are the same, it will
|
|
// take the same time again to reach the bottom
|
|
float t = 2.0f * v/force;
|
|
|
|
// Jump if either the jump is estimated to be long enough, or
|
|
// the texture has the jump property set.
|
|
if(t>getKartProperties()->getJumpAnimationTime() ||
|
|
last_m->isJumpTexture() )
|
|
m_kart_model->setAnimation(KartModel::AF_JUMP_START);
|
|
m_is_jumping = true;
|
|
}
|
|
m_jump_time+=dt;
|
|
}
|
|
else if (m_is_jumping)
|
|
{
|
|
// Kart touched ground again
|
|
m_is_jumping = false;
|
|
HitEffect *effect = new Explosion(getXYZ(), "jump",
|
|
"jump_explosion.xml");
|
|
projectile_manager->addHitEffect(effect);
|
|
m_kart_model->setAnimation(KartModel::AF_DEFAULT);
|
|
m_jump_time = 0;
|
|
}
|
|
|
|
if( (!isOnGround() || emergency) && m_shadow_enabled)
|
|
{
|
|
m_shadow_enabled = false;
|
|
m_shadow->disableShadow();
|
|
}
|
|
if(!m_shadow_enabled && isOnGround() && !emergency)
|
|
{
|
|
m_shadow->enableShadow();
|
|
m_shadow_enabled = true;
|
|
}
|
|
} // update
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Show fire to go with a zipper.
|
|
*/
|
|
void Kart::showZipperFire()
|
|
{
|
|
m_kart_gfx->setCreationRateAbsolute(KartGFX::KGFX_ZIPPER, 800.0f);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Squashes this kart: it will scale the kart in up direction, and causes
|
|
* a slowdown while this kart is squashed.
|
|
* \param time How long the kart will be squashed. A value of 0 will reset
|
|
* the kart to be unsquashed.
|
|
* \param slowdown Reduction of max speed.
|
|
*/
|
|
void Kart::setSquash(float time, float slowdown)
|
|
{
|
|
if (isInvulnerable()) return;
|
|
|
|
if (isShielded())
|
|
{
|
|
decreaseShieldTime(stk_config->m_bubblegum_shield_time/2.0f);
|
|
Log::verbose("Kart", "Decreasing shield \n");
|
|
return;
|
|
}
|
|
|
|
if(m_attachment->getType()==Attachment::ATTACH_BOMB && time>0)
|
|
{
|
|
ExplosionAnimation::create(this);
|
|
return;
|
|
}
|
|
m_node->setScale(core::vector3df(1.0f, 0.5f, 1.0f));
|
|
m_max_speed->setSlowdown(MaxSpeed::MS_DECREASE_SQUASH, slowdown,
|
|
0.1f, time);
|
|
m_squash_time = time;
|
|
} // setSquash
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Plays any terrain specific sound effect.
|
|
*/
|
|
void Kart::handleMaterialSFX(const Material *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(getLastMaterial()!=material)
|
|
{
|
|
// First stop any previously playing terrain sound
|
|
// and remove it, so 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);
|
|
|
|
// In multiplayer mode sounds are NOT positional, because we have
|
|
// multiple listeners. This would make the sounds of all AIs be
|
|
// audible at all times. So silence AI karts.
|
|
if (race_manager->getNumLocalPlayers() > 1)
|
|
{
|
|
if (!m_controller->isPlayerController())
|
|
{
|
|
m_terrain_sound->volume( 0.0f );
|
|
}
|
|
}
|
|
|
|
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_STOPPED)
|
|
{
|
|
// 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;
|
|
}
|
|
|
|
// terrain sound is not necessarily a looping sound so check its status before
|
|
// setting its speed, to avoid 'ressuscitating' sounds that had already stopped
|
|
if(m_terrain_sound
|
|
&& (m_terrain_sound->getStatus() == SFXManager::SFX_PLAYING
|
|
|| m_terrain_sound->getStatus() == SFXManager::SFX_PAUSED))
|
|
{
|
|
m_terrain_sound->position(getXYZ());
|
|
material->setSFXSpeed(m_terrain_sound, m_speed);
|
|
}
|
|
|
|
} // handleMaterialSFX
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Handles material specific GFX, mostly particle effects. Particle
|
|
* effects can be triggered by two different situations: either
|
|
* because a kart drives on top of a terrain with a special effect,
|
|
* or because the kart is driving or falling under a surface (e.g.
|
|
* water), and the terrain effect is coming from that surface. Those
|
|
* effects are exclusive - you either get the effect from the terrain
|
|
* you are driving on, or the effect from a surface the kart is
|
|
* (partially) under. The surface effect is triggered, if either the
|
|
* kart is falling, or if the surface the kart is driving on has
|
|
* the 'isBelowSurface' property set.
|
|
*/
|
|
void Kart::handleMaterialGFX()
|
|
{
|
|
const Material *material = getMaterial();
|
|
|
|
// First test: give the terrain effect, if the kart is
|
|
// on top of a surface (i.e. not falling), actually touching
|
|
// something with the wheels, and the material has not the
|
|
// below surface property set.
|
|
if (material && isOnGround() && !material->isBelowSurface() &&
|
|
!getKartAnimation() && UserConfigParams::m_graphical_effects)
|
|
{
|
|
|
|
// Get the appropriate particle data depending on
|
|
// wether the kart is skidding or driving.
|
|
const ParticleKind* pk =
|
|
material->getParticlesWhen(m_skidding->isSkidding()
|
|
? Material::EMIT_ON_SKID
|
|
: Material::EMIT_ON_DRIVE);
|
|
if(!pk)
|
|
{
|
|
// Disable potentially running particle effects
|
|
m_kart_gfx->setCreationRateAbsolute(KartGFX::KGFX_TERRAIN, 0);
|
|
return; // no particle effect, return
|
|
}
|
|
m_kart_gfx->updateTerrain(pk);
|
|
return;
|
|
}
|
|
|
|
// Now the kart is either falling, or driving on a terrain which
|
|
// has the 'below surface' flag set. Detect if there is a surface
|
|
// on top of the kart.
|
|
// --------------------------------------------------------------
|
|
if (m_controller->isPlayerController() && !hasFinishedRace())
|
|
{
|
|
for(unsigned int i=0; i<Camera::getNumCameras(); i++)
|
|
{
|
|
Camera *camera = Camera::getCamera(i);
|
|
if(camera->getKart()!=this) continue;
|
|
|
|
if (material && material->hasFallingEffect() && !m_flying)
|
|
{
|
|
camera->setMode(Camera::CM_FALLING);
|
|
}
|
|
else if (camera->getMode() != Camera::CM_NORMAL &&
|
|
camera->getMode() != Camera::CM_REVERSE)
|
|
{
|
|
camera->setMode(Camera::CM_NORMAL);
|
|
}
|
|
} // for i in all cameras for this kart
|
|
} // camera != final camera
|
|
|
|
if (!UserConfigParams::m_graphical_effects)
|
|
return;
|
|
|
|
// Use the middle of the contact points of the two rear wheels
|
|
// as the point from which to cast the ray upwards
|
|
const btWheelInfo::RaycastInfo &ri2 =
|
|
getVehicle()->getWheelInfo(2).m_raycastInfo;
|
|
const btWheelInfo::RaycastInfo &ri3 =
|
|
getVehicle()->getWheelInfo(3).m_raycastInfo;
|
|
Vec3 from = (ri2.m_contactPointWS + ri3.m_contactPointWS)*0.5f;
|
|
Vec3 xyz;
|
|
const Material *surface_material;
|
|
if(!m_terrain_info->getSurfaceInfo(from, &xyz, &surface_material))
|
|
{
|
|
m_kart_gfx->setCreationRateAbsolute(KartGFX::KGFX_TERRAIN, 0);
|
|
return;
|
|
}
|
|
const ParticleKind *pk =
|
|
surface_material->getParticlesWhen(Material::EMIT_ON_DRIVE);
|
|
|
|
if(!pk || m_flying || dynamic_cast<RescueAnimation*>(getKartAnimation()))
|
|
return;
|
|
|
|
// Now the kart is under a surface, and there is a surface effect
|
|
// --------------------------------------------------------------
|
|
m_kart_gfx->setParticleKind(KartGFX::KGFX_TERRAIN, pk);
|
|
m_kart_gfx->setXYZ(KartGFX::KGFX_TERRAIN, xyz);
|
|
|
|
const float distance = xyz.distance2(from);
|
|
float ratio;
|
|
if (distance < 2.0f) ratio = 1.0f;
|
|
else if (distance < 4.0f) ratio = (4.0f-distance)*0.5f;
|
|
else ratio = -1.0f; // No more particles
|
|
m_kart_gfx->setCreationRateRelative(KartGFX::KGFX_TERRAIN, ratio);
|
|
|
|
// Play special sound effects for this terrain
|
|
// -------------------------------------------
|
|
const std::string &s = surface_material->getSFXName();
|
|
if (s != "" && !dynamic_cast<RescueAnimation*>(getKartAnimation())&&
|
|
(m_terrain_sound == NULL ||
|
|
m_terrain_sound->getStatus() == SFXManager::SFX_STOPPED))
|
|
{
|
|
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);
|
|
|
|
m_terrain_sound = sfx_manager->createSoundSource(s);
|
|
m_terrain_sound->play();
|
|
m_terrain_sound->setLoop(false);
|
|
}
|
|
|
|
} // handleMaterialGFX
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Sets zipper time, and apply one time additional speed boost. It can be
|
|
* used with a specific material, in which case the zipper parmaters are
|
|
* taken from this material (parameters that are <0 will be using the
|
|
* kart-specific values from kart-properties.
|
|
* \param material If not NULL, will be used to determine the zipper
|
|
* parameters, otherwise the defaults from kart properties
|
|
* will be used.
|
|
* \param play_sound If true this will cause a sfx to be played even if the
|
|
* terrain hasn't changed. It is used by the zipper powerup.
|
|
*/
|
|
void Kart::handleZipper(const Material *material, bool play_sound)
|
|
{
|
|
/** The additional speed allowed on top of the kart-specific maximum kart
|
|
* speed. */
|
|
float max_speed_increase;
|
|
|
|
/**Time the zipper stays activated. */
|
|
float duration;
|
|
/** A one time additional speed gain - the kart will instantly add this
|
|
* amount of speed to its current speed. */
|
|
float speed_gain;
|
|
/** Time it takes for the zipper advantage to fade out. */
|
|
float fade_out_time;
|
|
/** Additional engine force. */
|
|
float engine_force;
|
|
|
|
if(material)
|
|
{
|
|
material->getZipperParameter(&max_speed_increase, &duration,
|
|
&speed_gain, &fade_out_time, &engine_force);
|
|
if(max_speed_increase<0)
|
|
max_speed_increase = m_kart_properties->getZipperMaxSpeedIncrease();
|
|
if(duration<0)
|
|
duration = m_kart_properties->getZipperTime();
|
|
if(speed_gain<0)
|
|
speed_gain = m_kart_properties->getZipperSpeedGain();
|
|
if(fade_out_time<0)
|
|
fade_out_time = m_kart_properties->getZipperFadeOutTime();
|
|
if(engine_force<0)
|
|
engine_force = m_kart_properties->getZipperForce();
|
|
}
|
|
else
|
|
{
|
|
max_speed_increase = m_kart_properties->getZipperMaxSpeedIncrease();
|
|
duration = m_kart_properties->getZipperTime();
|
|
speed_gain = m_kart_properties->getZipperSpeedGain();
|
|
fade_out_time = m_kart_properties->getZipperFadeOutTime();
|
|
engine_force = m_kart_properties->getZipperForce();
|
|
}
|
|
// Ignore a zipper that's activated while braking
|
|
if(m_controls.m_brake || m_speed<0) return;
|
|
|
|
m_max_speed->instantSpeedIncrease(MaxSpeed::MS_INCREASE_ZIPPER,
|
|
max_speed_increase, speed_gain,
|
|
engine_force, duration, fade_out_time);
|
|
// Play custom character sound (weee!)
|
|
playCustomSFX(SFXManager::CUSTOM_ZIPPER);
|
|
m_controller->handleZipper(play_sound);
|
|
} // handleZipper
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Updates the current nitro status.
|
|
* \param dt Time step size.
|
|
*/
|
|
void Kart::updateNitro(float dt)
|
|
{
|
|
if (m_controls.m_nitro && m_min_nitro_time <= 0.0f)
|
|
{
|
|
m_min_nitro_time = m_kart_properties->getNitroMinConsumptionTime();
|
|
}
|
|
if (m_min_nitro_time > 0.0f)
|
|
{
|
|
m_min_nitro_time -= dt;
|
|
|
|
// when pressing the key, don't allow the min time to go under zero.
|
|
// If it went under zero, it would be reset
|
|
if (m_controls.m_nitro && m_min_nitro_time <= 0.0f)
|
|
m_min_nitro_time = 0.1f;
|
|
}
|
|
|
|
bool increase_speed = (m_controls.m_nitro && isOnGround());
|
|
if (!increase_speed && m_min_nitro_time <= 0.0f)
|
|
{
|
|
return;
|
|
}
|
|
m_collected_energy -= dt * m_kart_properties->getNitroConsumption();
|
|
if (m_collected_energy < 0)
|
|
{
|
|
m_collected_energy = 0;
|
|
return;
|
|
}
|
|
|
|
if (increase_speed)
|
|
{
|
|
m_max_speed->increaseMaxSpeed(MaxSpeed::MS_INCREASE_NITRO,
|
|
m_kart_properties->getNitroMaxSpeedIncrease(),
|
|
m_kart_properties->getNitroEngineForce(),
|
|
m_kart_properties->getNitroDuration(),
|
|
m_kart_properties->getNitroFadeOutTime() );
|
|
}
|
|
} // updateNitro
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Activates a slipstream effect */
|
|
void Kart::setSlipstreamEffect(float f)
|
|
{
|
|
m_kart_gfx->setCreationRateAbsolute(KartGFX::KGFX_ZIPPER, f);
|
|
} // setSlipstreamEffect
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Called when the kart crashes against another kart.
|
|
* \param k The kart that was hit.
|
|
* \param update_attachments If true the attachment of this kart and the
|
|
* other kart hit will be updated (e.g. bombs will be moved)
|
|
*/
|
|
void Kart::crashed(AbstractKart *k, bool update_attachments)
|
|
{
|
|
if(update_attachments)
|
|
{
|
|
assert(k);
|
|
getAttachment()->handleCollisionWithKart(k);
|
|
}
|
|
m_controller->crashed(k);
|
|
crashed();
|
|
} // crashed(Kart, update_attachments
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Kart hits the track with a given material.
|
|
* \param m Material hit, can be NULL if no specific material exists.
|
|
*/
|
|
void Kart::crashed(const Material *m, const Vec3 &normal)
|
|
{
|
|
#ifdef DEBUG
|
|
// Simple debug output for people playing without sound.
|
|
// This makes it easier to see if a kart hit the track (esp.
|
|
// after a jump).
|
|
// FIXME: This should be removed once the physics are fixed.
|
|
if(UserConfigParams::m_physics_debug)
|
|
{
|
|
// Add a counter to make it easier to see if a new line of
|
|
// output was added.
|
|
static int counter=0;
|
|
Log::info("Kart","Kart %s hit track: %d material %s.\n",
|
|
getIdent().c_str(), counter++,
|
|
m ? m->getTexFname().c_str() : "None");
|
|
}
|
|
#endif
|
|
|
|
const LinearWorld *lw = dynamic_cast<LinearWorld*>(World::getWorld());
|
|
if(getKartProperties()->getTerrainImpulseType()
|
|
==KartProperties::IMPULSE_NORMAL &&
|
|
m_vehicle->getCentralImpulseTime()<=0 )
|
|
{
|
|
// Restrict impule to plane defined by gravity (i.e. X/Z plane).
|
|
// This avoids the problem that karts can be pushed up, e.g. above
|
|
// a fence.
|
|
btVector3 gravity = m_body->getGravity();
|
|
gravity.normalize();
|
|
// Cast necessary since otherwise to operator- (vec3/btvector) exists
|
|
Vec3 impulse = (btVector3)normal - gravity* btDot(normal, gravity);
|
|
if(impulse.getX() || impulse.getZ())
|
|
impulse.normalize();
|
|
else
|
|
impulse = Vec3(0, 0, -1); // Arbitrary
|
|
impulse *= m_kart_properties->getCollisionTerrainImpulse();
|
|
m_bounce_back_time = 0.2f;
|
|
m_vehicle->setTimedCentralImpulse(0.1f, impulse);
|
|
}
|
|
// If there is a quad graph, push the kart towards the previous
|
|
// graph node center (we have to use the previous point since the
|
|
// kart might have only now reached the new quad, meaning the kart
|
|
// would be pushed forward).
|
|
else if(getKartProperties()->getTerrainImpulseType()
|
|
==KartProperties::IMPULSE_TO_DRIVELINE &&
|
|
lw && m_vehicle->getCentralImpulseTime()<=0 &&
|
|
World::getWorld()->getTrack()->isPushBackEnabled())
|
|
{
|
|
int sector = lw->getSectorForKart(this);
|
|
if(sector!=QuadGraph::UNKNOWN_SECTOR)
|
|
{
|
|
// Use the first predecessor node, which is the most
|
|
// natural one (i.e. the one on the main driveline).
|
|
const GraphNode &gn = QuadGraph::get()->getNode(
|
|
QuadGraph::get()->getNode(sector).getPredecessor(0));
|
|
Vec3 impulse = gn.getCenter() - getXYZ();
|
|
impulse.setY(0);
|
|
if(impulse.getX() || impulse.getZ())
|
|
impulse.normalize();
|
|
else
|
|
impulse = Vec3(0, 0, -1); // Arbitrary
|
|
impulse *= m_kart_properties->getCollisionTerrainImpulse();
|
|
m_bounce_back_time = 0.2f;
|
|
m_vehicle->setTimedCentralImpulse(0.1f, impulse);
|
|
}
|
|
|
|
}
|
|
/** 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(m && m->getCollisionReaction() != Material::NORMAL &&
|
|
!getKartAnimation())
|
|
{
|
|
std::string particles = m->getCrashResetParticles();
|
|
if (particles.size() > 0)
|
|
{
|
|
ParticleKind* kind =
|
|
ParticleKindManager::get()->getParticles(particles);
|
|
if (kind != NULL)
|
|
{
|
|
if (m_collision_particles == NULL)
|
|
{
|
|
Vec3 position(-getKartWidth()*0.35f, 0.06f,
|
|
getKartLength()*0.5f);
|
|
m_collision_particles =
|
|
new ParticleEmitter(kind, position, getNode());
|
|
}
|
|
else
|
|
{
|
|
m_collision_particles->setParticleType(kind);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log::error("Kart","Unknown particles kind <%s> in material "
|
|
"crash-reset properties\n", particles.c_str());
|
|
}
|
|
}
|
|
|
|
if (m->getCollisionReaction() == Material::RESCUE)
|
|
{
|
|
new RescueAnimation(this);
|
|
}
|
|
else if (m->getCollisionReaction() == Material::PUSH_BACK)
|
|
{
|
|
if (m_bounce_back_time <= 0.2f) // this variable is sometimes set to 0.1 somewhere else
|
|
{
|
|
btVector3 push = m_body->getLinearVelocity().normalized();
|
|
push[1] = 0.1f;
|
|
m_body->applyCentralImpulse( -4000.0f*push );
|
|
m_bounce_back_time = 2.0f;
|
|
|
|
core::stringw msg = _("You need more points\nto enter this challenge!");
|
|
std::vector<core::stringw> parts = StringUtils::split(msg, '\n', false);
|
|
|
|
// For now, until we have scripting, special-case the overworld... (TODO)
|
|
if (dynamic_cast<OverWorld*>(World::getWorld()) != NULL)
|
|
{
|
|
sfx_manager->quickSound("forcefield");
|
|
|
|
for (unsigned int n = 0; n < parts.size(); n++)
|
|
{
|
|
World::getWorld()->getRaceGUI()->addMessage(parts[n], NULL, 4.0f,
|
|
video::SColor(255, 255,255,255),
|
|
true, true);
|
|
} // for n<parts.size()
|
|
} // if world exist
|
|
} // if m_bounce_back_time <= 0.2f
|
|
} // if (m->getCollisionReaction() == Material::PUSH_BACK)
|
|
} // if(m && m->getCollisionReaction() != Material::NORMAL &&
|
|
// !getKartAnimation())
|
|
m_controller->crashed(m);
|
|
crashed();
|
|
} // crashed(Material)
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Common code used when a kart or a material was hit.
|
|
*/
|
|
void Kart::crashed()
|
|
{
|
|
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)
|
|
{
|
|
if (m_body->getLinearVelocity().length()> 0.555f)
|
|
{
|
|
// 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
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Plays a beep sfx.
|
|
*/
|
|
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;
|
|
float f = m_kart_properties->getStartupBoost();
|
|
m_max_speed->instantSpeedIncrease(MaxSpeed::MS_INCREASE_ZIPPER,
|
|
0.9f*f, f,
|
|
/*engine_force*/200.0f,
|
|
/*duration*/5.0f,
|
|
/*fade_out_time*/5.0f);
|
|
}
|
|
|
|
m_bounce_back_time-=dt;
|
|
|
|
updateEnginePowerAndBrakes(dt);
|
|
|
|
// apply flying physics if relevant
|
|
if (m_flying)
|
|
updateFlying();
|
|
|
|
m_skidding->update(dt, isOnGround(), m_controls.m_steer,
|
|
m_controls.m_skid);
|
|
|
|
if(( m_skidding->getSkidState() == Skidding::SKID_ACCUMULATE_LEFT ||
|
|
m_skidding->getSkidState() == Skidding::SKID_ACCUMULATE_RIGHT ) &&
|
|
m_skidding->getGraphicalJumpOffset()==0)
|
|
{
|
|
if(m_skid_sound->getStatus() != SFXManager::SFX_PLAYING &&!isWheeless())
|
|
m_skid_sound->play();
|
|
}
|
|
else if(m_skid_sound->getStatus() == SFXManager::SFX_PLAYING)
|
|
{
|
|
m_skid_sound->stop();
|
|
}
|
|
|
|
float steering = getMaxSteerAngle() * m_skidding->getSteeringFraction();
|
|
m_vehicle->setSteeringValue(steering, 0);
|
|
m_vehicle->setSteeringValue(steering, 1);
|
|
|
|
updateSliding();
|
|
|
|
// Compute the speed of the kart.
|
|
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 speed if necessary
|
|
m_max_speed->update(dt);
|
|
|
|
// 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;
|
|
updateEngineSFX();
|
|
#ifdef XX
|
|
Log::info("Kart","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
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Adjust the engine sound effect depending on the speed of the kart.
|
|
*/
|
|
void Kart::updateEngineSFX()
|
|
{
|
|
// when going faster, use higher pitch for engine
|
|
if(!m_engine_sound || !sfx_manager->sfxAllowed())
|
|
return;
|
|
|
|
if(isOnGround())
|
|
{
|
|
float max_speed = m_max_speed->getCurrentMaxSpeed();
|
|
// 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 f = m_speed/max_speed;
|
|
// Speed at this stage is not yet capped, so it can be > 1, which
|
|
// results in odd engine sfx.
|
|
if (f>1.0f) f=1.0f;
|
|
|
|
float gears = 3.0f * fmod(f, 0.333334f);
|
|
m_engine_sound->speed(0.6f + (f +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());
|
|
} // updateEngineSFX
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Sets the engine power. It considers the engine specs, items that influence
|
|
* the available power, and braking/steering.
|
|
*/
|
|
void Kart::updateEnginePowerAndBrakes(float dt)
|
|
{
|
|
updateNitro(dt);
|
|
float engine_power = getActualWheelForce();
|
|
|
|
// apply parachute physics if relevant
|
|
if(m_attachment->getType()==Attachment::ATTACH_PARACHUTE)
|
|
engine_power*=0.2f;
|
|
|
|
// apply bubblegum physics if relevant
|
|
if (m_bubblegum_time > 0.0f)
|
|
{
|
|
engine_power = 0.0f;
|
|
m_body->applyTorque(btVector3(0.0, m_bubblegum_torque, 0.0));
|
|
}
|
|
|
|
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 advantage
|
|
if(m_controls.m_skid &&
|
|
m_kart_properties->getSkiddingProperties()->getSkidVisualTime()==0)
|
|
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())
|
|
m_vehicle->setAllBrakes(0);
|
|
}
|
|
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)
|
|
{ // Still going forward while braking
|
|
applyEngineForce(0.f);
|
|
|
|
//apply the brakes
|
|
m_vehicle->setAllBrakes(m_kart_properties->getBrakeFactor());
|
|
}
|
|
else // m_speed < 0
|
|
{
|
|
m_vehicle->setAllBrakes(0);
|
|
// going backward, apply reverse gear ratio (unless he goes
|
|
// too fast backwards)
|
|
if ( -m_speed < m_max_speed->getCurrentMaxSpeed()
|
|
*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
|
|
assert(!isnan(m_controls.m_accel));
|
|
assert(!isnan(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
|
|
if(abs(m_speed) < 5.0f)
|
|
m_vehicle->setAllBrakes(20.0f);
|
|
} // !m_brake
|
|
} // not accelerating
|
|
} // updateEnginePowerAndBrakes
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Handles sliding, i.e. the kart sliding off terrain that is too steep.
|
|
*/
|
|
void Kart::updateSliding()
|
|
{
|
|
// dynamically determine friction so that the kart looses its traction
|
|
// when trying to drive on too steep surfaces. Below angles of 0.25 rad,
|
|
// you have full traction; above 0.5 rad angles you have absolutely none;
|
|
// inbetween there is a linear change in friction
|
|
float friction = 1.0f;
|
|
bool enable_sliding = false;
|
|
|
|
// This way the current handling of sliding can be disabled
|
|
// for certain material (e.g. the curve in skyline on which otherwise
|
|
// karts could not drive).
|
|
// We also had a crash reported here, which was caused by not
|
|
// having a material here - no idea how this could have happened,
|
|
// but this problem is now avoided by testing if there is a material
|
|
if (isOnGround() &&
|
|
(!getMaterial() || !getMaterial()->highTireAdhesion()))
|
|
{
|
|
const btMatrix3x3 &m = m_vehicle->getChassisWorldTransform().getBasis();
|
|
// To get the angle between up=(0,1,0), we have to do:
|
|
// m*(0,1,0) to get the up vector of the kart, then the
|
|
// scalar product between this and (0,1,0) - which is m[1][1]:
|
|
float distanceFromUp = m[1][1];
|
|
|
|
if (distanceFromUp < 0.85f)
|
|
{
|
|
friction = 0.0f;
|
|
enable_sliding = true;
|
|
}
|
|
else if (distanceFromUp > 0.9f)
|
|
{
|
|
friction = 1.0f;
|
|
}
|
|
else
|
|
{
|
|
friction = (distanceFromUp - 0.85f) / 0.5f;
|
|
enable_sliding = true;
|
|
}
|
|
}
|
|
|
|
for (unsigned int i=0; i<4; i++)
|
|
{
|
|
btWheelInfo& wheel = m_vehicle->getWheelInfo(i);
|
|
wheel.m_frictionSlip = friction*m_kart_properties->getFrictionSlip();
|
|
}
|
|
|
|
m_vehicle->setSliding(enable_sliding);
|
|
} // updateSliding
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Adjusts kart translation if the kart is flying (in debug mode).
|
|
*/
|
|
void Kart::updateFlying()
|
|
{
|
|
if (m_controls.m_accel)
|
|
{
|
|
float orientation = getHeading();
|
|
m_body->applyCentralImpulse(btVector3(60.0f*sin(orientation), 0.0,
|
|
60.0f*cos(orientation)));
|
|
}
|
|
if (m_controls.m_steer != 0.0f)
|
|
{
|
|
m_body->applyTorque(btVector3(0.0, m_controls.m_steer * 3500.0f, 0.0));
|
|
}
|
|
if (m_controls.m_brake)
|
|
{
|
|
btVector3 velocity = m_body->getLinearVelocity();
|
|
|
|
const float x = velocity.x();
|
|
if (x > 0.2f) velocity.setX(x - 0.2f);
|
|
else if (x < -0.2f) velocity.setX(x + 0.2f);
|
|
else velocity.setX(0);
|
|
|
|
const float y = velocity.y();
|
|
if (y > 0.2f) velocity.setY(y - 0.2f);
|
|
else if (y < -0.2f) velocity.setY(y + 0.2f);
|
|
else velocity.setY(0);
|
|
|
|
const float z = velocity.z();
|
|
if (z > 0.2f) velocity.setZ(z - 0.2f);
|
|
else if (z < -0.2f) velocity.setZ(z + 0.2f);
|
|
else velocity.setZ(0);
|
|
|
|
m_body->setLinearVelocity(velocity);
|
|
|
|
} // if brake
|
|
|
|
// dampen any roll while flying, makes the kart hard to control
|
|
btVector3 velocity = m_body->getAngularVelocity();
|
|
velocity.setX(0);
|
|
velocity.setZ(0);
|
|
m_body->setAngularVelocity(velocity);
|
|
|
|
} // updateFlying
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Attaches the right model, creates the physics and loads all special
|
|
* effects (particle systems etc.)
|
|
* \param type Type of the kart.
|
|
* \param is_animated_model True if the model is animated.
|
|
*/
|
|
void Kart::loadData(RaceManager::KartType type, bool is_animated_model)
|
|
{
|
|
|
|
m_node = m_kart_model->attachModel(is_animated_model);
|
|
|
|
#ifdef DEBUG
|
|
m_node->setName( (getIdent()+"(lod-node)").c_str() );
|
|
#endif
|
|
|
|
// 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
|
|
|
|
Track *track = World::getWorld()->getTrack();
|
|
if (type == RaceManager::KT_PLAYER &&
|
|
UserConfigParams::m_weather_effects &&
|
|
track->getSkyParticles() != NULL)
|
|
{
|
|
track->getSkyParticles()->setBoxSizeXZ(150.0f, 150.0f);
|
|
|
|
m_sky_particles_emitter =
|
|
new ParticleEmitter(track->getSkyParticles(),
|
|
core::vector3df(0.0f, 40.0f, 100.0f),
|
|
getNode());
|
|
|
|
// FIXME: in multiplayer mode, this will result in several instances
|
|
// of the heightmap being calculated and kept in memory
|
|
m_sky_particles_emitter->addHeightMapAffector(track);
|
|
}
|
|
|
|
Vec3 position(0, getKartHeight()*0.35f, -getKartLength()*0.35f);
|
|
|
|
m_slipstream = new SlipStream(this);
|
|
|
|
if(m_kart_properties->getSkiddingProperties()->hasSkidmarks())
|
|
{
|
|
m_skidmarks = new SkidMarks(*this);
|
|
m_skidmarks->adjustFog(
|
|
track_manager->getTrack(race_manager->getTrackName())
|
|
->isFogEnabled() );
|
|
}
|
|
|
|
m_shadow = new Shadow(m_kart_properties->getShadowTexture(),
|
|
m_node,
|
|
m_kart_properties->getShadowScale(),
|
|
m_kart_properties->getShadowXOffset(),
|
|
m_kart_properties->getShadowYOffset());
|
|
|
|
World::getWorld()->kartAdded(this, 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)
|
|
{
|
|
assert(!isnan(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(float dt, 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_dt, 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() );
|
|
y += m_skidding->getGraphicalJumpOffset();
|
|
center_shift.setY(y);
|
|
|
|
if ((m_controls.m_nitro || m_min_nitro_time > 0.0f) && isOnGround() && m_collected_energy > 0)
|
|
{
|
|
// fabs(speed) is important, otherwise the negative number will
|
|
// become a huge unsigned number in the particle scene node!
|
|
float f = fabsf(getSpeed())/m_kart_properties->getMaxSpeed();
|
|
// The speed of the kart can be higher (due to powerups) than
|
|
// the normal maximum speed of the kart.
|
|
if(f>1.0f) f = 1.0f;
|
|
m_kart_gfx->setCreationRateRelative(KartGFX::KGFX_NITRO1, f);
|
|
m_kart_gfx->setCreationRateRelative(KartGFX::KGFX_NITRO2, f);
|
|
m_kart_gfx->setCreationRateRelative(KartGFX::KGFX_NITROSMOKE1, f);
|
|
m_kart_gfx->setCreationRateRelative(KartGFX::KGFX_NITROSMOKE2, f);
|
|
}
|
|
else
|
|
{
|
|
m_kart_gfx->setCreationRateAbsolute(KartGFX::KGFX_NITRO1, 0);
|
|
m_kart_gfx->setCreationRateAbsolute(KartGFX::KGFX_NITRO2, 0);
|
|
m_kart_gfx->setCreationRateAbsolute(KartGFX::KGFX_NITROSMOKE1, 0);
|
|
m_kart_gfx->setCreationRateAbsolute(KartGFX::KGFX_NITROSMOKE2, 0);
|
|
|
|
}
|
|
m_kart_gfx->resizeBox(KartGFX::KGFX_NITRO1, getSpeed(), dt);
|
|
m_kart_gfx->resizeBox(KartGFX::KGFX_NITRO2, getSpeed(), dt);
|
|
m_kart_gfx->resizeBox(KartGFX::KGFX_NITROSMOKE1, getSpeed(), dt);
|
|
m_kart_gfx->resizeBox(KartGFX::KGFX_NITROSMOKE2, getSpeed(), dt);
|
|
|
|
m_kart_gfx->resizeBox(KartGFX::KGFX_ZIPPER, getSpeed(), dt);
|
|
|
|
// Note that we compare with maximum speed of the kart, not
|
|
// maximum speed including terrain effects. This avoids that
|
|
// leaning might get less if a kart gets a special that increases
|
|
// its maximum speed, but not the current speed (by much). On the
|
|
// other hand, that ratio can often be greater than 1.
|
|
float speed_frac = m_speed / m_kart_properties->getMaxSpeed();
|
|
if(speed_frac>1.0f)
|
|
speed_frac = 1.0f;
|
|
else if (speed_frac < 0.0f) // no leaning when backwards driving
|
|
speed_frac = 0.0f;
|
|
|
|
const float steer_frac = m_skidding->getSteeringFraction();
|
|
|
|
const float roll_speed = m_kart_properties->getLeanSpeed();
|
|
if(speed_frac > 0.8f && fabsf(steer_frac)>0.5f)
|
|
{
|
|
// Use steering ^ 7, which means less effect at lower
|
|
// steering
|
|
const float f = m_skidding->getSteeringFraction();
|
|
const float f2 = f*f;
|
|
const float max_lean = -m_kart_properties->getMaxLean()
|
|
* f2*f2*f2*f
|
|
* speed_frac;
|
|
if(max_lean>0)
|
|
{
|
|
m_current_lean += dt* roll_speed;
|
|
if(m_current_lean > max_lean)
|
|
m_current_lean = max_lean;
|
|
}
|
|
else if(max_lean<0)
|
|
{
|
|
m_current_lean -= dt*roll_speed;
|
|
if(m_current_lean < max_lean)
|
|
m_current_lean = max_lean;
|
|
}
|
|
}
|
|
else if(m_current_lean!=0.0f)
|
|
{
|
|
// Disable any potential roll factor that is still applied
|
|
// --------------------------------------------------------
|
|
if(m_current_lean>0)
|
|
{
|
|
m_current_lean -= dt * roll_speed;
|
|
if(m_current_lean < 0.0f)
|
|
m_current_lean = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
m_current_lean += dt * roll_speed;
|
|
if(m_current_lean>0.0f)
|
|
m_current_lean = 0.0f;
|
|
}
|
|
}
|
|
|
|
float heading = m_skidding->getVisualSkidRotation();
|
|
Moveable::updateGraphics(dt, center_shift,
|
|
btQuaternion(heading, 0, m_current_lean));
|
|
|
|
#ifdef XX
|
|
// cheap wheelie effect
|
|
if (m_controls.m_nitro)
|
|
{
|
|
m_node->updateAbsolutePosition();
|
|
m_kart_model->getWheelNodes()[0]->updateAbsolutePosition();
|
|
float wheel_y = m_kart_model->getWheelNodes()[0]->getAbsolutePosition().Y;
|
|
|
|
core::vector3df rot = m_node->getRotation();
|
|
|
|
float ratio = 0.8f; //float(m_zipper_fire->getCreationRate())
|
|
// /float(m_zipper_fire->getParticlesInfo()->getMaxRate());
|
|
|
|
const float a = (13.4f - ratio*13.0f);
|
|
float dst = -45.0f*sin((a*a)/180.f*M_PI);
|
|
|
|
rot.X = dst;
|
|
m_node->setRotation(rot);
|
|
|
|
m_node->updateAbsolutePosition();
|
|
m_kart_model->getWheelNodes()[0]->updateAbsolutePosition();
|
|
float wheel_y_after = m_kart_model->getWheelNodes()[0]->getAbsolutePosition().Y;
|
|
|
|
m_node->setPosition(m_node->getPosition() + core::vector3df(0,wheel_y_after - wheel_y,0));
|
|
}
|
|
#endif
|
|
|
|
} // updateGraphics
|
|
|
|
// ----------------------------------------------------------------------------
|
|
btQuaternion Kart::getVisualRotation() const
|
|
{
|
|
return getRotation()
|
|
* btQuaternion(m_skidding->getVisualSkidRotation(), 0, 0);
|
|
} // getVisualRotation
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Sets a text that is being displayed on top of a kart. This can be 'leader'
|
|
* for the leader kart in a FTL race, the name of a driver, or even debug
|
|
* output.
|
|
* \param text The text to display
|
|
*/
|
|
void Kart::setOnScreenText(const wchar_t *text)
|
|
{
|
|
core::dimension2d<u32> textsize = GUIEngine::getFont()->getDimension(text);
|
|
scene::ISceneManager* sm = irr_driver->getSceneManager();
|
|
// FIXME: Titlefont is the only font guaranteed to be loaded if STK
|
|
// is started without splash screen (since "Loading" is shown even in this
|
|
// case). A smaller font would be better
|
|
sm->addBillboardTextSceneNode(GUIEngine::getFont() ? GUIEngine::getFont()
|
|
: GUIEngine::getTitleFont(),
|
|
text,
|
|
getNode(),
|
|
core::dimension2df(textsize.Width/55.0f,
|
|
textsize.Height/55.0f),
|
|
core::vector3df(0.0f, 1.5f, 0.0f),
|
|
-1 /* id */,
|
|
video::SColor(255, 255, 225, 0),
|
|
video::SColor(255, 255, 89, 0));
|
|
// No need to store the reference to the billboard scene node:
|
|
// It has one reference to the parent, and will get deleted
|
|
// when the parent is deleted.
|
|
} // setOnScreenText
|
|
|
|
/* EOF */
|