Files
stk-code_catmod/src/items/flyable.cpp

820 lines
31 KiB
C++

//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2007-2015 Joerg Henrichs
//
// Linear item-kart intersection function written by
// Copyright (C) 2009-2015 David Mikos
//
// 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 "items/flyable.hpp"
#include <cmath>
#include <IMeshManipulator.h>
#include <IMeshSceneNode.h>
#include "audio/sfx_base.hpp"
#include "achievements/achievements_status.hpp"
#include "config/player_manager.hpp"
#include "graphics/explosion.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/material.hpp"
#include "graphics/mesh_tools.hpp"
#include "guiengine/engine.hpp"
#include "io/xml_node.hpp"
#include "items/projectile_manager.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/cannon_animation.hpp"
#include "karts/controller/controller.hpp"
#include "karts/explosion_animation.hpp"
#include "modes/linear_world.hpp"
#include "network/compress_network_body.hpp"
#include "network/network_config.hpp"
#include "network/network_string.hpp"
#include "network/rewind_manager.hpp"
#include "physics/physics.hpp"
#include "tracks/track.hpp"
#include "utils/constants.hpp"
#include "utils/string_utils.hpp"
#include "utils/vs.hpp"
#include <typeinfo>
// static variables:
float Flyable::m_st_speed [PowerupManager::POWERUP_MAX];
scene::IMesh* Flyable::m_st_model [PowerupManager::POWERUP_MAX];
float Flyable::m_st_min_height [PowerupManager::POWERUP_MAX];
float Flyable::m_st_max_height [PowerupManager::POWERUP_MAX];
float Flyable::m_st_force_updown[PowerupManager::POWERUP_MAX];
Vec3 Flyable::m_st_extend [PowerupManager::POWERUP_MAX];
// ----------------------------------------------------------------------------
Flyable::Flyable(AbstractKart *kart, PowerupManager::PowerupType type,
float mass)
: Moveable(), TerrainInfo(), m_mass(mass)
{
// get the appropriate data from the static fields
m_speed = m_st_speed[type];
m_extend = m_st_extend[type];
m_max_height = m_st_max_height[type];
m_min_height = m_st_min_height[type];
m_average_height = (m_min_height+m_max_height)/2.0f;
m_force_updown = m_st_force_updown[type];
m_owner = kart;
m_type = type;
m_has_hit_something = false;
m_shape = NULL;
m_animation = NULL;
m_adjust_up_velocity = true;
m_ticks_since_thrown = 0;
m_position_offset = Vec3(0,0,0);
m_owner_has_temporary_immunity = true;
m_do_terrain_info = true;
m_deleted_once = false;
m_max_lifespan = -1;
m_compressed_gravity_vector = 0;
// It will be reset for each state restore
m_has_server_state = true;
m_last_deleted_ticks = -1;
// Add the graphical model
#ifndef SERVER_ONLY
if (!GUIEngine::isNoGraphics())
{
setNode(irr_driver->addMesh(m_st_model[type],
StringUtils::insertValues("flyable_%i", (int)type)));
#ifdef DEBUG
std::string debug_name("flyable: ");
debug_name += type;
getNode()->setName(debug_name.c_str());
#endif
}
#endif
// Smooth network body for flyable doesn't seem to be needed, most of the
// time it rewinds almost the same
SmoothNetworkBody::setEnable(false);
m_created_ticks = World::getWorld()->getTicksSinceStart();
} // Flyable
// ----------------------------------------------------------------------------
/** Creates a bullet physics body for the flyable item.
* \param forw_offset How far ahead of the kart the flyable should be
* positioned. Necessary to avoid exploding a rocket inside of the
* firing kart.
* \param velocity Initial velocity of the flyable.
* \param shape Collision shape of the flyable.
* \param gravity Gravity to use for this flyable.
* \param rotates True if the item should rotate, otherwise the angular factor
* is set to 0 preventing rotations from happening.
* \param turn_around True if the item is fired backwards.
* \param custom_direction If defined the initial heading for this item,
* otherwise the kart's heading will be used.
*/
void Flyable::createPhysics(float forw_offset, const Vec3 &velocity,
btCollisionShape *shape,
float restitution, const btVector3& gravity,
const bool rotates, const bool turn_around,
const btTransform* custom_direction)
{
// Remove previously physics data if any
removePhysics();
// Get Kart heading direction
btTransform trans = ( !custom_direction ? m_owner->getAlignedTransform()
: *custom_direction );
// Apply offset
btTransform offset_transform;
offset_transform.setIdentity();
assert(!std::isnan(m_average_height));
assert(!std::isnan(forw_offset));
offset_transform.setOrigin(Vec3(0,m_average_height,forw_offset));
// turn around
if(turn_around)
{
btTransform turn_around_trans;
//turn_around_trans.setOrigin(trans.getOrigin());
turn_around_trans.setIdentity();
turn_around_trans.setRotation(btQuaternion(btVector3(0, 1, 0), M_PI));
trans *= turn_around_trans;
}
trans *= offset_transform;
m_shape = shape;
createBody(m_mass, trans, m_shape, restitution);
m_user_pointer.set(this);
Physics::get()->addBody(getBody());
m_body->setGravity(gravity);
if (gravity.length2() != 0.0f && m_do_terrain_info)
{
m_compressed_gravity_vector = MiniGLM::compressVector3(
Vec3(m_body->getGravity().normalized()).toIrrVector());
}
// Rotate velocity to point in the right direction
btVector3 v=trans.getBasis()*velocity;
if(m_mass!=0.0f) // Don't set velocity for kinematic or static objects
{
#ifdef DEBUG
// Just to get some additional information if the assert is triggered
if(std::isnan(v.getX()) || std::isnan(v.getY()) || std::isnan(v.getZ()))
{
Log::debug("[Flyable]", "vel %f %f %f v %f %f %f",
velocity.getX(),velocity.getY(),velocity.getZ(),
v.getX(),v.getY(),v.getZ());
}
#endif
assert(!std::isnan(v.getX()));
assert(!std::isnan(v.getY()));
assert(!std::isnan(v.getZ()));
m_body->setLinearVelocity(v);
if(!rotates) m_body->setAngularFactor(0.0f); // prevent rotations
}
} // createPhysics
// -----------------------------------------------------------------------------
/** Initialises the static members of this class for a certain type with
* default values and with settings from powerup.xml.
* \param The xml node containing settings.
* \param model The mesh to use.
* \param type The type of flyable.
*/
void Flyable::init(const XMLNode &node, scene::IMesh *model,
PowerupManager::PowerupType type)
{
m_st_speed[type] = 25.0f;
m_st_max_height[type] = 1.0f;
m_st_min_height[type] = 3.0f;
m_st_force_updown[type] = 15.0f;
node.get("speed", &(m_st_speed[type]) );
node.get("min-height", &(m_st_min_height[type]) );
node.get("max-height", &(m_st_max_height[type]) );
node.get("force-updown", &(m_st_force_updown[type]));
// Store the size of the model
Vec3 min, max;
MeshTools::minMax3D(model, &min, &max);
m_st_extend[type] = btVector3(max-min);
m_st_model[type] = model;
} // init
//-----------------------------------------------------------------------------
Flyable::~Flyable()
{
removePhysics();
if (m_animation)
{
m_animation->handleResetRace();
delete m_animation;
}
} // ~Flyable
//-----------------------------------------------------------------------------
/* Called when delete this flyable or re-firing during rewind. */
void Flyable::removePhysics()
{
if (m_shape)
{
delete m_shape;
m_shape = NULL;
}
if (m_body.get())
{
Physics::get()->removeBody(m_body.get());
m_body.reset();
}
} // removePhysics
//-----------------------------------------------------------------------------
/** Returns information on what is the closest kart and at what distance it is.
* All 3 parameters first are of type 'out'. 'inFrontOf' can be set if you
* wish to know the closest kart in front of some karts (will ignore those
* behind). Useful e.g. for throwing projectiles in front only.
*/
void Flyable::getClosestKart(const AbstractKart **minKart,
float *minDistSquared, Vec3 *minDelta,
const AbstractKart* inFrontOf,
const bool backwards) const
{
btTransform trans_projectile = (inFrontOf != NULL ? inFrontOf->getTrans()
: getTrans());
*minDistSquared = 999999.9f;
*minKart = NULL;
World *world = World::getWorld();
for(unsigned int i=0 ; i<world->getNumKarts(); i++ )
{
AbstractKart *kart = world->getKart(i);
// If a kart has star effect shown, the kart is immune, so
// it is not considered a target anymore.
if(kart->isEliminated() || kart == m_owner ||
kart->isInvulnerable() ||
kart->getKartAnimation() ) continue;
// Don't hit teammates in team world
if (world->hasTeam() &&
world->getKartTeam(kart->getWorldKartId()) ==
world->getKartTeam(m_owner->getWorldKartId()))
continue;
btTransform t=kart->getTrans();
Vec3 delta = t.getOrigin()-trans_projectile.getOrigin();
// the Y distance is added again because karts above or below should//
// not be prioritized when aiming
float distance2 = delta.length2() + std::abs(t.getOrigin().getY()
- trans_projectile.getOrigin().getY())*2;
if(inFrontOf != NULL)
{
// Ignore karts behind the current one
Vec3 to_target = kart->getXYZ() - inFrontOf->getXYZ();
const float distance = to_target.length();
if(distance > 50) continue; // kart too far, don't aim at it
btTransform trans = inFrontOf->getTrans();
// get heading=trans.getBasis*(0,0,1) ... so save the multiplication:
Vec3 direction(trans.getBasis().getColumn(2));
// Originally it used angle = to_target.angle( backwards ? -direction : direction );
// but sometimes due to rounding errors we get an acos(x) with x>1, causing
// an assertion failure. So we remove the whole acos() test here and copy the
// code from to_target.angle(...)
Vec3 v = backwards ? -direction : direction;
float s = sqrt(v.length2() * to_target.length2());
float c = to_target.dot(v)/s;
// Original test was: fabsf(acos(c))>1, which is the same as
// c<cos(1) (acos returns values in [0, pi] anyway)
if(c<0.54) continue;
}
if(distance2 < *minDistSquared)
{
*minDistSquared = distance2;
*minKart = kart;
*minDelta = delta;
}
} // for i<getNumKarts
} // getClosestKart
//-----------------------------------------------------------------------------
/** Returns information on the parameters needed to hit a target kart moving
* at constant velocity and direction for a given speed in the XZ-plane.
* \param origin Location of the kart shooting the item.
* \param target_kart Which kart to target.
* \param item_xz_speed Speed of the item projected in XZ plane.
* \param gravity The gravity used for this item.
* \param forw_offset How far ahead of the kart the item is shot (so that
* the item does not originate inside of the shooting kart.
* \param fire_angle Returns the angle to fire the item at.
* \param up_velocity Returns the upwards velocity to use for the item.
*/
void Flyable::getLinearKartItemIntersection (const Vec3 &origin,
const AbstractKart *target_kart,
float item_XZ_speed,
float gravity, float forw_offset,
float *fire_angle,
float *up_velocity)
{
// Transform the target into the firing kart's frame of reference
btTransform inv_trans = m_owner->getTrans().inverse();
Vec3 relative_target_kart_loc = inv_trans(target_kart->getXYZ());
// Find the direction target is moving in
btTransform trans = target_kart->getTrans();
Vec3 target_direction(trans.getBasis().getColumn(2));
// Now rotate it to the firing kart's frame of reference
btQuaternion inv_rotate = inv_trans.getRotation();
target_direction =
target_direction.rotate(inv_rotate.getAxis(), inv_rotate.getAngle());
// Now we try to find the angle to aim at to hit the target.
// Warning : Funky math stuff going on below. To understand, see answer by
// Jeffrey Hantin here :
// http://stackoverflow.com/questions/2248876/2d-game-fire-at-a-moving-target-by-predicting-intersection-of-projectile-and-u
float target_x_speed = target_direction.getX()*target_kart->getSpeed();
float target_z_speed = target_direction.getZ()*target_kart->getSpeed();
float target_y_speed = target_direction.getY()*target_kart->getSpeed();
float a = (target_x_speed*target_x_speed) + (target_z_speed*target_z_speed) -
(item_XZ_speed*item_XZ_speed);
float b = 2 * (target_x_speed * (relative_target_kart_loc.getX())
+ target_z_speed * (relative_target_kart_loc.getZ()));
float c = relative_target_kart_loc.getX()*relative_target_kart_loc.getX()
+ relative_target_kart_loc.getZ()*relative_target_kart_loc.getZ();
float discriminant = b*b - 4 * a*c;
if (discriminant < 0) discriminant = 0;
float t1 = (-b + sqrt(discriminant)) / (2 * a);
float t2 = (-b - sqrt(discriminant)) / (2 * a);
float time;
if (t1 >= 0 && t1<t2) time = t1;
else time = t2;
//createPhysics offset
time -= forw_offset / item_XZ_speed;
float aimX = time*target_x_speed + relative_target_kart_loc.getX();
float aimZ = time*target_z_speed + relative_target_kart_loc.getZ();
assert(time!=0);
float angle = atan2f(aimX, aimZ);
*fire_angle = angle;
// Now find the up_velocity. This is an application of newton's equation.
*up_velocity = (0.5f * time * gravity) + (relative_target_kart_loc.getY() / time)
+ ( target_y_speed);
} // getLinearKartItemIntersection
//-----------------------------------------------------------------------------
void Flyable::setAnimation(AbstractKartAnimation *animation)
{
if (animation)
{
assert(m_animation == NULL);
// add or removeBody currently breaks animation rewind
moveToInfinity(/*set_moveable_trans*/false);
}
else // animation = NULL
{
assert(m_animation != NULL);
m_body->setWorldTransform(getTrans());
}
m_animation = animation;
} // addAnimation
//-----------------------------------------------------------------------------
/** Called once per rendered frame. It is used to only update any graphical
* effects.
* \param dt Time step size (since last call).
*/
void Flyable::updateGraphics(float dt)
{
Moveable::updateSmoothedGraphics(dt);
Moveable::updateGraphics();
} // updateGraphics
//-----------------------------------------------------------------------------
/** Updates this flyable. It calls Moveable::update. If this function returns
* true, the flyable will be deleted by the projectile manager.
* \param dt Time step size.
* \returns True if this object can be deleted.
*/
bool Flyable::updateAndDelete(int ticks)
{
if (!m_has_server_state)
return false;
if (hasAnimation())
{
m_animation->update(ticks);
Moveable::update(ticks);
// Move the physical body to infinity so it doesn't interact with
// game objects (for easier rewind)
moveToInfinity(/*set_moveable_trans*/false);
return false;
} // if animation
// 32767 for max m_ticks_since_thrown so the last bit for animation save
if (m_ticks_since_thrown < 32767)
m_ticks_since_thrown += ticks;
if(m_max_lifespan > -1 && (int)m_ticks_since_thrown > m_max_lifespan)
hit(NULL);
if(m_has_hit_something) return true;
// Round values in network for better synchronization
if (NetworkConfig::get()->roundValuesNow())
CompressNetworkBody::compress(m_body.get(), m_motion_state.get());
// Save the compressed values if done in client
Moveable::update(ticks);
//Vec3 xyz=getBody()->getWorldTransform().getOrigin();
const Vec3 &xyz=getXYZ();
// Check if the flyable is outside of the track. If so, explode it.
const Vec3 *min, *max;
Track::getCurrentTrack()->getAABB(&min, &max);
// I have seen that the bullet AABB can be slightly different from the
// one computed here - I assume due to minor floating point errors
// (e.g. 308.25842 instead of 308.25845). To avoid a crash with a bullet
// assertion (see bug 3058932) I add an epsilon here - but admittedly
// that does not really explain the bullet crash, since bullet tests
// against its own AABB, and should therefore not cause the assertion.
// But since we couldn't reproduce the problem, and the epsilon used
// here does not hurt, I'll leave it in.
float eps = 0.1f;
assert(!std::isnan(xyz.getX()));
assert(!std::isnan(xyz.getY()));
assert(!std::isnan(xyz.getZ()));
if(xyz[0]<(*min)[0]+eps || xyz[2]<(*min)[2]+eps || xyz[1]<(*min)[1]+eps ||
xyz[0]>(*max)[0]-eps || xyz[2]>(*max)[2]-eps || xyz[1]>(*max)[1]-eps )
{
hit(NULL); // flyable out of track boundary
return true;
}
if (m_do_terrain_info)
{
Vec3 towards = MiniGLM::decompressVector3(m_compressed_gravity_vector);
// Add the position offset so that the flyable can adjust its position
// (usually to do the raycast from a slightly higher position to avoid
// problems finding the terrain in steep uphill sections).
// Towards is a unit vector. so we can multiply -towards to offset the
// position by one unit.
TerrainInfo::update(xyz + m_position_offset*(-towards), towards);
// Make flyable anti-gravity when the it's projected on such surface
const Material* m = TerrainInfo::getMaterial();
if (m && m->hasGravity())
{
getBody()->setGravity(TerrainInfo::getNormal() * -70.0f);
}
else
{
getBody()->setGravity(Vec3(0, 1, 0) * -70.0f);
}
m_compressed_gravity_vector = MiniGLM::compressVector3(
Vec3(m_body->getGravity().normalized()).toIrrVector());
}
if(m_adjust_up_velocity)
{
float hat = (xyz - getHitPoint()).length();
// Use the Height Above Terrain to set the Z velocity.
// HAT is clamped by min/max height. This might be somewhat
// unphysical, but feels right in the game.
float delta = m_average_height - std::max(std::min(hat, m_max_height),
m_min_height);
Vec3 v = getVelocity();
assert(!std::isnan(v.getX()));
assert(!std::isnan(v.getX()));
assert(!std::isnan(v.getX()));
float heading = atan2f(v.getX(), v.getZ());
assert(!std::isnan(heading));
float pitch = getTerrainPitch(heading);
float vel_up = m_force_updown*(delta);
if (hat < m_max_height) // take into account pitch of surface
vel_up += v.length_2d()*tanf(pitch);
assert(!std::isnan(vel_up));
v.setY(vel_up);
setVelocity(v);
} // if m_adjust_up_velocity
return false;
} // updateAndDelete
// ----------------------------------------------------------------------------
/** Returns true if the item hit the kart who shot it (to avoid that an item
* that's too close to the shooter hits the shooter).
* \param kart Kart who was hit.
*/
bool Flyable::isOwnerImmunity(const AbstractKart* kart_hit) const
{
return m_owner_has_temporary_immunity &&
kart_hit == m_owner &&
(int)m_ticks_since_thrown < stk_config->time2Ticks(2.0f);
} // isOwnerImmunity
// ----------------------------------------------------------------------------
/** Callback from the physics in case that a kart or physical object is hit.
* \param kart The kart hit (NULL if no kart was hit).
* \param object The object that was hit (NULL if none).
* \return True if there was actually a hit (i.e. not owner, and target is
* not immune), false otherwise.
*/
bool Flyable::hit(AbstractKart *kart_hit, PhysicalObject* object)
{
if (!m_has_server_state || hasAnimation())
return false;
// the owner of this flyable should not be hit by his own flyable
if(isOwnerImmunity(kart_hit)) return false;
m_has_hit_something=true;
return true;
} // hit
// ----------------------------------------------------------------------------
/** Creates the explosion physical effect, i.e. pushes the karts and ph
* appropriately. The corresponding visual/sfx needs to be added manually!
* \param kart_hit If non-NULL a kart that was directly hit.
* \param object If non-NULL a physical item that was hit directly.
* \param secondary_hits True if items that are not directly hit should
* also be affected.
*/
void Flyable::explode(AbstractKart *kart_hit, PhysicalObject *object,
bool secondary_hits)
{
// Apply explosion effect
// ----------------------
World *world = World::getWorld();
for ( unsigned int i = 0 ; i < world->getNumKarts() ; i++ )
{
AbstractKart *kart = world->getKart(i);
// Don't explode teammates in team world
if (world->hasTeam() &&
world->getKartTeam(kart->getWorldKartId()) ==
world->getKartTeam(m_owner->getWorldKartId()))
continue;
if (kart->isGhostKart()) continue;
// If no secondary hits should be done, only hit the
// direct hit kart.
if(!secondary_hits && kart!=kart_hit)
continue;
// Handle the actual explosion. The kart that fired a flyable will
// only be affected if it's a direct hit. This allows karts to use
// rockets on short distance.
if( (m_owner!=kart || m_owner==kart_hit) && !kart->getKartAnimation())
{
// The explosion animation will register itself with the kart
// and will free it later.
ExplosionAnimation::create(kart, getXYZ(), kart==kart_hit);
if (kart == kart_hit)
{
world->kartHit(kart->getWorldKartId(),
m_owner->getWorldKartId());
if (m_owner->getController()->canGetAchievements())
{
if (m_owner->getWorldKartId() != kart->getWorldKartId())
PlayerManager::addKartHit(kart->getWorldKartId());
PlayerManager::increaseAchievement(AchievementsStatus::ALL_HITS, 1);
if (RaceManager::get()->isLinearRaceMode())
PlayerManager::increaseAchievement(AchievementsStatus::ALL_HITS_1RACE, 1);
}
// Rumble!
Controller* controller = kart->getController();
if (controller && controller->isLocalPlayerController())
{
controller->rumble(0, 0.8f, 500);
}
}
}
}
Track::getCurrentTrack()->handleExplosion(getXYZ(), object,secondary_hits);
} // explode
// ----------------------------------------------------------------------------
/** Returns the hit effect object to use when this objects hits something.
* \returns The hit effect object, or NULL if no hit effect should be played.
*/
HitEffect* Flyable::getHitEffect() const
{
if (GUIEngine::isNoGraphics())
return NULL;
return m_deleted_once ? NULL :
new Explosion(getXYZ(), "explosion", "explosion_cake.xml");
} // getHitEffect
// ----------------------------------------------------------------------------
unsigned int Flyable::getOwnerId()
{
return m_owner->getWorldKartId();
} // getOwnerId
// ----------------------------------------------------------------------------
/** It's called when undoing the creation or destruction of flyables, so that
* it will not affected the current game, and it will be deleted in
* computeError.
* \param set_moveable_trans If true, set m_transform in moveable, so the
* graphical node will have the same transform, otherwise only the physical
* body will be moved to infinity
*/
void Flyable::moveToInfinity(bool set_moveable_trans)
{
const Vec3 *min, *max;
Track::getCurrentTrack()->getAABB(&min, &max);
btTransform t = m_body->getWorldTransform();
t.setOrigin(*max * 2.0f);
m_body->proceedToTransform(t);
if (set_moveable_trans)
setTrans(t);
else
m_motion_state->setWorldTransform(t);
} // moveToInfinity
// ----------------------------------------------------------------------------
BareNetworkString* Flyable::saveState(std::vector<std::string>* ru)
{
if (m_has_hit_something)
return NULL;
ru->push_back(getUniqueIdentity());
BareNetworkString* buffer = new BareNetworkString();
uint16_t ticks_since_thrown_animation = (m_ticks_since_thrown & 32767) |
(hasAnimation() ? 32768 : 0);
buffer->addUInt16(ticks_since_thrown_animation);
if (m_do_terrain_info)
buffer->addUInt32(m_compressed_gravity_vector);
if (hasAnimation())
m_animation->saveState(buffer);
else
{
CompressNetworkBody::compress(
m_body.get(), m_motion_state.get(), buffer);
}
return buffer;
} // saveState
// ----------------------------------------------------------------------------
void Flyable::restoreState(BareNetworkString *buffer, int count)
{
uint16_t ticks_since_thrown_animation = buffer->getUInt16();
bool has_animation_in_state =
(ticks_since_thrown_animation >> 15 & 1) == 1;
if (m_do_terrain_info)
m_compressed_gravity_vector = buffer->getUInt32();
if (has_animation_in_state)
{
// At the moment we only have cannon animation for rubber ball
if (!m_animation)
{
try
{
CannonAnimation* ca = new CannonAnimation(this, buffer);
setAnimation(ca);
}
catch (const KartAnimationCreationException& kace)
{
Log::error("Flyable", "Kart animation creation error: %s",
kace.what());
buffer->skip(kace.getSkippingOffset());
}
}
else
m_animation->restoreState(buffer);
}
else
{
if (hasAnimation())
{
// Delete unconfirmed animation, destructor of cannon animation
// will set m_animation to null
delete m_animation;
}
CompressNetworkBody::decompress(
buffer, m_body.get(), m_motion_state.get());
m_transform = m_body->getWorldTransform();
}
m_ticks_since_thrown = ticks_since_thrown_animation & 32767;
m_has_server_state = true;
m_has_hit_something = false;
} // restoreState
// ----------------------------------------------------------------------------
void Flyable::addForRewind(const std::string& uid)
{
Rewinder::setUniqueIdentity(uid);
Rewinder::rewinderAdd();
} // addForRewind
// ----------------------------------------------------------------------------
void Flyable::saveTransform()
{
// It will be overwritten in restoreState (so it's confirmed by server) or
// onFireFlyable (before the game state all flyables are assumed to be
// sucessfully created)
moveToInfinity();
m_has_server_state = false;
m_last_deleted_ticks = -1;
} // saveTransform
// ----------------------------------------------------------------------------
void Flyable::computeError()
{
// Remove the flyable if it doesn't exist or failed to create on server
// For each saveTransform it will call moveToInfinity so the invalid
// flyables won't affect the current game
const int state_ticks = RewindManager::get()->getLatestConfirmedState();
if (!m_has_server_state && (m_last_deleted_ticks == -1 ||
state_ticks > m_last_deleted_ticks))
{
const std::string& uid = getUniqueIdentity();
Log::debug("Flyable", "Flyable %s by %s created at %d "
"doesn't exist on server, remove it.",
typeid(*this).name(), StringUtils::wideToUtf8(
m_owner->getController()->getName()).c_str(), m_created_ticks);
ProjectileManager::get()->removeByUID(uid);
}
} // computeError
// ----------------------------------------------------------------------------
/** Call when the item is (re-)fired (during rewind if needed) by
* projectile_manager. */
void Flyable::onFireFlyable()
{
if (m_animation)
{
m_animation->handleResetRace();
delete m_animation;
m_animation = NULL;
}
m_ticks_since_thrown = 0;
m_has_hit_something = false;
m_has_server_state = true;
m_deleted_once = false;
m_last_deleted_ticks = -1;
// Reset the speed each time for (re-)firing, so subclass access with same
// initial value
m_speed = m_st_speed[m_type];
m_extend = m_st_extend[m_type];
m_max_height = m_st_max_height[m_type];
m_min_height = m_st_min_height[m_type];
m_average_height = (m_min_height + m_max_height) / 2.0f;
m_force_updown = m_st_force_updown[m_type];
} // onFireFlyable
// ----------------------------------------------------------------------------
/* Call when deleting the flyable locally and save the deleted world ticks. */
void Flyable::onDeleteFlyable()
{
m_deleted_once = true;
m_last_deleted_ticks = World::getWorld()->getTicksSinceStart();
m_has_server_state = false;
moveToInfinity();
} // onDeleteFlyable
// ----------------------------------------------------------------------------
/* Make specifc sfx lower volume if needed in splitscreen multiplayer. */
void Flyable::fixSFXSplitscreen(SFXBase* sfx)
{
if (sfx && RaceManager::get()->getNumLocalPlayers() > 1)
sfx->setVolume(1.0f / (float)RaceManager::get()->getNumLocalPlayers());
} // fixSFXSplitscreen
/* EOF */