3428 lines
129 KiB
C++
3428 lines
129 KiB
C++
//
|
|
// SuperTuxKart - a fun racing game with go-kart
|
|
// Copyright (C) 2004-2016 Steve Baker <sjbaker1@airmail.net>
|
|
// Copyright (C) 2006-2016 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 "audio/sfx_manager.hpp"
|
|
#include "audio/sfx_base.hpp"
|
|
#include "challenges/challenge_status.hpp"
|
|
#include "challenges/unlock_manager.hpp"
|
|
#include "config/player_manager.hpp"
|
|
#include "config/user_config.hpp"
|
|
#include "font/bold_face.hpp"
|
|
#include "font/font_manager.hpp"
|
|
#include "graphics/camera.hpp"
|
|
#include "graphics/central_settings.hpp"
|
|
#include "graphics/explosion.hpp"
|
|
#include "graphics/irr_driver.hpp"
|
|
#include "graphics/material.hpp"
|
|
#include "graphics/material_manager.hpp"
|
|
#include "graphics/particle_emitter.hpp"
|
|
#include "graphics/particle_kind_manager.hpp"
|
|
#include "graphics/shadow.hpp"
|
|
#include "graphics/skid_marks.hpp"
|
|
#include "graphics/slip_stream.hpp"
|
|
#include "graphics/stk_text_billboard.hpp"
|
|
#include "graphics/stars.hpp"
|
|
#include "guiengine/scalable_font.hpp"
|
|
#include "io/file_manager.hpp"
|
|
#include "items/attachment.hpp"
|
|
#include "items/item_manager.hpp"
|
|
#include "items/powerup.hpp"
|
|
#include "items/projectile_manager.hpp"
|
|
#include "karts/abstract_characteristic.hpp"
|
|
#include "karts/abstract_kart_animation.hpp"
|
|
#include "karts/cached_characteristic.hpp"
|
|
#include "karts/controller/local_player_controller.hpp"
|
|
#include "karts/controller/end_controller.hpp"
|
|
#include "karts/controller/spare_tire_ai.hpp"
|
|
#include "karts/explosion_animation.hpp"
|
|
#include "karts/kart_gfx.hpp"
|
|
#include "karts/kart_model.hpp"
|
|
#include "karts/kart_properties.hpp"
|
|
#include "karts/kart_properties_manager.hpp"
|
|
#include "karts/kart_rewinder.hpp"
|
|
#include "karts/max_speed.hpp"
|
|
#include "karts/rescue_animation.hpp"
|
|
#include "karts/skidding.hpp"
|
|
#include "main_loop.hpp"
|
|
#include "modes/capture_the_flag.hpp"
|
|
#include "modes/linear_world.hpp"
|
|
#include "modes/overworld.hpp"
|
|
#include "modes/soccer_world.hpp"
|
|
#include "network/compress_network_body.hpp"
|
|
#include "network/network_config.hpp"
|
|
#include "network/protocols/client_lobby.hpp"
|
|
#include "network/race_event_manager.hpp"
|
|
#include "network/rewind_info.hpp"
|
|
#include "network/rewind_manager.hpp"
|
|
#include "physics/btKart.hpp"
|
|
#include "physics/btKartRaycast.hpp"
|
|
#include "physics/physics.hpp"
|
|
#include "race/history.hpp"
|
|
#include "tracks/terrain_info.hpp"
|
|
#include "tracks/drive_graph.hpp"
|
|
#include "tracks/drive_node.hpp"
|
|
#include "tracks/track.hpp"
|
|
#include "tracks/track_manager.hpp"
|
|
#include "tracks/track_sector.hpp"
|
|
#include "utils/constants.hpp"
|
|
#include "utils/helpers.hpp"
|
|
#include "utils/log.hpp" //TODO: remove after debugging is done
|
|
#include "utils/profiler.hpp"
|
|
#include "utils/string_utils.hpp"
|
|
#include "utils/translation.hpp"
|
|
#include "utils/vs.hpp"
|
|
|
|
#include <ICameraSceneNode.h>
|
|
#include <ISceneManager.h>
|
|
|
|
#include <algorithm> // for min and max
|
|
#include <iostream>
|
|
#include <limits>
|
|
#include <cmath>
|
|
|
|
|
|
#if defined(WIN32) && !defined(__CYGWIN__) && !defined(__MINGW32__)
|
|
// 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, unsigned int world_kart_id,
|
|
int position, const btTransform& init_transform,
|
|
HandicapLevel handicap, std::shared_ptr<RenderInfo> ri)
|
|
: AbstractKart(ident, world_kart_id, position, init_transform,
|
|
handicap, ri)
|
|
|
|
#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_initial_position = position;
|
|
m_race_result = false;
|
|
m_wheel_box = NULL;
|
|
m_collision_particles = NULL;
|
|
m_controller = NULL;
|
|
m_saved_controller = NULL;
|
|
m_consumption_per_tick = stk_config->ticks2Time(1) *
|
|
m_kart_properties->getNitroConsumption();
|
|
m_fire_clicked = 0;
|
|
m_default_suspension_force = 0.0f;
|
|
m_boosted_ai = false;
|
|
m_type = RaceManager::KT_AI;
|
|
m_flying = false;
|
|
|
|
m_xyz_history_size = stk_config->time2Ticks(XYZ_HISTORY_TIME);
|
|
|
|
Vec3 initial_position = getXYZ();
|
|
for (int i=0;i<m_xyz_history_size;i++)
|
|
{
|
|
m_previous_xyz.push_back(initial_position);
|
|
m_previous_xyz_times.push_back(0.0f);
|
|
}
|
|
|
|
// 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_last_factor_engine_sound = 0.0f;
|
|
|
|
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] = SFXManager::get()->newSFX(id);
|
|
}
|
|
}*/
|
|
|
|
m_horn_sound = SFXManager::get()->getBuffer("horn");
|
|
m_crash_sounds[0] = SFXManager::get()->getBuffer("crash");
|
|
m_crash_sounds[1] = SFXManager::get()->getBuffer("crash2");
|
|
m_crash_sounds[2] = SFXManager::get()->getBuffer("crash3");
|
|
m_goo_sound = SFXManager::get()->getBuffer("goo");
|
|
m_boing_sound = SFXManager::get()->getBuffer("boing");
|
|
|
|
m_engine_sound = SFXManager::get()->createSoundSource(m_kart_properties->getEngineSfxType());
|
|
|
|
for (int i = 0; i < EMITTER_COUNT; i++)
|
|
m_emitters[i] = SFXManager::get()->createSoundSource("crash");
|
|
|
|
m_skid_sound = SFXManager::get()->createSoundSource( "skid" );
|
|
m_nitro_sound = SFXManager::get()->createSoundSource( "nitro" );
|
|
m_terrain_sound = NULL;
|
|
m_last_sound_material = 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)
|
|
{
|
|
m_type = type;
|
|
|
|
// In multiplayer mode, sounds are NOT positional
|
|
if (RaceManager::get()->getNumLocalPlayers() > 1)
|
|
{
|
|
float factor = 1.0f / RaceManager::get()->getNumberOfKarts();
|
|
// players have louder sounds than AIs
|
|
if (type == RaceManager::KT_PLAYER)
|
|
factor = std::min(1.0f, RaceManager::get()->getNumLocalPlayers()/2.0f);
|
|
|
|
for (int i = 0; i < EMITTER_COUNT; i++)
|
|
m_emitters[i]->setVolume(factor);
|
|
|
|
m_skid_sound->setVolume(factor);
|
|
m_nitro_sound->setVolume(factor);
|
|
} // if getNumLocalPlayers > 1
|
|
|
|
if(!m_engine_sound)
|
|
{
|
|
Log::error("Kart","Could not allocate a sfx object for the kart. Further errors may ensue!");
|
|
}
|
|
|
|
loadData(type, UserConfigParams::m_animated_characters);
|
|
reset();
|
|
} // init
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void Kart::changeKart(const std::string& new_ident,
|
|
HandicapLevel handicap,
|
|
std::shared_ptr<RenderInfo> ri)
|
|
{
|
|
AbstractKart::changeKart(new_ident, handicap, ri);
|
|
m_kart_model->setKart(this);
|
|
|
|
scene::ISceneNode* old_node = m_node;
|
|
loadData(m_type, UserConfigParams::m_animated_characters);
|
|
m_wheel_box = NULL;
|
|
|
|
if (LocalPlayerController* lpc =
|
|
dynamic_cast<LocalPlayerController*>(getController()))
|
|
lpc->initParticleEmitter();
|
|
|
|
if (old_node)
|
|
old_node->remove();
|
|
|
|
// Reset 1 more time (add back the body)
|
|
reset();
|
|
|
|
for (int i = 0; i < m_vehicle->getNumWheels(); i++)
|
|
{
|
|
btWheelInfo &wi = m_vehicle->getWheelInfo(i);
|
|
wi.m_raycastInfo.m_suspensionLength = m_default_suspension_force /
|
|
m_vehicle->getNumWheels();
|
|
}
|
|
m_graphical_y_offset = -m_default_suspension_force /
|
|
m_vehicle->getNumWheels() + m_kart_model->getLowestPoint();
|
|
m_kart_model->setDefaultSuspension();
|
|
startEngineSFX();
|
|
} // changeKart
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** 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)
|
|
SFXManager::get()->deleteSFX(m_custom_sounds[n]);
|
|
}*/
|
|
|
|
m_engine_sound->deleteSFX();
|
|
m_skid_sound ->deleteSFX();
|
|
|
|
for (int i = 0; i < EMITTER_COUNT; i++)
|
|
m_emitters[i]->deleteSFX();
|
|
|
|
m_nitro_sound ->deleteSFX();
|
|
if(m_terrain_sound) m_terrain_sound->deleteSFX();
|
|
if(m_previous_terrain_sound) m_previous_terrain_sound->deleteSFX();
|
|
if(m_collision_particles) delete m_collision_particles;
|
|
|
|
if (m_wheel_box) m_wheel_box->remove();
|
|
|
|
// Ghost karts don't have a body
|
|
if(m_body)
|
|
{
|
|
Physics::get()->removeKart(this);
|
|
}
|
|
|
|
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 && !isGhostKart())
|
|
{
|
|
m_flying = false;
|
|
stopFlying();
|
|
}
|
|
|
|
m_network_finish_check_ticks = 0;
|
|
m_network_confirmed_finish_ticks = 0;
|
|
// 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)
|
|
{
|
|
Physics::get()->removeKart(this);
|
|
Physics::get()->addKart(this);
|
|
}
|
|
|
|
m_min_nitro_ticks = 0;
|
|
m_energy_to_min_ratio = 0;
|
|
m_consumption_per_tick = stk_config->ticks2Time(1) *
|
|
m_kart_properties->getNitroConsumption();
|
|
|
|
// Reset star effect in case that it is currently being shown.
|
|
if (m_stars_effect)
|
|
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)
|
|
{
|
|
delete m_controller;
|
|
m_controller = m_saved_controller;
|
|
m_saved_controller = NULL;
|
|
}
|
|
m_kart_model->setAnimation(KartModel::AF_DEFAULT);
|
|
m_attachment->reset();
|
|
m_kart_gfx->reset();
|
|
m_skidding->reset();
|
|
|
|
m_weight = 0.0f;
|
|
updateWeight();
|
|
|
|
#ifndef SERVER_ONLY
|
|
if (m_collision_particles)
|
|
m_collision_particles->setCreationRateAbsolute(0.0f);
|
|
#endif
|
|
|
|
unsetSquash();
|
|
|
|
m_last_used_powerup = PowerupManager::POWERUP_NOTHING;
|
|
m_race_position = m_initial_position;
|
|
m_finished_race = false;
|
|
m_eliminated = false;
|
|
m_finish_time = 0.0f;
|
|
m_bubblegum_ticks = 0;
|
|
m_bubblegum_torque_sign = true;
|
|
m_invulnerable_ticks = 0;
|
|
m_min_nitro_ticks = 0;
|
|
m_energy_to_min_ratio = 0;
|
|
m_collected_energy = 0;
|
|
m_bounce_back_ticks = 0;
|
|
m_brake_ticks = 0;
|
|
m_ticks_last_crash = 0;
|
|
m_ticks_last_zipper = 0;
|
|
m_speed = 0.0f;
|
|
m_current_lean = 0.0f;
|
|
m_falling_time = 0.0f;
|
|
m_view_blocked_by_plunger = 0;
|
|
m_has_caught_nolok_bubblegum = false;
|
|
m_is_jumping = false;
|
|
m_flying = false;
|
|
m_startup_boost = 0.0f;
|
|
|
|
if (m_node)
|
|
m_node->setScale(core::vector3df(1.0f, 1.0f, 1.0f));
|
|
|
|
for (int i=0;i<m_xyz_history_size;i++)
|
|
{
|
|
m_previous_xyz[i] = getXYZ();
|
|
m_previous_xyz_times[i] = 0.0f;
|
|
}
|
|
m_time_previous_counter = 0.0f;
|
|
|
|
// In case that the kart was in the air, in which case its
|
|
// linear damping is 0
|
|
if(m_body)
|
|
m_body->setDamping(m_kart_properties->getStabilityChassisLinearDamping(),
|
|
m_kart_properties->getStabilityChassisAngularDamping());
|
|
|
|
if(m_terrain_sound)
|
|
{
|
|
m_terrain_sound->deleteSFX();
|
|
m_terrain_sound = NULL;
|
|
}
|
|
if(m_previous_terrain_sound)
|
|
{
|
|
m_previous_terrain_sound->deleteSFX();
|
|
m_previous_terrain_sound = NULL;
|
|
}
|
|
|
|
if(m_engine_sound)
|
|
m_engine_sound->stop();
|
|
|
|
m_controls.reset();
|
|
m_slipstream->reset();
|
|
|
|
if(m_vehicle)
|
|
{
|
|
for (unsigned int i = 0; i < 4; i++)
|
|
{
|
|
m_vehicle->getWheelInfo(i).m_steering = 0;
|
|
}
|
|
|
|
m_vehicle->reset();
|
|
}
|
|
|
|
setTrans(m_reset_transform);
|
|
|
|
applyEngineForce (0.0f);
|
|
|
|
AbstractKart::reset();
|
|
#ifndef SERVER_ONLY
|
|
if (m_skidmarks)
|
|
{
|
|
m_skidmarks->reset();
|
|
}
|
|
#endif
|
|
|
|
Vec3 front(0, 0, getKartLength()*0.5f);
|
|
m_xyz_front = getTrans()(front);
|
|
|
|
// Base on update() below, require if starting point of kart is not near
|
|
// 0, 0, 0 (like in battle arena)
|
|
m_terrain_info->update(getTrans().getBasis(),
|
|
getTrans().getOrigin() + getTrans().getBasis() * Vec3(0, 0.3f, 0));
|
|
|
|
// 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::setXYZ(const Vec3& a)
|
|
{
|
|
AbstractKart::setXYZ(a);
|
|
Vec3 front(0, 0, getKartLength()*0.5f);
|
|
m_xyz_front = getTrans()(front);
|
|
} // setXYZ
|
|
|
|
// -----------------------------------------------------------------------------
|
|
void Kart::increaseMaxSpeed(unsigned int category, float add_speed,
|
|
float engine_force, int duration,
|
|
int fade_out_time)
|
|
{
|
|
m_max_speed->increaseMaxSpeed(category, add_speed, engine_force,
|
|
duration, fade_out_time);
|
|
} // increaseMaxSpeed
|
|
|
|
// -----------------------------------------------------------------------------
|
|
void Kart::instantSpeedIncrease(unsigned int category, float add_max_speed,
|
|
float speed_boost, float engine_force,
|
|
int duration, int fade_out_time)
|
|
{
|
|
m_max_speed->instantSpeedIncrease(category, add_max_speed, speed_boost,
|
|
engine_force, duration, fade_out_time);
|
|
} // instantSpeedIncrease
|
|
|
|
// -----------------------------------------------------------------------------
|
|
void Kart::setSlowdown(unsigned int category, float max_speed_fraction,
|
|
int 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
|
|
// -----------------------------------------------------------------------------
|
|
int Kart::getSpeedIncreaseTicksLeft(unsigned int category) const
|
|
{
|
|
return m_max_speed->getSpeedIncreaseTicksLeft(category);
|
|
} // getSpeedIncreaseTimeLeft
|
|
|
|
// -----------------------------------------------------------------------------
|
|
void Kart::setBoostAI(bool boosted)
|
|
{
|
|
m_boosted_ai = boosted;
|
|
} // setBoostAI
|
|
// -----------------------------------------------------------------------------
|
|
bool Kart::getBoostAI() const
|
|
{
|
|
return m_boosted_ai;
|
|
} // getBoostAI
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** 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
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Sets the powerup this kart has last used. Number is always 1.
|
|
* \param t Type of the powerup.
|
|
*/
|
|
void Kart::setLastUsedPowerup(PowerupManager::PowerupType t)
|
|
{
|
|
m_last_used_powerup = t;
|
|
} // setLastUsedPowerup
|
|
|
|
// -----------------------------------------------------------------------------
|
|
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 = (int16_t)
|
|
stk_config->time2Ticks(m_kart_properties->getPlungerInFaceTime());
|
|
}
|
|
if(isShielded())
|
|
{
|
|
decreaseShieldTime();
|
|
}
|
|
} // 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);
|
|
*/
|
|
btTransform trans2;
|
|
trans2.setIdentity();
|
|
trans2.setRotation(btQuaternion(m_skidding->getVisualSkidRotation(), 0, 0));
|
|
trans *= trans2;
|
|
|
|
return trans;
|
|
} // getAlignedTransform
|
|
|
|
// ----------------------------------------------------------------------------
|
|
float Kart::getTimeFullSteer(float steer) const
|
|
{
|
|
return m_kart_properties->getTurnTimeFullSteer().get(steer);
|
|
} // getTimeFullSteer
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** 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
|
|
// -------------------------------------
|
|
const float kart_length = getKartLength();
|
|
const float kart_width = getKartWidth();
|
|
float kart_height = getKartHeight();
|
|
|
|
// improve physics for tall karts
|
|
if (kart_height > kart_length*0.6f)
|
|
{
|
|
kart_height = kart_length*0.6f;
|
|
}
|
|
|
|
const Vec3 &bevel = m_kart_properties->getBevelFactor();
|
|
Vec3 wheel_pos[4];
|
|
|
|
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 y = -1; y <= 1; y += 2)
|
|
{
|
|
for (int z = -1; z <= 1; z += 2)
|
|
{
|
|
for (int x = -1; x <= 1; x += 2)
|
|
{
|
|
Vec3 p(x*kart_width *0.5f,
|
|
y*kart_height *0.5f,
|
|
z*kart_length *0.5f);
|
|
|
|
hull->addPoint(p*orig_factor);
|
|
// Only add bevelled point if bevel is defined (i.e.!=0)
|
|
if(bevel.length2()>0)
|
|
hull->addPoint(p*bevel_factor);
|
|
if (y == -1)
|
|
{
|
|
int index = (x + 1) / 2 + 1 - z; // get index of wheel
|
|
float f = m_kart_properties->getPhysicalWheelPosition();
|
|
// f < 0 indicates to use the old physics position, i.e.
|
|
// to place the wheels outside of the chassis
|
|
if(f<0)
|
|
{
|
|
// All wheel positions are relative to the center of
|
|
// the collision shape.
|
|
wheel_pos[index].setX(x*0.5f*kart_width);
|
|
wheel_pos[index].setZ((0.5f*kart_length-0.25f)* z);
|
|
}
|
|
else
|
|
{
|
|
// Store the x/z position for the wheels as a weighted average
|
|
// of the two bevelled points (y is set below).
|
|
wheel_pos[index] = p*(orig_factor*(1.0f - f) + bevel_factor*f);
|
|
}
|
|
// The y position of the wheels (i.e. the points where
|
|
// the suspension is attached to) is just at the
|
|
// bottom of the kart (independent of collision shape).
|
|
// That is half the kart height down.
|
|
wheel_pos[index].setY(-0.5f*kart_height);
|
|
} // if y==-1
|
|
} // for x
|
|
} // for z
|
|
} // for y
|
|
|
|
// This especially enables proper drawing of the point cloud
|
|
hull->initializePolyhedralFeatures();
|
|
|
|
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.reset(new btCompoundShape());
|
|
m_kart_chassis->addChildShape(shiftCenterOfGravity, hull);
|
|
|
|
// Set mass and inertia
|
|
// --------------------
|
|
float mass = m_kart_properties->getMass();
|
|
|
|
// Position the chassis
|
|
// --------------------
|
|
btTransform trans;
|
|
trans.setIdentity();
|
|
createBody(mass, trans, m_kart_chassis.get(),
|
|
m_kart_properties->getRestitution(0.0f));
|
|
std::vector<float> ang_fact = m_kart_properties->getStabilityAngularFactor();
|
|
// The angular factor (with X and Z values <1) helps to keep the kart
|
|
// upright, especially in case of a collision.
|
|
m_body->setAngularFactor(Vec3(ang_fact[0], ang_fact[1], ang_fact[2]));
|
|
m_body->setFriction(m_kart_properties->getFrictionKartFriction());
|
|
m_user_pointer.set(this);
|
|
m_body->setDamping(m_kart_properties->getStabilityChassisLinearDamping(),
|
|
m_kart_properties->getStabilityChassisAngularDamping() );
|
|
|
|
// 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.reset(
|
|
new btKartRaycaster(Physics::get()->getPhysicsWorld(),
|
|
stk_config->m_smooth_normals &&
|
|
Track::getCurrentTrack()->smoothNormals()));
|
|
m_vehicle.reset(new btKart(m_body.get(), m_vehicle_raycaster.get(), this));
|
|
|
|
// never deactivate the vehicle
|
|
m_body->setActivationState(DISABLE_DEACTIVATION);
|
|
|
|
// Add wheels
|
|
// ----------
|
|
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_maxSuspensionTravel =
|
|
m_kart_properties->getSuspensionTravel();
|
|
tuning.m_maxSuspensionForce =
|
|
m_kart_properties->getSuspensionMaxForce();
|
|
|
|
const Vec3 &cs = m_kart_properties->getGravityCenterShift();
|
|
for(unsigned int i=0; i<4; i++)
|
|
{
|
|
bool is_front_wheel = i<2;
|
|
btWheelInfo& wheel = m_vehicle->addWheel(
|
|
wheel_pos[i]+cs,
|
|
wheel_direction, wheel_axle, suspension_rest,
|
|
m_kart_model->getWheelGraphicsRadius(i),
|
|
tuning, is_front_wheel);
|
|
wheel.m_suspensionStiffness = m_kart_properties->getSuspensionStiffness();
|
|
wheel.m_wheelsDampingRelaxation = m_kart_properties->getWheelsDampingRelaxation();
|
|
wheel.m_wheelsDampingCompression = m_kart_properties->getWheelsDampingCompression();
|
|
wheel.m_frictionSlip = m_kart_properties->getFrictionSlip();
|
|
wheel.m_rollInfluence = m_kart_properties->getStabilityRollInfluence();
|
|
}
|
|
// Body to be added in reset() which allows complete reset kart when
|
|
// restarting the race
|
|
|
|
} // createPhysics
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void Kart::flyUp()
|
|
{
|
|
m_flying = true;
|
|
Moveable::flyUp();
|
|
} // flyUp
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void Kart::flyDown()
|
|
{
|
|
if (isNearGround())
|
|
{
|
|
stopFlying();
|
|
m_flying = false;
|
|
}
|
|
else
|
|
{
|
|
Moveable::flyDown();
|
|
}
|
|
} // flyDown
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** 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 (RaceManager::get()->getNumLocalPlayers() > 1)
|
|
{
|
|
const int np = RaceManager::get()->getNumLocalPlayers();
|
|
const int nai = RaceManager::get()->getNumberOfKarts() - np;
|
|
|
|
// player karts twice as loud as AIs toghether
|
|
const float players_volume = (np * 2.0f) / (np*2.0f + np);
|
|
|
|
if (m_controller->isLocalPlayerController())
|
|
m_engine_sound->setVolume( players_volume / np );
|
|
else
|
|
m_engine_sound->setVolume( (1.0f - players_volume) / nai );
|
|
}
|
|
|
|
m_engine_sound->setSpeed(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 fabsf(m_body->getLinearVelocity ().y())<0.2f;
|
|
} // 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()
|
|
{
|
|
if (!m_body)
|
|
return;
|
|
float mass = m_kart_properties->getMass() + m_attachment->weightAdjust();
|
|
if (m_weight != mass)
|
|
{
|
|
m_weight = mass;
|
|
btVector3 inertia;
|
|
m_kart_chassis->calculateLocalInertia(mass, inertia);
|
|
m_body->setMassProps(mass, inertia);
|
|
}
|
|
} // updateWeight
|
|
|
|
// ------------------------------------------------------------------------
|
|
/** Returns the (maximum) speed for a given turn radius.
|
|
* \param radius The radius for which the speed needs to be computed. */
|
|
float Kart::getSpeedForTurnRadius(float radius) const
|
|
{
|
|
InterpolationArray turn_angle_at_speed = m_kart_properties->getTurnRadius();
|
|
// Convert the turn radius into turn angle
|
|
for(int i = 0; i < (int)turn_angle_at_speed.size(); i++)
|
|
turn_angle_at_speed.setY(i, sinf( 1.0f / turn_angle_at_speed.getY(i)));
|
|
|
|
float angle = sinf(1.0f / radius);
|
|
return turn_angle_at_speed.getReverse(angle);
|
|
} // getSpeedForTurnRadius
|
|
|
|
// ------------------------------------------------------------------------
|
|
/** Returns the maximum steering angle (depending on speed).
|
|
This is proportional to kart length because physics reverse this effect,
|
|
the results of this function should not be used to determine the
|
|
real raw steer angle. */
|
|
float Kart::getMaxSteerAngle(float speed) const
|
|
{
|
|
InterpolationArray turn_angle_at_speed = m_kart_properties->getTurnRadius();
|
|
// Convert the turn radius into turn angle
|
|
// We multiply by wheel base to keep turn radius identical
|
|
// across karts of different lengths sharing the same
|
|
// turn radius properties
|
|
for(int i = 0; i < (int)turn_angle_at_speed.size(); i++)
|
|
turn_angle_at_speed.setY(i, sinf( 1.0f / turn_angle_at_speed.getY(i))
|
|
* m_kart_properties->getWheelBase());
|
|
|
|
return turn_angle_at_speed.get(speed);
|
|
} // getMaxSteerAngle
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** 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.
|
|
* \param from_server In a network game, only the server can notify
|
|
* about a kart finishing a race. This parameter is to distinguish
|
|
* between a local detection (which is ignored on clients in a
|
|
* network game), and a server notification.
|
|
*/
|
|
void Kart::finishedRace(float time, bool from_server)
|
|
{
|
|
// 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;
|
|
|
|
const bool is_linear_race =
|
|
RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_NORMAL_RACE ||
|
|
RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_TIME_TRIAL ||
|
|
RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_FOLLOW_LEADER;
|
|
if (NetworkConfig::get()->isNetworking() && !from_server)
|
|
{
|
|
if (NetworkConfig::get()->isServer())
|
|
{
|
|
RaceEventManager::get()->kartFinishedRace(this, time);
|
|
} // isServer
|
|
|
|
// Ignore local detection of a kart finishing a race in a
|
|
// network game.
|
|
else if (NetworkConfig::get()->isClient())
|
|
{
|
|
if (is_linear_race && m_saved_controller == NULL &&
|
|
!RewindManager::get()->isRewinding())
|
|
{
|
|
m_network_finish_check_ticks =
|
|
World::getWorld()->getTicksSinceStart() +
|
|
stk_config->time2Ticks(1.0f);
|
|
EndController* ec = new EndController(this, m_controller);
|
|
Controller* old_controller = m_controller;
|
|
setController(ec);
|
|
// Seamless endcontroller replay
|
|
RewindManager::get()->addRewindInfoEventFunction(new
|
|
RewindInfoEventFunction(
|
|
World::getWorld()->getTicksSinceStart(),
|
|
/*undo_function*/[old_controller, this]()
|
|
{
|
|
if (m_network_finish_check_ticks == -1)
|
|
return;
|
|
|
|
m_controller = old_controller;
|
|
},
|
|
/*replay_function*/[ec, old_controller, this]()
|
|
{
|
|
if (m_network_finish_check_ticks == -1)
|
|
return;
|
|
|
|
m_saved_controller = old_controller;
|
|
ec->reset();
|
|
m_controller = ec;
|
|
}));
|
|
}
|
|
return;
|
|
}
|
|
} // !from_server
|
|
|
|
if (NetworkConfig::get()->isClient())
|
|
{
|
|
m_network_confirmed_finish_ticks =
|
|
World::getWorld()->getTicksSinceStart();
|
|
}
|
|
|
|
m_finished_race = true;
|
|
|
|
m_finish_time = time;
|
|
|
|
m_controller->finishedRace(time);
|
|
m_kart_model->finishedRace();
|
|
RaceManager::get()->kartFinishedRace(this, time);
|
|
|
|
// If this is spare tire kart, end now
|
|
if (dynamic_cast<SpareTireAI*>(m_controller) != NULL) return;
|
|
|
|
if (is_linear_race && m_controller->isPlayerController() && !m_eliminated)
|
|
{
|
|
RaceGUIBase* m = World::getWorld()->getRaceGUI();
|
|
if (m)
|
|
{
|
|
bool won_the_race = false, too_slow = false;
|
|
unsigned int win_position = 1;
|
|
|
|
if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_FOLLOW_LEADER)
|
|
win_position = 2;
|
|
|
|
if (getPosition() == (int)win_position &&
|
|
World::getWorld()->getNumKarts() > win_position)
|
|
won_the_race = true;
|
|
|
|
if (RaceManager::get()->hasTimeTarget() && m_finish_time > RaceManager::get()->getTimeTarget())
|
|
too_slow = true;
|
|
|
|
m->addMessage((too_slow ? _("You were too slow!") :
|
|
won_the_race ? _("You won the race!") :
|
|
_("You finished the race!")),
|
|
this, 2.0f, video::SColor(255, 255, 255, 255), true, true, true);
|
|
}
|
|
}
|
|
|
|
if (RaceManager::get()->isLinearRaceMode() || RaceManager::get()->isBattleMode() ||
|
|
RaceManager::get()->isSoccerMode() || RaceManager::get()->isEggHuntMode())
|
|
{
|
|
// Save for music handling in race result gui
|
|
setRaceResult();
|
|
if (!isGhostKart())
|
|
{
|
|
if (m_saved_controller == NULL)
|
|
{
|
|
setController(new EndController(this, m_controller));
|
|
}
|
|
else
|
|
m_saved_controller->finishedRace(time);
|
|
}
|
|
// Skip animation if this kart is eliminated
|
|
if (m_eliminated || isGhostKart()) return;
|
|
|
|
m_kart_model->setAnimation(m_race_result ?
|
|
KartModel::AF_WIN_START : KartModel::AF_LOSE_START);
|
|
}
|
|
} // finishedRace
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void Kart::setRaceResult()
|
|
{
|
|
if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_NORMAL_RACE ||
|
|
RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_TIME_TRIAL)
|
|
{
|
|
if (m_controller->isLocalPlayerController()) // if player is on this computer
|
|
{
|
|
PlayerProfile *player = PlayerManager::getCurrentPlayer();
|
|
const ChallengeStatus *challenge = player->getCurrentChallengeStatus();
|
|
// 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_race_result = true;
|
|
else
|
|
m_race_result = false;
|
|
}
|
|
else if (this->getPosition() <= 0.5f *
|
|
World::getWorld()->getCurrentNumKarts() ||
|
|
this->getPosition() == 1)
|
|
m_race_result = true;
|
|
else
|
|
m_race_result = false;
|
|
}
|
|
else
|
|
{
|
|
if (this->getPosition() <= 0.5f *
|
|
World::getWorld()->getCurrentNumKarts() ||
|
|
this->getPosition() == 1)
|
|
m_race_result = true;
|
|
else
|
|
m_race_result = false;
|
|
}
|
|
}
|
|
else if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_FOLLOW_LEADER ||
|
|
RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES)
|
|
{
|
|
// the kart wins if it isn't eliminated
|
|
m_race_result = !this->isEliminated();
|
|
}
|
|
else if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL)
|
|
{
|
|
FreeForAll* ffa = dynamic_cast<FreeForAll*>(World::getWorld());
|
|
m_race_result = ffa->getKartFFAResult(getWorldKartId());
|
|
}
|
|
else if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_CAPTURE_THE_FLAG)
|
|
{
|
|
CaptureTheFlag* ctf = dynamic_cast<CaptureTheFlag*>(World::getWorld());
|
|
m_race_result = ctf->getKartCTFResult(getWorldKartId());
|
|
}
|
|
else if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_SOCCER)
|
|
{
|
|
SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
|
|
m_race_result = sw->getKartSoccerResult(this->getWorldKartId());
|
|
}
|
|
else if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_EASTER_EGG)
|
|
{
|
|
// Easter egg mode only has one player, so always win
|
|
m_race_result = true;
|
|
}
|
|
else
|
|
Log::warn("Kart", "Unknown game mode given.");
|
|
|
|
} // setRaceResult
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Called when an item is collected. It will either adjust the collected
|
|
* energy, or update the attachment or powerup for this kart.
|
|
* \param item_state The item that was hit.
|
|
*/
|
|
void Kart::collectedItem(ItemState *item_state)
|
|
{
|
|
float old_energy = m_collected_energy;
|
|
const Item::ItemType type = item_state->getType();
|
|
|
|
switch (type)
|
|
{
|
|
case Item::ITEM_BANANA:
|
|
m_attachment->hitBanana(item_state);
|
|
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_state);
|
|
break;
|
|
}
|
|
case Item::ITEM_BUBBLEGUM:
|
|
m_has_caught_nolok_bubblegum =
|
|
(item_state->getPreviousOwner()&&
|
|
item_state->getPreviousOwner()->getIdent() == "nolok");
|
|
|
|
// slow down
|
|
m_bubblegum_ticks = (int16_t)stk_config->time2Ticks(
|
|
m_kart_properties->getBubblegumDuration());
|
|
m_bubblegum_torque_sign =
|
|
((World::getWorld()->getTicksSinceStart() / 10) % 2 == 0) ?
|
|
true : false;
|
|
m_max_speed->setSlowdown(MaxSpeed::MS_DECREASE_BUBBLE,
|
|
m_kart_properties->getBubblegumSpeedFraction() ,
|
|
stk_config->time2Ticks(m_kart_properties->getBubblegumFadeInTime()),
|
|
m_bubblegum_ticks);
|
|
if (!RewindManager::get()->isRewinding())
|
|
getNextEmitter()->play(getXYZ(), m_goo_sound);
|
|
|
|
// 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_state, old_energy);
|
|
|
|
} // collectedItem
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Called the first time a kart accelerates after 'ready'. It searches
|
|
* through the startup times to find the appropriate slot, and returns the
|
|
* speed-boost from the corresponding entry.
|
|
* If the kart started too slow (i.e. slower than the longest time in the
|
|
* startup times list), it returns 0.
|
|
*/
|
|
float Kart::getStartupBoostFromStartTicks(int ticks) const
|
|
{
|
|
int ticks_since_ready = ticks - stk_config->time2Ticks(1.0f);
|
|
if (ticks_since_ready < 0)
|
|
return 0.0f;
|
|
float t = stk_config->ticks2Time(ticks_since_ready);
|
|
std::vector<float> startup_times = m_kart_properties->getStartupTime();
|
|
for (unsigned int i = 0; i < startup_times.size(); i++)
|
|
{
|
|
if (t <= startup_times[i])
|
|
return m_kart_properties->getStartupBoost()[i];
|
|
}
|
|
return 0.0f;
|
|
} // getStartupBoostFromStartTicks
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** 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(!std::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->getEngineMaxSpeed() * gear_ratio[i])
|
|
{
|
|
assert(!std::isnan(m_kart_properties->getEnginePower()));
|
|
assert(!std::isnan(m_kart_properties->getGearPowerIncrease()[i]));
|
|
return m_kart_properties->getEnginePower()
|
|
* m_kart_properties->getGearPowerIncrease()[i]
|
|
+ add_force;
|
|
}
|
|
}
|
|
assert(!std::isnan(m_kart_properties->getEnginePower()));
|
|
return m_kart_properties->getEnginePower() + add_force * 2;
|
|
|
|
} // 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 stop flying.
|
|
*/
|
|
bool Kart::isNearGround() const
|
|
{
|
|
if((m_terrain_info->getHitPoint() - getXYZ()).length() ==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()->setTicksLeft(stk_config->time2Ticks(t));
|
|
}
|
|
} // setShieldTime
|
|
|
|
// ------------------------------------------------------------------------
|
|
/**
|
|
* Returns true if the kart is protected by a shield.
|
|
*/
|
|
bool Kart::isShielded() const
|
|
{
|
|
if(getAttachment() != NULL)
|
|
{
|
|
Attachment::AttachmentType type = getAttachment()->getType();
|
|
return type == Attachment::ATTACH_BUBBLEGUM_SHIELD ||
|
|
type == Attachment::ATTACH_NOLOK_BUBBLEGUM_SHIELD;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
} // isShielded
|
|
|
|
// ------------------------------------------------------------------------
|
|
/**
|
|
*Returns the remaining time the kart is protected by a shield.
|
|
*/
|
|
float Kart::getShieldTime() const
|
|
{
|
|
if (isShielded())
|
|
return stk_config->ticks2Time(getAttachment()->getTicksLeft());
|
|
else
|
|
return 0.0f;
|
|
} // getShieldTime
|
|
|
|
// ------------------------------------------------------------------------
|
|
/**
|
|
* Decreases the kart's shield time.
|
|
* \param t The time subtracted from the shield timer. If t == 0.0f, the
|
|
default amout of time is subtracted.
|
|
*/
|
|
void Kart::decreaseShieldTime()
|
|
{
|
|
if (isShielded())
|
|
{
|
|
getAttachment()->setTicksLeft(0);
|
|
}
|
|
} // decreaseShieldTime
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Shows the star effect for a certain time.
|
|
* \param t Time to show the star effect for.
|
|
*/
|
|
void Kart::showStarEffect(float t)
|
|
{
|
|
if (m_stars_effect)
|
|
m_stars_effect->showFor(t);
|
|
} // showStarEffect
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void Kart::eliminate()
|
|
{
|
|
if (!getKartAnimation())
|
|
{
|
|
Physics::get()->removeKart(this);
|
|
}
|
|
if (m_stars_effect)
|
|
{
|
|
m_stars_effect->reset();
|
|
m_stars_effect->update(1);
|
|
}
|
|
|
|
if (m_attachment)
|
|
m_attachment->clear();
|
|
|
|
if (m_slipstream)
|
|
m_slipstream->reset();
|
|
|
|
m_kart_gfx->setCreationRateAbsolute(KartGFX::KGFX_TERRAIN, 0);
|
|
m_kart_gfx->setGFXInvisible();
|
|
|
|
if (m_engine_sound)
|
|
m_engine_sound->stop();
|
|
|
|
m_eliminated = true;
|
|
|
|
#ifndef SERVER_ONLY
|
|
if (m_shadow)
|
|
m_shadow->update(false);
|
|
#endif
|
|
if (m_node)
|
|
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(int ticks)
|
|
{
|
|
if (m_network_finish_check_ticks > 0 &&
|
|
World::getWorld()->getTicksSinceStart() >
|
|
m_network_finish_check_ticks &&
|
|
!m_finished_race && m_saved_controller != NULL)
|
|
{
|
|
Log::warn("Kart", "Missing finish race from server.");
|
|
m_network_finish_check_ticks = -1;
|
|
delete m_controller;
|
|
m_controller = m_saved_controller;
|
|
m_saved_controller = NULL;
|
|
}
|
|
|
|
m_powerup->update(ticks);
|
|
|
|
// Reset any instant speed increase in the bullet kart
|
|
m_vehicle->resetMaxSpeed();
|
|
|
|
if (m_bubblegum_ticks > 0)
|
|
m_bubblegum_ticks -= ticks;
|
|
|
|
// This is to avoid a rescue immediately after an explosion
|
|
const bool has_animation_before = m_kart_animation != NULL;
|
|
// A kart animation can change the xyz position. This needs to be done
|
|
// before updating the graphical position (which is done in
|
|
// Moveable::update() ), otherwise 'stuttering' can happen (caused by
|
|
// graphical and physical position not being the same).
|
|
if (has_animation_before)
|
|
{
|
|
m_kart_animation->update(ticks);
|
|
}
|
|
else if (NetworkConfig::get()->roundValuesNow())
|
|
CompressNetworkBody::compress(m_body.get(), m_motion_state.get());
|
|
|
|
float dt = stk_config->ticks2Time(ticks);
|
|
if (!RewindManager::get()->isRewinding())
|
|
{
|
|
m_time_previous_counter += dt;
|
|
while (m_time_previous_counter > stk_config->ticks2Time(1))
|
|
{
|
|
m_previous_xyz[0] = getXYZ();
|
|
m_previous_xyz_times[0] = World::getWorld()->getTime();
|
|
for (int i=m_xyz_history_size-1;i>0;i--)
|
|
{
|
|
m_previous_xyz[i] = m_previous_xyz[i-1];
|
|
m_previous_xyz_times[i] = m_previous_xyz_times[i-1];
|
|
}
|
|
m_time_previous_counter -= stk_config->ticks2Time(1);
|
|
}
|
|
}
|
|
|
|
// Update the position and other data taken from the physics (or
|
|
// an animation which calls setXYZ(), which also updates the kart
|
|
// physical position).
|
|
Moveable::update(ticks);
|
|
|
|
Vec3 front(0, 0, getKartLength()*0.5f);
|
|
m_xyz_front = getTrans()(front);
|
|
|
|
// Hover the kart above reset position before entering the game
|
|
// Add invulnerability depends on kart
|
|
if (m_live_join_util != 0 &&
|
|
((m_live_join_util > World::getWorld()->getTicksSinceStart() &&
|
|
World::getWorld()->isActiveRacePhase()) ||
|
|
World::getWorld()->isLiveJoinWorld()))
|
|
{
|
|
btRigidBody *body = getBody();
|
|
body->clearForces();
|
|
body->setLinearVelocity(Vec3(0.0f));
|
|
body->setAngularVelocity(Vec3(0.0f));
|
|
btTransform hovering = m_starting_transform;
|
|
hovering.setOrigin(hovering.getOrigin() +
|
|
m_starting_transform.getBasis().getColumn(1) * 3.0f);
|
|
body->proceedToTransform(hovering);
|
|
setTrans(hovering);
|
|
float time = getKartProperties()->getExplosionInvulnerabilityTime();
|
|
m_invulnerable_ticks = stk_config->time2Ticks(time);
|
|
}
|
|
|
|
// Update the locally maintained speed of the kart (m_speed), which
|
|
// is used furthermore for engine power, camera distance etc
|
|
updateSpeed();
|
|
// Make the restitution depend on speed: this avoids collision issues,
|
|
// otherwise a collision with high speed can see a kart being push
|
|
// high up in the air (and out of control). So for higher speed we
|
|
// reduce the restitution, meaning the karts will get less of a push
|
|
// based on the collision speed.
|
|
m_body->setRestitution(m_kart_properties->getRestitution(fabsf(m_speed)));
|
|
|
|
m_controller->update(ticks);
|
|
|
|
#ifndef SERVER_ONLY
|
|
#undef DEBUG_CAMERA_SHAKE
|
|
#ifdef DEBUG_CAMERA_SHAKE
|
|
Log::verbose("camera", "%s t %f %d xyz %f %f %f v %f %f %f d3 %f d2 %f",
|
|
getIdent().c_str(),
|
|
World::getWorld()->getTime(), ticks,
|
|
getXYZ().getX(), getXYZ().getY(), getXYZ().getZ(),
|
|
getVelocity().getX(), getVelocity().getY(), getVelocity().getZ(),
|
|
(Camera::getCamera(0)->getXYZ()-getXYZ()).length(),
|
|
(Camera::getCamera(0)->getXYZ()-getXYZ()).length_2d()
|
|
);
|
|
#endif
|
|
#endif
|
|
|
|
#undef DEBUG_TO_COMPARE_KART_PHYSICS
|
|
#ifdef DEBUG_TO_COMPARE_KART_PHYSICS
|
|
// This information is useful when comparing kart physics, e.g. to
|
|
// see top speed, acceleration (i.e. time to top speed) etc.
|
|
Log::verbose("physics", " %s t %f %d xyz(9-11) %f %f %f v(13-15) %f %f %f steerf(17) %f maxangle(19) %f speed(21) %f steering(23-24) %f %f clock %lf",
|
|
getIdent().c_str(),
|
|
World::getWorld()->getTime(), ticks,
|
|
getXYZ().getX(), getXYZ().getY(), getXYZ().getZ(),
|
|
getVelocity().getX(), getVelocity().getY(), getVelocity().getZ(), //13,14,15
|
|
m_skidding->getSteeringFraction(), //19
|
|
getMaxSteerAngle(), //20
|
|
m_speed, //21
|
|
m_vehicle->getWheelInfo(0).m_steering, //23
|
|
m_vehicle->getWheelInfo(1).m_steering, //24
|
|
StkTime::getRealTime()
|
|
);
|
|
#endif
|
|
|
|
// if its view is blocked by plunger, decrease remaining time
|
|
if(m_view_blocked_by_plunger > 0)
|
|
{
|
|
m_view_blocked_by_plunger -= ticks;
|
|
//unblock the view if kart just became shielded
|
|
if(isShielded())
|
|
m_view_blocked_by_plunger = 0;
|
|
}
|
|
|
|
// Decrease the remaining invulnerability time
|
|
if(m_invulnerable_ticks>0)
|
|
m_invulnerable_ticks -= ticks;
|
|
|
|
if (!RewindManager::get()->isRewinding())
|
|
m_slipstream->update(ticks);
|
|
m_slipstream->updateSpeedIncrease();
|
|
|
|
// 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);
|
|
|
|
// 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->getStabilityChassisAngularDamping());
|
|
}
|
|
else
|
|
{
|
|
m_body->setDamping(m_kart_properties->getStabilityChassisLinearDamping(),
|
|
m_kart_properties->getStabilityChassisAngularDamping());
|
|
}
|
|
|
|
// Used to prevent creating a rescue animation after an explosion animation got deleted
|
|
|
|
m_attachment->update(ticks);
|
|
|
|
// 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;
|
|
}
|
|
|
|
// After the physics step was done, the position of the wheels (as stored
|
|
// in wheelInfo) is actually outdated, since the chassis was moved
|
|
// according to the force acting from the wheels. So the center of the
|
|
// chassis is not at the center of the wheels anymore, it is somewhat
|
|
// moved forward (depending on speed and fps). In very extreme cases
|
|
// (see bug 2246) the center of the chassis can actually be ahead of the
|
|
// front wheels. So if we do a raycast to detect the terrain from the
|
|
// current chassis, that raycast might be ahead of the wheels - which
|
|
// results in incorrect rescues (the wheels are still on the ground,
|
|
// but the raycast happens ahead of the front wheels and are over
|
|
// a rescue texture).
|
|
// To avoid this problem, we do the raycast for terrain detection from
|
|
// the center of the 4 wheel positions (in world coordinates).
|
|
|
|
if (!has_animation_before)
|
|
{
|
|
Vec3 from(0.0f, 0.0f, 0.0f);
|
|
for (unsigned int i = 0; i < 4; i++)
|
|
from += m_vehicle->getWheelInfo(i).m_raycastInfo.m_hardPointWS;
|
|
|
|
// 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).
|
|
from = from/4 + (getTrans().getBasis() * Vec3(0.0f, 0.3f, 0.0f));
|
|
|
|
m_terrain_info->update(getTrans().getBasis(), from);
|
|
}
|
|
else
|
|
{
|
|
// Use kart transform directly as wheel info is not updated when
|
|
// there is an animation
|
|
m_terrain_info->update(getTrans().getBasis(),
|
|
getXYZ() + getTrans().getBasis().getColumn(1) * 0.1f);
|
|
}
|
|
|
|
if (m_body->getBroadphaseHandle())
|
|
m_body->getBroadphaseHandle()->m_collisionFilterGroup = old_group;
|
|
|
|
// Check if a kart is (nearly) upside down and not moving much -->
|
|
// automatic rescue
|
|
// But only do this if auto-rescue is enabled (i.e. it will be disabled in
|
|
// battle mode), and the material the kart is driving on does not have
|
|
// gravity (which atm affects the roll angle).
|
|
|
|
// To be used later
|
|
float dist_to_sector = 0.0f;
|
|
LinearWorld* lw = dynamic_cast<LinearWorld*>(World::getWorld());
|
|
if (lw && DriveGraph::get())
|
|
{
|
|
const int sector =
|
|
lw->getTrackSector(getWorldKartId())->getCurrentGraphNode();
|
|
dist_to_sector = getXYZ().distance
|
|
(DriveGraph::get()->getNode(sector)->getCenter());
|
|
|
|
const Vec3& quad_normal = DriveGraph::get()->getNode(sector)
|
|
->getNormal();
|
|
const btQuaternion& q = getTrans().getRotation();
|
|
const float roll = quad_normal.angle
|
|
((Vec3(0, 1, 0).rotate(q.getAxis(), q.getAngle())));
|
|
|
|
if (Track::getCurrentTrack()->isAutoRescueEnabled() &&
|
|
(!m_terrain_info->getMaterial() ||
|
|
!m_terrain_info->getMaterial()->hasGravity()) &&
|
|
!has_animation_before && fabs(roll) > 60 * DEGREE_TO_RAD &&
|
|
fabs(getSpeed()) < 3.0f)
|
|
{
|
|
RescueAnimation::create(this, /*is_auto_rescue*/true);
|
|
m_last_factor_engine_sound = 0.0f;
|
|
}
|
|
}
|
|
|
|
// Update physics from newly updated material
|
|
PROFILER_PUSH_CPU_MARKER("Kart::updatePhysics", 0x60, 0x34, 0x7F);
|
|
const Material* material = m_terrain_info->getMaterial();
|
|
|
|
// First update the gravity of the kart, as updateSliding in updatePhysics
|
|
// need the newly set gravity to test for sliding.
|
|
if (!m_flying)
|
|
{
|
|
float g = Track::getCurrentTrack()->getGravity();
|
|
Vec3 gravity(0.0f, -g, 0.0f);
|
|
btRigidBody *body = getVehicle()->getRigidBody();
|
|
|
|
// If the material should overwrite the gravity,
|
|
if (material && material->hasGravity())
|
|
{
|
|
Vec3 normal = m_terrain_info->getNormal();
|
|
gravity = normal * -g;
|
|
}
|
|
|
|
body->setGravity(gravity);
|
|
}
|
|
updatePhysics(ticks);
|
|
PROFILER_POP_CPU_MARKER();
|
|
|
|
if(!m_controls.getFire()) m_fire_clicked = 0;
|
|
|
|
if(m_controls.getFire() && !m_fire_clicked && !m_kart_animation)
|
|
{
|
|
if (m_powerup->getType() != PowerupManager::POWERUP_NOTHING)
|
|
{
|
|
setLastUsedPowerup(m_powerup->getType());
|
|
}
|
|
// use() needs to be called even if there currently is no collecteable
|
|
// since use() can test if something needs to be switched on/off.
|
|
if (!World::getWorld()->isStartPhase())
|
|
m_powerup->use();
|
|
else
|
|
{
|
|
if(!getKartAnimation())
|
|
beep();
|
|
}
|
|
World::getWorld()->onFirePressed(getController());
|
|
m_fire_clicked = 1;
|
|
}
|
|
|
|
#undef XX
|
|
#ifdef XX
|
|
Log::verbose("physicsafter", "%s t %f %d xyz(9-11) %f %f %f %f %f %f "
|
|
"v(16-18) %f %f %f steerf(20) %f maxangle(22) %f speed(24) %f "
|
|
"steering(26-27) %f %f clock(29) %lf skidstate(31) %d factor(33) %f "
|
|
"maxspeed(35) %f engf(37) %f braketick(39) %d brakes(41) %d heading(43) %f "
|
|
"bubticks(45) %d",
|
|
getIdent().c_str(),
|
|
World::getWorld()->getTime(), World::getWorld()->getTicksSinceStart(),
|
|
getXYZ().getX(), getXYZ().getY(), getXYZ().getZ(),
|
|
m_body->getWorldTransform().getOrigin().getX(),
|
|
m_body->getWorldTransform().getOrigin().getY(),
|
|
m_body->getWorldTransform().getOrigin().getZ(),
|
|
getVelocity().getX(), getVelocity().getY(), getVelocity().getZ(), //16-18
|
|
m_skidding->getSteeringFraction(), //20
|
|
getMaxSteerAngle(), //22
|
|
m_speed, //24
|
|
m_vehicle->getWheelInfo(0).m_steering, //26
|
|
m_vehicle->getWheelInfo(1).m_steering, //27
|
|
StkTime::getRealTime(), //29
|
|
m_skidding->getSkidState(), //31
|
|
m_skidding->getSkidFactor(), //33
|
|
m_max_speed->getCurrentMaxSpeed(),
|
|
m_max_speed->getCurrentAdditionalEngineForce(), // 37
|
|
m_brake_ticks, //39
|
|
m_controls.getButtonsCompressed(), //41
|
|
getHeading(), //43
|
|
m_bubblegum_ticks // 45
|
|
);
|
|
#endif
|
|
|
|
PROFILER_PUSH_CPU_MARKER("Kart::Update (material)", 0x60, 0x34, 0x7F);
|
|
if (!material) // kart falling off the track
|
|
{
|
|
// let kart fall a bit before rescuing
|
|
const Vec3 *min, *max;
|
|
Track::getCurrentTrack()->getAABB(&min, &max);
|
|
|
|
if((min->getY() - getXYZ().getY() > 17 || dist_to_sector > 25) && !m_flying &&
|
|
!has_animation_before)
|
|
{
|
|
RescueAnimation::create(this);
|
|
m_last_factor_engine_sound = 0.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!has_animation_before && material->isDriveReset() && isOnGround())
|
|
{
|
|
RescueAnimation::create(this);
|
|
m_last_factor_engine_sound = 0.0f;
|
|
}
|
|
else if(material->isZipper() && isOnGround())
|
|
{
|
|
handleZipper(material);
|
|
showZipperFire();
|
|
}
|
|
else
|
|
{
|
|
m_max_speed->setSlowdown(MaxSpeed::MS_DECREASE_TERRAIN,
|
|
material->getMaxSpeedFraction(),
|
|
material->getSlowDownTicks() );
|
|
#ifdef DEBUG
|
|
if(UserConfigParams::m_material_debug)
|
|
{
|
|
Log::info("Kart","World %d %s\tfraction %f\ttime %d.",
|
|
World::getWorld()->getTicksSinceStart(),
|
|
material->getTexFname().c_str(),
|
|
material->getMaxSpeedFraction(),
|
|
material->getSlowDownTicks() );
|
|
}
|
|
#endif
|
|
}
|
|
} // if there is material
|
|
PROFILER_POP_CPU_MARKER();
|
|
|
|
Track::getCurrentTrack()->getItemManager()->checkItemHit(this);
|
|
|
|
const bool emergency = has_animation_before;
|
|
|
|
if (emergency)
|
|
{
|
|
m_view_blocked_by_plunger = 0;
|
|
if (m_flying)
|
|
{
|
|
stopFlying();
|
|
m_flying = false;
|
|
}
|
|
}
|
|
|
|
if (RewindManager::get()->isRewinding())
|
|
return;
|
|
// 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() && !has_animation_before)
|
|
{
|
|
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 &&
|
|
m_kart_model->getAnimation() == KartModel::AF_DEFAULT)
|
|
{
|
|
float v = getVelocity().getY();
|
|
float force = Track::getCurrentTrack()->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 > m_kart_properties->getJumpAnimationTime() ||
|
|
last_m->isJumpTexture())
|
|
{
|
|
m_kart_model->setAnimation(KartModel::AF_JUMP_START);
|
|
}
|
|
|
|
m_is_jumping = true;
|
|
}
|
|
}
|
|
else if (m_is_jumping)
|
|
{
|
|
// Kart touched ground again
|
|
m_is_jumping = false;
|
|
m_kart_model->setAnimation(KartModel::AF_DEFAULT);
|
|
|
|
if (!GUIEngine::isNoGraphics() && !has_animation_before)
|
|
{
|
|
HitEffect *effect = new Explosion(getXYZ(), "jump",
|
|
"jump_explosion.xml");
|
|
ProjectileManager::get()->addHitEffect(effect);
|
|
}
|
|
}
|
|
|
|
} // update
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Updates the local speed based on the current physical velocity. The value
|
|
* is smoothed exponentially to avoid camera stuttering (camera distance
|
|
* is dependent on speed)
|
|
*/
|
|
void Kart::updateSpeed()
|
|
{
|
|
// Compute the speed of the kart. Smooth it with previous speed to make
|
|
// the camera smoother (because of capping the speed in m_max_speed
|
|
// the speed value jitters when approaching maximum speed. This results
|
|
// in the distance between kart and camera to jitter as well (typically
|
|
// only in the order of centimetres though). Smoothing the speed value
|
|
// gets rid of this jitter, and also r
|
|
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]);
|
|
|
|
// In theory <0 should be sufficient, but floating point errors can cause
|
|
// flipping from +eps to -eps and back, resulting in animation flickering
|
|
// if the kart has backpedal animations.
|
|
if (forwardW.dot(getVehicle()->getRigidBody()->getLinearVelocity())
|
|
< btScalar(-0.01f))
|
|
{
|
|
m_speed = -m_speed;
|
|
}
|
|
|
|
// At low velocity, forces on kart push it back and forth so we ignore this
|
|
// - quick'n'dirty workaround for bug 1776883
|
|
if (fabsf(m_speed) < 0.2f ||
|
|
dynamic_cast<RescueAnimation*> ( getKartAnimation() ) ||
|
|
dynamic_cast<ExplosionAnimation*>( getKartAnimation() ) )
|
|
{
|
|
m_speed = 0;
|
|
}
|
|
} // updateSpeed
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** 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.
|
|
* Returns true if the squash is successful, false otherwise.
|
|
* \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.
|
|
*/
|
|
bool Kart::setSquash(float time, float slowdown)
|
|
{
|
|
if (isInvulnerable() || getKartAnimation()) return false;
|
|
|
|
if (isShielded())
|
|
{
|
|
decreaseShieldTime();
|
|
return false;
|
|
}
|
|
|
|
if(m_attachment->getType()==Attachment::ATTACH_BOMB && time>0)
|
|
{
|
|
ExplosionAnimation::create(this);
|
|
return true;
|
|
}
|
|
|
|
m_max_speed->setSlowdown(MaxSpeed::MS_DECREASE_SQUASH, slowdown,
|
|
stk_config->time2Ticks(0.1f),
|
|
stk_config->time2Ticks(time));
|
|
return true;
|
|
} // setSquash
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void Kart::setSquashGraphics()
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
if (isGhostKart() || GUIEngine::isNoGraphics()) return;
|
|
|
|
m_node->setScale(core::vector3df(1.0f, 0.5f, 1.0f));
|
|
if (m_vehicle->getNumWheels() > 0)
|
|
{
|
|
if (!m_wheel_box)
|
|
{
|
|
m_wheel_box = irr_driver->getSceneManager()
|
|
->addDummyTransformationSceneNode(m_node);
|
|
}
|
|
scene::ISceneNode **wheels = m_kart_model->getWheelNodes();
|
|
for (int i = 0; i < 4 && i < m_vehicle->getNumWheels(); i++)
|
|
{
|
|
if (wheels[i])
|
|
wheels[i]->setParent(m_wheel_box);
|
|
}
|
|
m_wheel_box->getRelativeTransformationMatrix()
|
|
.setScale(core::vector3df(1.0f, 2.0f, 1.0f));
|
|
}
|
|
#endif
|
|
} // setSquashGraphics
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void Kart::unsetSquash()
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
if (isGhostKart() || GUIEngine::isNoGraphics()) return;
|
|
|
|
m_node->setScale(core::vector3df(1.0f, 1.0f, 1.0f));
|
|
if (m_vehicle && m_vehicle->getNumWheels() > 0)
|
|
{
|
|
scene::ISceneNode** wheels = m_kart_model->getWheelNodes();
|
|
scene::ISceneNode* node = m_kart_model->getAnimatedNode() ?
|
|
m_kart_model->getAnimatedNode() : m_node;
|
|
|
|
for (int i = 0; i < 4 && i < m_vehicle->getNumWheels(); i++)
|
|
{
|
|
if (wheels[i])
|
|
{
|
|
wheels[i]->setParent(node);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
} // unsetSquash
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Returns if the kart is currently being squashed
|
|
*/
|
|
bool Kart::isSquashed() const
|
|
{
|
|
return
|
|
m_max_speed->isSpeedDecreaseActive(MaxSpeed::MS_DECREASE_SQUASH) == 1;
|
|
} // setSquash
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Plays any terrain specific sound effect.
|
|
*/
|
|
void Kart::handleMaterialSFX()
|
|
{
|
|
// 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.
|
|
|
|
// FIXME: if there are already two sfx playing, don't add another
|
|
// one. This should reduce the performance impact when driving
|
|
// on the bridge in Cocoa.
|
|
const Material* material =
|
|
isOnGround() ? m_terrain_info->getMaterial() : NULL;
|
|
|
|
// We can not use getLastMaterial() since, since the last material might
|
|
// be updated several times during the physics updates, not indicating
|
|
// that we have reached a new material with regards to the sound effect.
|
|
// So we separately save the material last used for a sound effect and
|
|
// then use this for comparison.
|
|
if(m_last_sound_material!=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)
|
|
{
|
|
m_previous_terrain_sound->deleteSFX();
|
|
}
|
|
|
|
// Disable looping for the current terrain sound, and
|
|
// make it the previous terrain sound.
|
|
if (m_terrain_sound) m_terrain_sound->setLoop(false);
|
|
m_previous_terrain_sound = m_terrain_sound;
|
|
|
|
const std::string &sound_name = material ? material->getSFXName() : "";
|
|
|
|
// 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 (!sound_name.empty() && (RaceManager::get()->getNumPlayers()==1 ||
|
|
m_controller->isLocalPlayerController() ) )
|
|
{
|
|
m_terrain_sound = SFXManager::get()->createSoundSource(sound_name);
|
|
m_terrain_sound->play();
|
|
m_terrain_sound->setLoop(true);
|
|
}
|
|
else
|
|
{
|
|
m_terrain_sound = NULL;
|
|
}
|
|
}
|
|
|
|
// Check if a previous terrain sound (now not looped anymore)
|
|
// is finished and can be deleted.
|
|
if(m_previous_terrain_sound &&
|
|
m_previous_terrain_sound->getStatus()==SFXBase::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.
|
|
m_previous_terrain_sound->deleteSFX();
|
|
m_previous_terrain_sound = NULL;
|
|
}
|
|
|
|
bool m_schedule_pause = m_flying ||
|
|
dynamic_cast<RescueAnimation*>(getKartAnimation()) ||
|
|
dynamic_cast<ExplosionAnimation*>(getKartAnimation());
|
|
|
|
// 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()==SFXBase::SFX_PLAYING ||
|
|
m_terrain_sound->getStatus()==SFXBase::SFX_PAUSED) )
|
|
{
|
|
m_terrain_sound->setPosition(getXYZ());
|
|
if(material)
|
|
material->setSFXSpeed(m_terrain_sound, m_speed, m_schedule_pause);
|
|
}
|
|
|
|
m_last_sound_material = material;
|
|
} // 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. This function is called once
|
|
* per rendered frame from updateGraphics().
|
|
* \param dt Time step size.
|
|
*/
|
|
void Kart::handleMaterialGFX(float dt)
|
|
{
|
|
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_particles_effects > 1)
|
|
{
|
|
|
|
// 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->isLocalPlayerController() && !hasFinishedRace())
|
|
{
|
|
bool falling = material && material->hasFallingEffect() && !m_flying;
|
|
if (falling)
|
|
{
|
|
m_falling_time -= dt;
|
|
if (m_falling_time < 0)
|
|
m_falling_time = 0;
|
|
}
|
|
else
|
|
m_falling_time = 0.35f;
|
|
|
|
for(unsigned int i=0; i<Camera::getNumCameras(); i++)
|
|
{
|
|
Camera *camera = Camera::getCamera(i);
|
|
if(camera->getKart()!=this) continue;
|
|
|
|
if (falling && m_falling_time <= 0)
|
|
{
|
|
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_particles_effects < 2)
|
|
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() == SFXBase::SFX_STOPPED))
|
|
{
|
|
if (m_previous_terrain_sound) m_previous_terrain_sound->deleteSFX();
|
|
m_previous_terrain_sound = m_terrain_sound;
|
|
if(m_previous_terrain_sound)
|
|
m_previous_terrain_sound->setLoop(false);
|
|
|
|
m_terrain_sound = SFXManager::get()->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->getZipperDuration();
|
|
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->getZipperDuration();
|
|
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.getBrake() || m_speed<0) return;
|
|
|
|
m_max_speed->instantSpeedIncrease(MaxSpeed::MS_INCREASE_ZIPPER,
|
|
max_speed_increase, speed_gain,
|
|
engine_force,
|
|
stk_config->time2Ticks(duration),
|
|
stk_config->time2Ticks(fade_out_time));
|
|
// Play custom character sound (weee!)
|
|
int zipper_ticks = World::getWorld()->getTicksSinceStart();
|
|
if (zipper_ticks > m_ticks_last_zipper)
|
|
{
|
|
m_ticks_last_zipper = zipper_ticks;
|
|
playCustomSFX(SFXManager::CUSTOM_ZIPPER);
|
|
m_controller->handleZipper(play_sound);
|
|
}
|
|
|
|
} // handleZipper
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Updates the current nitro status.
|
|
* \param ticks Number of physics time steps - should be 1.
|
|
*/
|
|
void Kart::updateNitro(int ticks)
|
|
{
|
|
if (m_collected_energy == 0)
|
|
m_min_nitro_ticks = 0;
|
|
|
|
if (m_controls.getNitro() && m_min_nitro_ticks <= 0 && m_collected_energy > 0)
|
|
{
|
|
m_min_nitro_ticks = m_kart_properties->getNitroMinConsumptionTicks();
|
|
float min_consumption = m_min_nitro_ticks * m_consumption_per_tick;
|
|
m_energy_to_min_ratio = std::min<float>(1, m_collected_energy/min_consumption);
|
|
}
|
|
if (m_min_nitro_ticks > 0)
|
|
{
|
|
m_min_nitro_ticks -= ticks;
|
|
|
|
// when pressing the key, don't allow the min time to go under zero.
|
|
// If it went under zero, it would be reset
|
|
// As the time deduction happens before, it can be an arbitrarily
|
|
// small number > 0. Smaller means more responsive controls.
|
|
if (m_controls.getNitro() && m_min_nitro_ticks <= 0)
|
|
m_min_nitro_ticks = 1;
|
|
}
|
|
|
|
bool rewinding = RewindManager::get()->isRewinding();
|
|
bool increase_speed = (m_min_nitro_ticks > 0 && isOnGround());
|
|
if (!increase_speed && m_min_nitro_ticks <= 0)
|
|
{
|
|
if (m_nitro_sound->getStatus() == SFXBase::SFX_PLAYING && !rewinding)
|
|
m_nitro_sound->stop();
|
|
return;
|
|
}
|
|
|
|
|
|
m_collected_energy -= m_consumption_per_tick*ticks;
|
|
if (m_collected_energy < 0)
|
|
{
|
|
if(m_nitro_sound->getStatus() == SFXBase::SFX_PLAYING && !rewinding)
|
|
m_nitro_sound->stop();
|
|
m_collected_energy = 0;
|
|
return;
|
|
}
|
|
|
|
if (increase_speed)
|
|
{
|
|
if(m_nitro_sound->getStatus() != SFXBase::SFX_PLAYING && !rewinding)
|
|
m_nitro_sound->play();
|
|
|
|
m_max_speed->increaseMaxSpeed(MaxSpeed::MS_INCREASE_NITRO,
|
|
m_kart_properties->getNitroMaxSpeedIncrease(),
|
|
m_kart_properties->getNitroEngineForce(),
|
|
stk_config->time2Ticks(m_kart_properties->getNitroDuration()*m_energy_to_min_ratio),
|
|
stk_config->time2Ticks(m_kart_properties->getNitroFadeOutTime()));
|
|
}
|
|
else
|
|
{
|
|
if(m_nitro_sound->getStatus() == SFXBase::SFX_PLAYING && !rewinding)
|
|
m_nitro_sound->stop();
|
|
}
|
|
} // 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);
|
|
playCrashSFX(NULL, k);
|
|
} // crashed(Kart, update_attachments
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Kart hits the track with a given material.
|
|
* \param m Material hit, can be NULL if no specific material exists.
|
|
* \param normal The normal of the hit (used to push a kart back, which avoids
|
|
* that karts sometimes can get stuck).
|
|
*/
|
|
void Kart::crashed(const Material *m, const Vec3 &normal)
|
|
{
|
|
if (m && !(m->getCollisionReaction() == Material::RESCUE))
|
|
playCrashSFX(m, NULL);
|
|
#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.",
|
|
getIdent().c_str(), counter++,
|
|
m ? m->getTexFname().c_str() : "None");
|
|
}
|
|
#endif
|
|
|
|
const LinearWorld *lw = dynamic_cast<LinearWorld*>(World::getWorld());
|
|
if(m_kart_properties->getTerrainImpulseType()
|
|
==KartProperties::IMPULSE_NORMAL &&
|
|
m_vehicle->getCentralImpulseTicks()<=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();
|
|
Vec3 impulse = normal - gravity* btDot(normal, gravity);
|
|
if(impulse.getX() || impulse.getZ())
|
|
impulse.normalize();
|
|
else
|
|
impulse = Vec3(0, 0, -1); // Arbitrary
|
|
// Impulse depends of kart speed - and speed can be negative
|
|
// If the speed is too low, karts can still get stuck into a wall
|
|
// so make sure there is always enough impulse to avoid this
|
|
float abs_speed = fabsf(getSpeed());
|
|
impulse *= ( abs_speed<10 ? 10.0f : sqrt(abs_speed) )
|
|
* m_kart_properties->getCollisionTerrainImpulse();
|
|
m_bounce_back_ticks = 0;
|
|
impulse = Vec3(0, 0, 0);
|
|
//m_vehicle->setTimedCentralImpulse(0.1f, impulse);
|
|
m_vehicle->setTimedCentralImpulse(0, 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(m_kart_properties->getTerrainImpulseType()
|
|
==KartProperties::IMPULSE_TO_DRIVELINE &&
|
|
lw && m_vehicle->getCentralImpulseTicks()<=0 &&
|
|
Track::getCurrentTrack()->isPushBackEnabled())
|
|
{
|
|
int sector = lw->getSectorForKart(this);
|
|
if(sector!=Graph::UNKNOWN_SECTOR)
|
|
{
|
|
// Use the first predecessor node, which is the most
|
|
// natural one (i.e. the one on the main driveline).
|
|
const DriveNode* dn = DriveGraph::get()->getNode(
|
|
DriveGraph::get()->getNode(sector)->getPredecessor(0));
|
|
Vec3 impulse = dn->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_ticks = (uint8_t)stk_config->time2Ticks(0.2f);
|
|
m_vehicle->setTimedCentralImpulse(
|
|
(uint16_t)stk_config->time2Ticks(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())
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
std::string particles = m->getCrashResetParticles();
|
|
if (!GUIEngine::isNoGraphics() &&
|
|
particles.size() > 0 && UserConfigParams::m_particles_effects > 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());
|
|
}
|
|
}
|
|
#endif
|
|
if (m->getCollisionReaction() == Material::RESCUE)
|
|
{
|
|
RescueAnimation::create(this);
|
|
m_last_factor_engine_sound = 0.0f;
|
|
}
|
|
else if (m->getCollisionReaction() == Material::PUSH_BACK)
|
|
{
|
|
// This variable is set to 0.2 in case of a kart-terrain collision
|
|
if (m_bounce_back_ticks <= (uint8_t)stk_config->time2Ticks(0.2f))
|
|
{
|
|
btVector3 push = m_body->getLinearVelocity().normalized();
|
|
push[1] = 0.1f;
|
|
m_body->applyCentralImpulse( -4000.0f*push );
|
|
m_bounce_back_ticks = (uint8_t)stk_config->time2Ticks(2.0f);
|
|
} // if m_bounce_back_ticks <= 0.2f
|
|
} // if (m->getCollisionReaction() == Material::PUSH_BACK)
|
|
} // if(m && m->getCollisionReaction() != Material::NORMAL &&
|
|
// !getKartAnimation())
|
|
m_controller->crashed(m);
|
|
} // crashed(Material)
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** Common code used when a kart or a material was hit.
|
|
* @param m The material collided into, or NULL if none
|
|
* @param k The kart collided into, or NULL if none
|
|
*/
|
|
void Kart::playCrashSFX(const Material* m, AbstractKart *k)
|
|
{
|
|
int ticks_since_start = World::getWorld()->getTicksSinceStart();
|
|
if(ticks_since_start-m_ticks_last_crash < 60) return;
|
|
|
|
m_ticks_last_crash = ticks_since_start;
|
|
// 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_ticks == 0)
|
|
{
|
|
if (getVelocity().length()> 0.555f)
|
|
{
|
|
const float speed_for_max_volume = 15; //The speed at which the sound plays at maximum volume
|
|
const float max_volume = 1; //The maximum volume a sound is played at
|
|
const float min_volume = 0.2f; //The minimum volume a sound is played at
|
|
|
|
float volume; //The volume the crash sound will be played at
|
|
|
|
if (k == NULL) //Collision with wall
|
|
{
|
|
volume = sqrt( abs(m_speed / speed_for_max_volume));
|
|
}
|
|
else
|
|
{
|
|
const Vec3 ThisKartVelocity = getVelocity();
|
|
const Vec3 OtherKartVelocity = k->getVelocity();
|
|
const Vec3 VelocityDifference = ThisKartVelocity - OtherKartVelocity;
|
|
const float LengthOfDifference = VelocityDifference.length();
|
|
|
|
volume = sqrt( abs(LengthOfDifference / speed_for_max_volume));
|
|
}
|
|
|
|
if (volume > max_volume) { volume = max_volume; }
|
|
else if (volume < min_volume) { volume = min_volume; }
|
|
|
|
SFXBase* crash_sound_emitter = getNextEmitter();
|
|
crash_sound_emitter->setVolume(volume);
|
|
|
|
// In case that the sfx is longer than 0.5 seconds, only play it if
|
|
// it's not already playing.
|
|
if (isShielded() || (k != NULL && k->isShielded()))
|
|
{
|
|
crash_sound_emitter->play(getXYZ(), m_boing_sound);
|
|
}
|
|
else
|
|
{
|
|
int idx = rand() % CRASH_SOUND_COUNT;
|
|
|
|
SFXBuffer* buffer = m_crash_sounds[idx];
|
|
crash_sound_emitter->play(getXYZ(), buffer);
|
|
}
|
|
} // if lin_vel > 0.555
|
|
} // if m_bounce_back_ticks == 0
|
|
} // playCrashSFX
|
|
|
|
// -----------------------------------------------------------------------------
|
|
/** 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) &&
|
|
!RewindManager::get()->isRewinding())
|
|
{
|
|
getNextEmitter()->play(getXYZ(), m_horn_sound);
|
|
}
|
|
|
|
} // 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
|
|
SFXManager::get().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",
|
|
// SFXManager::get()->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 ticks Number if physics time steps - should be 1.
|
|
*/
|
|
void Kart::updatePhysics(int ticks)
|
|
{
|
|
if (m_controls.getAccel() > 0.0f &&
|
|
World::getWorld()->getTicksSinceStart() == 1)
|
|
{
|
|
if (m_startup_boost > 0.0f)
|
|
{
|
|
m_kart_gfx->setCreationRateAbsolute(KartGFX::KGFX_ZIPPER,
|
|
100.0f * m_startup_boost);
|
|
m_max_speed->instantSpeedIncrease(MaxSpeed::MS_INCREASE_ZIPPER,
|
|
0.9f * m_startup_boost, m_startup_boost,
|
|
/*engine_force*/200.0f,
|
|
/*duration*/stk_config->time2Ticks(5.0f),
|
|
/*fade_out_time*/stk_config->time2Ticks(5.0f));
|
|
}
|
|
}
|
|
if (m_bounce_back_ticks > 0)
|
|
m_bounce_back_ticks -= ticks;
|
|
|
|
updateEnginePowerAndBrakes(ticks);
|
|
|
|
// apply flying physics if relevant
|
|
if (m_flying)
|
|
updateFlying();
|
|
|
|
m_skidding->update(ticks, isOnGround(), m_controls.getSteer(),
|
|
m_controls.getSkidControl());
|
|
if( ( m_skidding->getSkidState() == Skidding::SKID_ACCUMULATE_LEFT ||
|
|
m_skidding->getSkidState() == Skidding::SKID_ACCUMULATE_RIGHT ) &&
|
|
!m_skidding->isJumping() )
|
|
{
|
|
if(m_skid_sound->getStatus()!=SFXBase::SFX_PLAYING && !isWheeless())
|
|
m_skid_sound->play(getXYZ());
|
|
}
|
|
else if(m_skid_sound->getStatus()==SFXBase::SFX_PLAYING)
|
|
{
|
|
m_skid_sound->stop();
|
|
}
|
|
|
|
float steering = getMaxSteerAngle() * m_skidding->getSteeringFraction();
|
|
m_vehicle->setSteeringValue(steering, 0);
|
|
m_vehicle->setSteeringValue(steering, 1);
|
|
|
|
updateSliding();
|
|
|
|
// Cap speed if necessary
|
|
const Material *m = getMaterial();
|
|
|
|
float min_speed = m && m->isZipper() ? m->getZipperMinSpeed() : -1.0f;
|
|
m_max_speed->setMinSpeed(min_speed);
|
|
m_max_speed->update(ticks);
|
|
|
|
#ifdef XX
|
|
Log::info("Kart","angVel %f %f %f heading %f suspension %f %f %f %f"
|
|
,m_body->getAngularVelocity().getX()
|
|
,m_body->getAngularVelocity().getY()
|
|
,m_body->getAngularVelocity().getZ()
|
|
,getHeading()
|
|
,m_vehicle->getWheelInfo(0).m_raycastInfo.m_suspensionLength
|
|
,m_vehicle->getWheelInfo(1).m_raycastInfo.m_suspensionLength
|
|
,m_vehicle->getWheelInfo(2).m_raycastInfo.m_suspensionLength
|
|
,m_vehicle->getWheelInfo(3).m_raycastInfo.m_suspensionLength
|
|
);
|
|
#endif
|
|
|
|
} // updatephysics
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Adjust the engine sound effect depending on the speed of the kart. This
|
|
* is called during updateGraphics, i.e. once per rendered frame only.
|
|
* \param dt Time step size.
|
|
*/
|
|
void Kart::updateEngineSFX(float dt)
|
|
{
|
|
// Only update SFX during the last substep (otherwise too many SFX commands
|
|
// in one frame), and if sfx are enabled
|
|
if(!m_engine_sound || !SFXManager::get()->sfxAllowed() )
|
|
return;
|
|
|
|
// when going faster, use higher pitch for engine
|
|
if(isOnGround())
|
|
{
|
|
float max_speed = m_kart_properties->getEngineMaxSpeed();
|
|
|
|
// 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 = max_speed > 0 ? m_speed/max_speed : 1.0f;
|
|
// Speed at this stage is not yet capped, reduce the amount beyond 1
|
|
if (f> 1.0f) f = 1.0f + (1.0f-1.0f/f);
|
|
|
|
float fc = f;
|
|
if (fc>1.0f) fc = 1.0f;
|
|
float gears = 3.0f * fmod(fc, 0.333334f);
|
|
assert(!std::isnan(f));
|
|
m_last_factor_engine_sound = (0.9f*f + gears) * 0.35f;
|
|
m_engine_sound->setSpeedPosition(0.6f + m_last_factor_engine_sound, getXYZ());
|
|
}
|
|
else
|
|
{
|
|
// When flying, reduce progressively the sound engine (since we can't accelerate)
|
|
m_last_factor_engine_sound *= (1.0f-0.1f*dt);
|
|
m_engine_sound->setSpeedPosition(0.6f + m_last_factor_engine_sound, getXYZ());
|
|
if (m_speed < 0.1f) m_last_factor_engine_sound = 0.0f;
|
|
}
|
|
} // updateEngineSFX
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Reduces the engine power according to speed
|
|
*
|
|
* TODO : find where the physics already apply a linear force decrease
|
|
* TODO : While this work fine, it should ideally be in physics
|
|
* However, the function use some kart properties and parachute
|
|
* effect needs to be applied, so keep both working if moving
|
|
* \param engine_power : the engine power on which to apply the decrease
|
|
*/
|
|
float Kart::applyAirFriction(float engine_power)
|
|
{
|
|
//The physics already do that a certain amount of engine force is needed to keep going
|
|
//at a given speed (~39,33 engine force = 1 speed for a mass of 350)
|
|
//But it's either too slow to accelerate to a target speed or makes it
|
|
//too easy to accelerate farther.
|
|
//Instead of making increasing gears have enormous power gaps, apply friction
|
|
|
|
float mass_factor = m_kart_properties->getMass()/350.0f;
|
|
float compense_linear_slowdown = 39.33f*fabsf(getSpeed())*mass_factor;
|
|
|
|
engine_power += compense_linear_slowdown;
|
|
|
|
// The result will always be a positive number
|
|
float friction_intensity = fabsf(getSpeed());
|
|
|
|
// Not a pure quadratic evolution as it would be too brutal
|
|
friction_intensity *= sqrt(friction_intensity)*5;
|
|
|
|
// Apply parachute physics
|
|
// Currently, all karts have the same base friction
|
|
// If this is changed, a compensation needs to be added here
|
|
if(m_attachment->getType()==Attachment::ATTACH_PARACHUTE)
|
|
friction_intensity *= m_kart_properties->getParachuteFriction();
|
|
|
|
if (friction_intensity < 0.0f) friction_intensity = 0.0f;
|
|
|
|
// We subtract the friction from the engine power
|
|
// 1)This is the logical behavior
|
|
// 2)That way, engine boosts remain useful at high speed
|
|
// 3)It helps heavier karts, who have an higher engine power
|
|
|
|
engine_power-=friction_intensity;
|
|
|
|
return engine_power;
|
|
} //applyAirFriction
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Sets the engine power. It considers the engine specs, items that influence
|
|
* the available power, and braking/steering.
|
|
*/
|
|
void Kart::updateEnginePowerAndBrakes(int ticks)
|
|
{
|
|
updateWeight();
|
|
updateNitro(ticks);
|
|
float engine_power = getActualWheelForce();
|
|
|
|
// apply nitro boost if relevant
|
|
if(getSpeedIncreaseTicksLeft(MaxSpeed::MS_INCREASE_NITRO) > 0)
|
|
{
|
|
engine_power*= m_kart_properties->getNitroEngineMult();
|
|
}
|
|
|
|
// apply bubblegum physics if relevant
|
|
if (m_bubblegum_ticks > 0)
|
|
{
|
|
engine_power = 0.0f;
|
|
m_body->applyTorque(btVector3(0.0,
|
|
m_kart_properties->getBubblegumTorque() *
|
|
(m_bubblegum_torque_sign ? 1.0f : -1.0f), 0.0));
|
|
}
|
|
|
|
if(m_controls.getAccel()) // 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_ticks > 0)
|
|
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.getSkidControl() &&
|
|
m_kart_properties->getSkidVisualTime() == 0)
|
|
engine_power *= 0.5f;
|
|
|
|
// This also applies parachute physics if relevant
|
|
engine_power = applyAirFriction(engine_power);
|
|
|
|
applyEngineForce(engine_power*m_controls.getAccel());
|
|
|
|
// 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);
|
|
m_brake_ticks = 0;
|
|
}
|
|
else // not accelerating
|
|
{
|
|
//The engine power is still guaranteed >= 0 at this point
|
|
float braking_power = engine_power;
|
|
|
|
// This also applies parachute physics if relevant
|
|
engine_power = applyAirFriction(engine_power);
|
|
|
|
if(m_controls.getBrake())
|
|
{ // check if the player is currently only slowing down
|
|
// or moving backwards
|
|
if(m_speed > 0.0f)
|
|
{ // Still going forward while braking
|
|
applyEngineForce(engine_power-braking_power*3);
|
|
m_brake_ticks += ticks;
|
|
// Apply the brakes - include the time dependent brake increase
|
|
float f = 1.0f + stk_config->ticks2Time(m_brake_ticks)
|
|
* m_kart_properties->getEngineBrakeTimeIncrease();
|
|
m_vehicle->setAllBrakes(m_kart_properties->getEngineBrakeFactor() * f);
|
|
}
|
|
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->getEngineMaxSpeedReverseRatio())
|
|
{
|
|
// The backwards acceleration is artificially increased to
|
|
// allow players to get "unstuck" quicker if they hit e.g.
|
|
// a wall.
|
|
applyEngineForce(engine_power-braking_power*3);
|
|
}
|
|
else // -m_speed >= max speed on this terrain
|
|
{
|
|
applyEngineForce(0.0f);
|
|
}
|
|
|
|
} // m_speed <00
|
|
}
|
|
else // no braking and no acceleration
|
|
{
|
|
m_brake_ticks = 0;
|
|
// lift the foot from throttle, let friction slow it down
|
|
assert(!std::isnan(m_controls.getAccel()));
|
|
assert(!std::isnan(engine_power));
|
|
|
|
// 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 (std::abs(m_speed) < 5.0f)
|
|
{
|
|
// Engine must be 0, otherwise braking is not used at all
|
|
applyEngineForce(0);
|
|
m_vehicle->setAllBrakes(20.0f);
|
|
}
|
|
else
|
|
{
|
|
// This ensure that parachute and air friction are applied
|
|
// and that the kart gracefully slows down
|
|
applyEngineForce(engine_power-braking_power);
|
|
m_vehicle->setAllBrakes(0);
|
|
}
|
|
} // no braking and no acceleration
|
|
} // not accelerating
|
|
} // updateEnginePowerAndBrakes
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Handles sliding, i.e. the kart sliding off terrain that is too steep.
|
|
* 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. This is done for each
|
|
* wheel individually (since otherwise karts were still able with enough
|
|
* speed to drive on walls - one wheel 'on a wall' would not tilt the
|
|
* kart chassis enough to trigger sliding, but since that wheel had still
|
|
* full friction, give the kart an upwards velocity).
|
|
*/
|
|
void Kart::updateSliding()
|
|
{
|
|
// Allow the sliding to be disabled per material (for so called
|
|
// high adhesion material), which is useful for e.g. banked curves.
|
|
// We don't have per-wheel material, so the test for special material
|
|
// with high adhesion is done per kart (not per wheel).
|
|
const Material * material = getMaterial();
|
|
if (material && material->highTireAdhesion())
|
|
{
|
|
for (int i = 0; i < m_vehicle->getNumWheels(); i++)
|
|
{
|
|
btWheelInfo &wheel = m_vehicle->getWheelInfo(i);
|
|
wheel.m_frictionSlip = m_kart_properties->getFrictionSlip();
|
|
}
|
|
m_vehicle->setSliding(false);
|
|
|
|
}
|
|
|
|
// Now test for each wheel if it should be sliding
|
|
// -----------------------------------------------
|
|
bool enable_sliding = false;
|
|
|
|
// We need the 'up' vector, which can be affected by material
|
|
// with gravity. So use the body's gravity to determine up:
|
|
Vec3 up = -m_body->getGravity();
|
|
up.normalize();
|
|
for (int i = 0; i < m_vehicle->getNumWheels(); i++)
|
|
{
|
|
const btWheelInfo &wheel = m_vehicle->getWheelInfo(i);
|
|
if (!wheel.m_raycastInfo.m_isInContact) continue;
|
|
|
|
const btVector3 &norm = m_vehicle->getWheelInfo(i).m_raycastInfo.m_contactNormalWS;
|
|
float distanceFromUp = norm.dot(up);
|
|
float friction;
|
|
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;
|
|
}
|
|
m_vehicle->getWheelInfo(i).m_frictionSlip = friction * m_kart_properties->getFrictionSlip();
|
|
} // for i < numWheels
|
|
|
|
m_vehicle->setSliding(enable_sliding);
|
|
} // updateSliding
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Adjusts kart translation if the kart is flying (in debug mode).
|
|
*/
|
|
void Kart::updateFlying()
|
|
{
|
|
m_body->setLinearVelocity(m_body->getLinearVelocity() * 0.99f);
|
|
|
|
if (m_controls.getAccel())
|
|
{
|
|
btVector3 velocity = m_body->getLinearVelocity();
|
|
if (velocity.length() < 25)
|
|
{
|
|
float orientation = getHeading();
|
|
m_body->applyCentralImpulse(btVector3(100.0f*sinf(orientation), 0.0,
|
|
100.0f*cosf(orientation)));
|
|
}
|
|
}
|
|
else if (m_controls.getBrake())
|
|
{
|
|
btVector3 velocity = m_body->getLinearVelocity();
|
|
if (velocity.length() > -15)
|
|
{
|
|
float orientation = getHeading();
|
|
m_body->applyCentralImpulse(btVector3(-100.0f*sinf(orientation), 0.0,
|
|
-100.0f*cosf(orientation)));
|
|
}
|
|
}
|
|
|
|
if (m_controls.getSteer()!= 0.0f)
|
|
{
|
|
m_body->applyTorque(btVector3(0.0, m_controls.getSteer()*3500.0f, 0.0));
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
bool always_animated = (type == RaceManager::KT_PLAYER &&
|
|
RaceManager::get()->getNumLocalPlayers() == 1);
|
|
if (!GUIEngine::isNoGraphics())
|
|
m_node = m_kart_model->attachModel(is_animated_model, always_animated);
|
|
|
|
#ifdef DEBUG
|
|
if (m_node)
|
|
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.reset(new Attachment(this));
|
|
createPhysics();
|
|
|
|
m_slipstream.reset(new SlipStream(this));
|
|
|
|
#ifndef SERVER_ONLY
|
|
m_skidmarks = nullptr;
|
|
m_shadow = nullptr;
|
|
if (!GUIEngine::isNoGraphics() &&
|
|
m_kart_properties->getSkidEnabled() && CVS->isGLSL())
|
|
{
|
|
m_skidmarks.reset(new SkidMarks(*this));
|
|
}
|
|
|
|
if (!GUIEngine::isNoGraphics() &&
|
|
CVS->isGLSL() && !CVS->isShadowEnabled() && m_kart_properties
|
|
->getShadowMaterial()->getSamplerPath(0) != "unicolor_white")
|
|
{
|
|
m_shadow.reset(new Shadow(m_kart_properties->getShadowMaterial(),
|
|
*this));
|
|
}
|
|
#endif
|
|
World::getWorld()->kartAdded(this, m_node);
|
|
m_kart_gfx.reset(
|
|
new KartGFX(this, Track::getCurrentTrack()->getIsDuringDay()));
|
|
m_skidding.reset(new Skidding(this));
|
|
// Create the stars effect
|
|
if (!GUIEngine::isNoGraphics())
|
|
m_stars_effect.reset(new Stars(this));
|
|
|
|
} // loadData
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** 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(!std::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
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Computes the transform of the graphical kart chasses with regards to the
|
|
* physical chassis. This function is called once the kart comes to rest
|
|
* before the race starts (see World::resetAllKarts). Based on the current
|
|
* physical kart position it computes an (at this stage Y-only) offset by
|
|
* which the graphical chassis is moved so that it appears the way it is
|
|
* designed in blender. This means that the distance of the wheels from the
|
|
* chassis (i.e. suspension) appears as in blender when karts are in rest.
|
|
* See updateGraphics for more details.
|
|
*/
|
|
void Kart::kartIsInRestNow()
|
|
{
|
|
AbstractKart::kartIsInRestNow();
|
|
m_default_suspension_force = 0.0f;
|
|
for (int i = 0; i < m_vehicle->getNumWheels(); i++)
|
|
{
|
|
const btWheelInfo &wi = m_vehicle->getWheelInfo(i);
|
|
m_default_suspension_force += wi.m_raycastInfo.m_suspensionLength;
|
|
}
|
|
|
|
// The offset 'lowest point' is added to avoid that the
|
|
// visual chassis appears in the ground (it could be any
|
|
// constant, there is no real reason to use the lowest point
|
|
// but that value has worked good in the past). See documentation
|
|
// for updateGraphics() for full details.
|
|
m_graphical_y_offset = -m_default_suspension_force /
|
|
m_vehicle->getNumWheels() + m_kart_model->getLowestPoint();
|
|
|
|
m_kart_model->setDefaultSuspension();
|
|
} // kartIsInRestNow
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
SFXBase* Kart::getNextEmitter()
|
|
{
|
|
m_emitter_id = (m_emitter_id + 1) % EMITTER_COUNT;
|
|
|
|
// The emitter is requested when a new sound is to be played.
|
|
// Always reset the volume to 1.0f (full), as crashes may
|
|
// have altered it. See issue #3596
|
|
m_emitters[m_emitter_id]->setVolume(1.0f);
|
|
|
|
return m_emitters[m_emitter_id];
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Updates the graphics model. It is responsible for positioning the graphical
|
|
* chasses at an 'appropriate' position: typically, the physical model has
|
|
* much longer suspension, so if the graphical chassis would be at the same
|
|
* location as the physical chassis, the wheels would be too far away.
|
|
* Instead the default suspension length is saved once the kart have been
|
|
* settled at start up (see World::resetAllKarts, which just runs several
|
|
* physics-only simulation steps so that karts come to a rest). Then at
|
|
* race time, only the difference between the current suspension length and
|
|
* this default suspension length is used. The graphical kart chassis will be
|
|
* offset so that when the kart is in rest, i.e. suspension length ==
|
|
* default suspension length, the kart will look the way it was modelled in
|
|
* blender. To explain the various offsets used, here a view from the side
|
|
* focusing on the Y axis only (X/Z position of the graphical chassis is
|
|
* identical to the physical chassis):
|
|
*
|
|
* Y| | visual kart | physical kart
|
|
* | | |
|
|
* | | |
|
|
* | | |
|
|
* | | +-------------COG---------------
|
|
* | | :
|
|
* | +---------low------ :
|
|
* | O :
|
|
* +--------------------------------------------------------------------------
|
|
* X
|
|
* 'O' : visual wheel ':' : raycast from physics
|
|
* 'low' : lowest Y coordinate of COG : Center of gravity (at bottom of
|
|
* model chassis)
|
|
*
|
|
* The visual kart is stored so that if it is shown at (0,0,0) it would be
|
|
* the same as in blender. This on the other hand means, if the kart is shown
|
|
* at the position of the physical chassis (which is at COG in the picture
|
|
* above), the kart and its wheels would be floating in the air (exactly by
|
|
* as much as the suspension length), and the wheels would be exactly at the
|
|
* bottom of the physical chassis (i.e. just on the plane going through COG
|
|
* and parallel to the ground).
|
|
* If we want to align the visual chassis to be the same as the physical
|
|
* chassis, we would need to subtract 'low' from the physical position.
|
|
* If the kart is then displayed at COG.y-low, the bottom of the kart (which
|
|
* is at 'low' above ground) would be at COG.y-low + low = COG.y --> visual
|
|
* and physical chassis are identical.
|
|
*
|
|
* Unfortunately, the suspension length used in the physics is much too high,
|
|
* the karts would be way above their wheels, basically disconneccted
|
|
* (typical physical suspension length is around 0.28, while the distance
|
|
* between wheel and chassis in blender is in the order of 0.10 --> so there
|
|
* would be an additional distance of around 0.18 between wheel chassis as
|
|
* designed in blender and in stk - even more if the kart is driving downhill
|
|
* when the suspension extends further to keep contact with the ground).
|
|
* To make the visuals look closer to what they are in blender, an additional
|
|
* offset is added: before the start of a race the physics simulation is run
|
|
* to find a stable position for each kart (see World::resetAllKarts). Once
|
|
* a kart is stable, we save this suspension length in m_graphical_y_offset.
|
|
* This offset is subtracted from the COG of the kart. So if the kart is in
|
|
* rest (i.e. suspenion == default_suspension == m_graphical_y_offset),
|
|
* The kart is showen exactly at the same height above ground as it is in
|
|
* blender. If the suspension is shorter by DY (because the kart is
|
|
* accelerating, the ground goes up, ...), the visual chassis is lowered by
|
|
* DY as well.
|
|
*
|
|
* While the above algorithm indeed results in the right position of the
|
|
* visual chassis, in reality the visual chassis is too low. For example,
|
|
* nolok's chassis has its lowest point at the rear at around 0.10 above the
|
|
* ground (and the lowest point overall is 0.05, though this is at the front
|
|
* and so not easily visible), so if the suspension is compressed by more than
|
|
* that, the chassiswill appear to be in the ground. Testing on the sand track
|
|
* shows that the suspension is compressed by 0.12 (and up to 0.16 in some
|
|
* extreme points), which means that the chassis will appear to be in the
|
|
* ground quite easily. Therefore the chassis is actually moved up a bit to
|
|
* avoid this problem. Historically (due to never sorting out that formula
|
|
* properly) the chassis was moved twice as high as its lowest point, e.g.
|
|
* nolok's lowest point is at 0.05, so the whole chassis was raised by 0.05
|
|
* (this was not done by design, but because of a bug ;) ). Since this visual
|
|
* offset has worked well in the past, the visual chassis is moved by the
|
|
* same amount higher.
|
|
*
|
|
* Of course this means that the Y position of the wheels (relative to the
|
|
* visual kart chassis) needs to be adjusted: if the kart is in rest, the
|
|
* wheels are exactly on the ground. If the suspension is shorter, that wheel
|
|
* would appear to be partly in the ground, and if the suspension is longer,
|
|
* the wheel would not touch the ground.
|
|
*
|
|
* The wheels must be offset by how much the current suspension length is
|
|
* longer or shorter than the default (i.e. at rest) suspension length.
|
|
* This is done in KartModel (pos is the position of the wheel relative
|
|
* to the visual kart chassis):
|
|
* pos.Y += m_default_physics_suspension[i]
|
|
* - wi.m_raycastInfo.m_suspensionLength
|
|
* But since the chassis is raised an additional 'getLowestPoint' (see
|
|
* desctiption two paragraphs above), the wheels need to be lowered by that
|
|
* amount so that they still touch the ground (the wheel nodes are child of
|
|
* the chassis scene node, so if the chassis is raised by X, the wheels need
|
|
* to be lowered by X).
|
|
* This function also takes additional graphical effects into account, e.g.
|
|
* a (visual only) jump when skidding, and leaning of the kart.
|
|
*/
|
|
void Kart::updateGraphics(float 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());
|
|
}
|
|
*/
|
|
#ifndef SERVER_ONLY
|
|
if (m_node && isSquashed() &&
|
|
m_node->getScale() != core::vector3df(1.0f, 0.5f, 1.0f))
|
|
setSquashGraphics();
|
|
else if (m_node && !isSquashed() &&
|
|
m_node->getScale() != core::vector3df(1.0f, 1.0f, 1.0f))
|
|
unsetSquash();
|
|
#endif
|
|
|
|
// Disable smoothing network body so it doesn't smooth the animation
|
|
// for karts in client
|
|
if (NetworkConfig::get()->isNetworking() &&
|
|
NetworkConfig::get()->isClient() &&
|
|
(!getController() || !getController()->isLocalPlayerController()))
|
|
{
|
|
if (m_kart_animation && SmoothNetworkBody::isEnabled())
|
|
{
|
|
SmoothNetworkBody::setEnable(false);
|
|
}
|
|
else if (!m_kart_animation && !SmoothNetworkBody::isEnabled())
|
|
{
|
|
SmoothNetworkBody::setEnable(true);
|
|
SmoothNetworkBody::reset();
|
|
SmoothNetworkBody::setSmoothedTransform(getTrans());
|
|
}
|
|
}
|
|
|
|
if (m_kart_animation)
|
|
m_kart_animation->updateGraphics(dt);
|
|
|
|
for (int i = 0; i < EMITTER_COUNT; i++)
|
|
m_emitters[i]->setPosition(getXYZ());
|
|
m_skid_sound->setPosition(getXYZ());
|
|
m_nitro_sound->setPosition(getXYZ());
|
|
|
|
m_attachment->updateGraphics(dt);
|
|
|
|
// update star effect (call will do nothing if stars are not activated)
|
|
// Remove it if no invulnerability
|
|
if (m_stars_effect)
|
|
{
|
|
if (!isInvulnerable() && m_stars_effect->isEnabled())
|
|
{
|
|
m_stars_effect->reset();
|
|
m_stars_effect->update(1);
|
|
}
|
|
else
|
|
m_stars_effect->update(dt);
|
|
}
|
|
|
|
// Update particle effects (creation rate, and emitter size
|
|
// depending on speed)
|
|
m_kart_gfx->update(dt);
|
|
if (m_collision_particles) m_collision_particles->update(dt);
|
|
|
|
// --------------------------------------------------------
|
|
float nitro_frac = 0;
|
|
if ( (m_controls.getNitro() || m_min_nitro_ticks > 0) &&
|
|
m_collected_energy > 0 )
|
|
{
|
|
// fabs(speed) is important, otherwise the negative number will
|
|
// become a huge unsigned number in the particle scene node!
|
|
nitro_frac = fabsf(getSpeed()) / (m_kart_properties->getEngineMaxSpeed());
|
|
// The speed of the kart can be higher (due to powerups) than
|
|
// the normal maximum speed of the kart.
|
|
if(nitro_frac>1.0f) nitro_frac = 1.0f;
|
|
}
|
|
m_kart_gfx->updateNitroGraphics(nitro_frac);
|
|
|
|
// Handle leaning of karts
|
|
// -----------------------
|
|
// 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->getEngineMaxSpeed();
|
|
float steer_frac = m_skidding->getSteeringFraction();
|
|
const float roll_speed = m_kart_properties->getLeanSpeed() * DEGREE_TO_RAD;
|
|
|
|
if(speed_frac > 0.8f && fabsf(steer_frac)>0.2f)
|
|
{
|
|
// Use steering and speed ^ 2,
|
|
// which means less effect at lower steering and speed.
|
|
speed_frac = std::min(speed_frac - 0.6f, 1.0f);
|
|
steer_frac = (steer_frac+0.25f)*0.8f;
|
|
const float f = m_skidding->getSteeringFraction();
|
|
const float max_lean = -m_kart_properties->getLeanMax() * DEGREE_TO_RAD
|
|
* f * speed_frac * speed_frac;
|
|
|
|
int max_lean_sign = extract_sign(max_lean);
|
|
m_current_lean += max_lean_sign * dt* roll_speed;
|
|
if( (max_lean > 0 && m_current_lean > max_lean)
|
|
||(max_lean < 0 && 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
|
|
int lean_sign = extract_sign(m_current_lean);
|
|
m_current_lean -= lean_sign * dt * roll_speed;
|
|
if (lean_sign != extract_sign(m_current_lean))
|
|
m_current_lean = 0.0f;
|
|
}
|
|
|
|
// If the kart is leaning, part of the kart might end up 'in' the track.
|
|
// To avoid this, raise the kart enough to offset the leaning.
|
|
float lean_height = tanf(m_current_lean) * getKartWidth()*0.5f;
|
|
|
|
Moveable::updateSmoothedGraphics(dt);
|
|
|
|
// Update the skidding jump height:
|
|
Vec3 center_shift(0, 0, 0);
|
|
float jump_height = m_skidding->updateGraphics(dt);
|
|
center_shift.setY(jump_height + fabsf(lean_height) + m_graphical_y_offset);
|
|
center_shift = getSmoothedTrans().getBasis() * center_shift;
|
|
|
|
float heading = m_skidding->getVisualSkidRotation();
|
|
Moveable::updateGraphics(center_shift,
|
|
btQuaternion(heading, 0, -m_current_lean));
|
|
|
|
static video::SColor pink(255, 255, 133, 253);
|
|
static video::SColor green(255, 61, 87, 23);
|
|
|
|
#ifndef SERVER_ONLY
|
|
// draw skidmarks if relevant (we force pink skidmarks on when hitting
|
|
// a bubblegum)
|
|
if (World::getWorld()->getPhase() !=
|
|
WorldStatus::IN_GAME_MENU_PHASE &&
|
|
m_kart_properties->getSkidEnabled() && m_skidmarks)
|
|
{
|
|
m_skidmarks->update(dt,
|
|
m_bubblegum_ticks > 0,
|
|
(m_bubblegum_ticks > 0
|
|
? (m_has_caught_nolok_bubblegum ? &green
|
|
: &pink)
|
|
: NULL));
|
|
}
|
|
#endif
|
|
|
|
// m_speed * dt is the distance the kart has moved, which determines
|
|
// how much the wheels need to rotate.
|
|
m_kart_model->update(dt, m_speed * dt, getSteerPercent(), m_speed,
|
|
m_current_lean);
|
|
|
|
#ifndef SERVER_ONLY
|
|
// Determine the shadow position from the terrain Y position. This
|
|
// leaves the shadow on the ground even if the kart is jumping because
|
|
// of skidding (shadows are disabled when wheel are not on the track).
|
|
if (m_shadow)
|
|
{
|
|
const bool emergency = getKartAnimation() != NULL;
|
|
m_shadow->update(isOnGround() && !emergency);
|
|
}
|
|
#endif
|
|
|
|
handleMaterialGFX(dt);
|
|
updateEngineSFX(dt);
|
|
handleMaterialSFX();
|
|
} // 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 core::stringw& text)
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
if (GUIEngine::isNoGraphics())
|
|
return;
|
|
|
|
BoldFace* bold_face = font_manager->getFont<BoldFace>();
|
|
STKTextBillboard* tb =
|
|
new STKTextBillboard(
|
|
GUIEngine::getSkin()->getColor("font::bottom"),
|
|
GUIEngine::getSkin()->getColor("font::top"),
|
|
getNode(), irr_driver->getSceneManager(), -1,
|
|
core::vector3df(0.0f, 1.5f, 0.0f),
|
|
core::vector3df(0.5f, 0.5f, 0.5f));
|
|
if (CVS->isGLSL())
|
|
tb->init(text, bold_face);
|
|
else
|
|
tb->initLegacy(text, bold_face);
|
|
tb->drop();
|
|
// 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.
|
|
#endif
|
|
} // setOnScreenText
|
|
|
|
// ------------------------------------------------------------------------
|
|
/** Returns the normal of the terrain the kart is over atm. This is
|
|
* defined even if the kart is flying. */
|
|
const Vec3& Kart::getNormal() const
|
|
{
|
|
return m_terrain_info->getNormal();
|
|
} // getNormal
|
|
|
|
// ------------------------------------------------------------------------
|
|
/** Returns a more recent different previous position */
|
|
const Vec3& Kart::getRecentPreviousXYZ() const
|
|
{
|
|
//Not the most recent, because the angle variations would be too
|
|
//irregular on some tracks whose roads are not smooth enough
|
|
return m_previous_xyz[m_xyz_history_size/5];
|
|
} // getRecentPreviousXYZ
|
|
|
|
// ------------------------------------------------------------------------
|
|
void Kart::playSound(SFXBuffer* buffer)
|
|
{
|
|
if (!RewindManager::get()->isRewinding())
|
|
getNextEmitter()->play(getXYZ(), buffer);
|
|
} // playSound
|
|
|
|
// ------------------------------------------------------------------------
|
|
const video::SColor& Kart::getColor() const
|
|
{
|
|
return m_kart_properties->getColor();
|
|
} // getColor
|
|
|
|
// ------------------------------------------------------------------------
|
|
bool Kart::isVisible() const
|
|
{
|
|
return m_node && m_node->isVisible();
|
|
} // isVisible
|
|
|
|
/* EOF */
|