// // SuperTuxKart - a fun racing game with go-kart // Copyright (C) 2011-2015 Joerg Henrichs // // 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/rubber_ball.hpp" #include "audio/sfx_base.hpp" #include "audio/sfx_manager.hpp" #include "config/stk_config.hpp" #include "config/user_config.hpp" #include "io/xml_node.hpp" #include "items/attachment.hpp" #include "items/projectile_manager.hpp" #include "karts/abstract_kart.hpp" #include "modes/linear_world.hpp" #include "network/rewind_manager.hpp" #include "physics/btKart.hpp" #include "physics/triangle_mesh.hpp" #include "tracks/check_manager.hpp" #include "tracks/drive_graph.hpp" #include "tracks/drive_node.hpp" #include "tracks/track.hpp" #include "utils/log.hpp" //TODO: remove after debugging is done float RubberBall::m_st_interval; float RubberBall::m_st_min_interpolation_distance; float RubberBall::m_st_squash_duration; float RubberBall::m_st_squash_slowdown; float RubberBall::m_st_target_distance; float RubberBall::m_st_target_max_angle; int RubberBall::m_st_delete_ticks; float RubberBall::m_st_max_height_difference; float RubberBall::m_st_fast_ping_distance; float RubberBall::m_st_early_target_factor; int RubberBall::m_next_id = 0; // Debug only, so that we can get a feel on how well balls are aiming etc. #undef PRINT_BALL_REMOVE_INFO RubberBall::RubberBall(AbstractKart *kart) : Flyable(kart, PowerupManager::POWERUP_RUBBERBALL, 0.0f /* mass */), TrackSector() { // For debugging purpose: pre-fix each debugging line with the id of // the ball so that it's easy to collect all debug output for one // particular ball only. m_next_id++; m_id = m_next_id; // Don't let Flyable update the terrain information, since this object // has to do it earlier than that. setDoTerrainInfo(false); float forw_offset = 0.5f*kart->getKartLength() + m_extend.getZ()*0.5f+5.0f; createPhysics(forw_offset, btVector3(0.0f, 0.0f, m_speed*2), new btSphereShape(0.5f*m_extend.getY()), -70.0f, btVector3(.0f,.0f,.0f) /*gravity*/, true /*rotates*/); // Do not adjust the up velocity setAdjustUpVelocity(false); m_max_lifespan = stk_config->time2Ticks(9999); m_target = NULL; m_ping_sfx = SFXManager::get()->createSoundSource("ball_bounce"); m_owner_init_pos = m_owner->getXYZ(); m_init_pos = getXYZ(); additionalPhysicsProperties(); CheckManager::get()->addFlyableToCannons(this); } // RubberBall // ---------------------------------------------------------------------------- void RubberBall::additionalPhysicsProperties() { setXYZ(m_init_pos); m_aiming_at_target = false; m_fast_ping = false; // At the start the ball aims at quads till it gets close enough to the // target: m_height_timer = 0.0f; m_interval = m_st_interval; m_current_max_height = m_max_height; // Just init the previoux coordinates with some value that's not getXYZ() m_previous_xyz = m_owner_init_pos; m_previous_height = 2.0f; // // A negative value indicates that the timer is not active m_delete_ticks = -1; m_tunnel_count = 0; LinearWorld *world = dynamic_cast(World::getWorld()); // FIXME: what does the rubber ball do in case of battle mode?? if(!world) return; computeTarget(); // initialises the current graph node TrackSector::update(getXYZ()); const Vec3& normal = DriveGraph::get()->getNode(getCurrentGraphNode())->getNormal(); TerrainInfo::update(getXYZ(), -normal); initializeControlPoints(m_owner_init_pos); } // additionalPhysicsProperties // ---------------------------------------------------------------------------- /** Destructor, removes any playing sfx. */ RubberBall::~RubberBall() { if(m_ping_sfx->getStatus()==SFXBase::SFX_PLAYING) m_ping_sfx->stop(); m_ping_sfx->deleteSFX(); CheckManager::get()->removeFlyableFromCannons(this); } // ~RubberBall // ---------------------------------------------------------------------------- /** Sets up the control points for the interpolation. The parameter contains * the coordinates of the first control points (i.e. a control point that * influences the direction the ball is flying only, not the actual * coordinates - see details about Catmull-Rom splines). This function will * then set the 2nd control point to be the current coordinates of the ball, * and find two more appropriate control points for a smooth movement. * \param xyz Coordinates of the first control points. */ void RubberBall::initializeControlPoints(const Vec3 &xyz) { m_control_points[0] = xyz; m_control_points[1] = getXYZ(); m_last_aimed_graph_node = getSuccessorToHitTarget(getCurrentGraphNode()); // This call defined m_control_points[3], but also sets a new // m_last_aimed_graph_node, which is further away from the current point, // which avoids the problem that the ball might go too quickly to the // left or right when firing the ball off track. getNextControlPoint(); m_control_points[2] = DriveGraph::get()->getNode(m_last_aimed_graph_node)->getCenter(); // This updates m_last_aimed_graph_node, and sets m_control_points[3] getNextControlPoint(); m_length_cp_1_2 = (m_control_points[2]-m_control_points[1]).length(); m_t = 0; m_t_increase = m_speed/m_length_cp_1_2; } // initializeControlPoints // ---------------------------------------------------------------------------- void RubberBall::setAnimation(AbstractKartAnimation *animation) { if (!animation) { initializeControlPoints(getXYZ()); m_height_timer = 0; if (RewindManager::get()->useLocalEvent()) { std::shared_ptr rb = getShared(); btTransform cur_trans = getTrans(); Vec3 cur_previous_xyz = m_previous_xyz; RewindManager::get()->addRewindInfoEventFunction(new RewindInfoEventFunction(World::getWorld()->getTicksSinceStart(), /*undo_function*/[rb]() { rb->m_undo_creation = true; rb->moveToInfinity(); }, /*replay_function*/[rb, cur_trans, cur_previous_xyz]() { rb->m_undo_creation = false; rb->m_body->setWorldTransform(cur_trans); rb->m_motion_state->setWorldTransform(cur_trans); rb->m_body->setInterpolationWorldTransform(cur_trans); rb->m_previous_xyz = cur_previous_xyz; rb->initializeControlPoints(cur_trans.getOrigin()); rb->m_height_timer = 0; })); } } Flyable::setAnimation(animation); } // setAnimation // ---------------------------------------------------------------------------- /** Determines the first kart that is still in the race. */ void RubberBall::computeTarget() { LinearWorld *world = dynamic_cast(World::getWorld()); for(unsigned int p = race_manager->getFinishedKarts()+1; p < world->getNumKarts()+1; p++) { m_target = world->getKartAtPosition(p); if(!m_target->isEliminated() && !m_target->hasFinishedRace()) { // If the firing kart itself is the first kart (that is // still driving), prepare to remove the rubber ball if(m_target==m_owner && m_delete_ticks < 0) { #ifdef PRINT_BALL_REMOVE_INFO Log::debug("[RubberBall]", "ball %d removed because owner is target.", m_id); #endif m_delete_ticks = m_st_delete_ticks; } return; } } // for p > num_karts // This means it must be the end-animation phase. Now just // aim at the owner (the ball is unlikely to hit it), and // this will trigger the usage of the delete time in updateAndDelete #ifdef PRINT_BALL_REMOVE_INFO Log::debug("[RubberBall]" "ball %d removed because no more active target.", m_id); #endif m_delete_ticks = m_st_delete_ticks; m_target = m_owner; } // computeTarget // ---------------------------------------------------------------------------- /** Determines the successor of a graph node. For now always a successor on * the main driveline is returned, but a more sophisticated implementation * might check if the target kart is on a shortcut, and select the path so * that it will get to the target even in this case. * \param node_index The node for which the successor is searched. * \param dist If not NULL a pointer to a float. The distance between * node_index and the selected successor is added to this float. * \return The node index of a successor node. */ unsigned int RubberBall::getSuccessorToHitTarget(unsigned int node_index, float *dist) { int succ = 0; LinearWorld *lin_world = dynamic_cast(World::getWorld()); unsigned int sect = lin_world->getSectorForKart(m_target); succ = DriveGraph::get()->getNode(node_index)->getSuccessorToReach(sect); if(dist) *dist += DriveGraph::get()->getNode(node_index) ->getDistanceToSuccessor(succ); return DriveGraph::get()->getNode(node_index)->getSuccessor(succ); } // getSuccessorToHitTarget // ---------------------------------------------------------------------------- /** Determines the next control points to aim at. The control points must not * be too close to each other (since otherwise the interpolation is still * not smooth enough), so keep on picking graph nodes till the distance * between the currently aimed at graph node and the next one is above a * certain threshold. It uses getSuccessorToHitTarget to determine which * graph node to select. */ void RubberBall::getNextControlPoint() { // Accumulate the distance between the current last graph node point // and the next one. This is used to approximate the length of the // spline between the control points. float dist=0; float f = DriveGraph::get()->getDistanceFromStart(m_last_aimed_graph_node); int next = getSuccessorToHitTarget(m_last_aimed_graph_node, &dist); float d = DriveGraph::get()->getDistanceFromStart(next)-f; while(d=0) { next = getSuccessorToHitTarget(next, &dist); d = DriveGraph::get()->getDistanceFromStart(next)-f; } m_last_aimed_graph_node = next; m_length_cp_2_3 = dist; const DriveNode* dn = DriveGraph::get()->getNode(m_last_aimed_graph_node); m_control_points[3] = dn->getCenter(); } // getNextControlPoint // ---------------------------------------------------------------------------- /** Initialises this object with data from the power.xml file (this is a static * function). * \param node XML Node * \param rubberball The rubber ball mesh */ void RubberBall::init(const XMLNode &node, scene::IMesh *rubberball) { m_st_interval = 1.0f; m_st_squash_duration = 3.0f; m_st_squash_slowdown = 0.5f; m_st_min_interpolation_distance = 30.0f; m_st_target_distance = 50.0f; m_st_target_max_angle = 25.0f; m_st_delete_ticks = stk_config->time2Ticks(10.0f); m_st_max_height_difference = 10.0f; m_st_fast_ping_distance = 50.0f; m_st_early_target_factor = 1.0f; if(!node.get("interval", &m_st_interval)) Log::warn("powerup", "No interval specified for rubber ball."); if(!node.get("squash-duration", &m_st_squash_duration)) Log::warn("powerup", "No squash-duration specified for rubber ball."); if(!node.get("squash-slowdown", &m_st_squash_slowdown)) Log::warn("powerup", "No squash-slowdown specified for rubber ball."); if(!node.get("min-interpolation-distance", &m_st_min_interpolation_distance)) Log::warn("powerup", "No min-interpolation-distance specified " "for rubber ball."); if(!node.get("target-distance", &m_st_target_distance)) Log::warn("powerup", "No target-distance specified for rubber ball."); float f; if(!node.get("delete-time", &f)) Log::warn("powerup", "No delete-time specified for rubber ball."); m_st_delete_ticks = stk_config->time2Ticks(f); if(!node.get("target-max-angle", &m_st_target_max_angle)) Log::warn("powerup", "No target-max-angle specified for rubber ball."); m_st_target_max_angle *= DEGREE_TO_RAD; if(!node.get("max-height-difference", &m_st_max_height_difference)) Log::warn("powerup", "No max-height-difference specified for rubber ball."); if(!node.get("fast-ping-distance", &m_st_fast_ping_distance)) Log::warn("powerup", "No fast-ping-distance specified for rubber ball."); if(m_st_fast_ping_distance < m_st_target_distance) Log::warn("powerup", "Ping-distance is smaller than target distance.\n" "That should not happen, but is ignored for now."); if(!node.get("early-target-factor", &m_st_early_target_factor)) Log::warn("powerup", "No early-target-factor specified for rubber ball."); Flyable::init(node, rubberball, PowerupManager::POWERUP_RUBBERBALL); } // init // ---------------------------------------------------------------------------- /** Updates the rubber ball. * \param dt Time step size. * \returns True if the rubber ball should be removed. */ bool RubberBall::updateAndDelete(int ticks) { LinearWorld *world = dynamic_cast(World::getWorld()); // FIXME: what does the rubber ball do in case of battle mode?? if(!world) return true; if(m_delete_ticks>0) { m_delete_ticks -= 1; if(m_delete_ticks<=0) { hit(NULL); #ifdef PRINT_BALL_REMOVE_INFO Log::debug("[RubberBall]", "ball %d deleted.", m_id); #endif return true; } } if (hasAnimation()) { // Flyable will call update() of the animation to // update the ball's position. m_previous_xyz = getXYZ(); return Flyable::updateAndDelete(ticks); } // Update the target in case that the first kart was overtaken (or has // finished the race). computeTarget(); updateDistanceToTarget(); // Determine the new position. This new position is only temporary, // since it still needs to be adjusted for the height of the terrain. Vec3 next_xyz; if(m_aiming_at_target) moveTowardsTarget(&next_xyz, ticks); else interpolate(&next_xyz, ticks); // If the ball is close to the ground, we have to start the raycast // slightly higher (to avoid that the ball tunnels through the floor). // But if the ball is close to the ceiling of a tunnel and we would // start the raycast slightly higher, the ball might end up on top // of the ceiling. // The ball is considered close to the ground if the height above the // terrain is less than half the current maximum height. bool close_to_ground = 2.0*m_previous_height < m_current_max_height; float vertical_offset = close_to_ground ? 4.0f : 2.0f; // Update height of terrain (which isn't done as part of // Flyable::update for rubber balls. TerrainInfo::update(next_xyz + getNormal()*vertical_offset, -getNormal()); m_height_timer += stk_config->ticks2Time(ticks); float height = updateHeight()+m_extend.getY()*0.5f; if(UserConfigParams::logFlyable()) Log::debug("[RubberBall]", "ball %d: %f %f %f height %f gethot %f terrain %d aim %d", m_id, next_xyz.getX(), next_xyz.getY(), next_xyz.getZ(), height, getHoT(), isOnRoad(), m_aiming_at_target); // No need to check for terrain height if the ball is low to the ground if(height > 0.5f) { float tunnel_height = getTunnelHeight(next_xyz, vertical_offset) - m_extend.getY(); // If the current height of ball (above terrain) is higher than the // tunnel height then set adjust max height and compute new height again. // Else reset the max height. if (height > tunnel_height) { m_max_height = tunnel_height; height = updateHeight(); } else m_max_height = m_st_max_height[m_type]; } if(UserConfigParams::logFlyable()) Log::verbose("RubberBall", "newy2 %f gmth %f", height, getTunnelHeight(next_xyz,vertical_offset)); // Ball squashing: // =============== float scale = 1.0f; if (height < 1.0f * m_extend.getY()) scale = height / m_extend.getY(); #ifndef SERVER_ONLY if (!RewindManager::get()->isRewinding()) m_node->setScale(core::vector3df(1.0f, scale, 1.0f)); #endif next_xyz = getHitPoint() + getNormal() * (height * scale); m_previous_xyz = getXYZ(); m_previous_height = (getXYZ() - getHitPoint()).length(); setXYZ(next_xyz); if(checkTunneling()) return true; // Determine new distance along track TrackSector::update(next_xyz); return Flyable::updateAndDelete(ticks); } // updateAndDelete // ---------------------------------------------------------------------------- /** Moves the rubber ball in a straight line towards the target. This is used * once the rubber ball is close to its target. It restricts the angle by * which the rubber ball can change its direction per frame. * \param next_xyz The position the ball should move to. * \param ticks Number of physics steps - should be 1. */ void RubberBall::moveTowardsTarget(Vec3 *next_xyz, int ticks) { // If the rubber ball is already close to a target, i.e. aiming // at it directly, stop interpolating, instead fly straight // towards it. Vec3 diff = m_target->getXYZ() - getXYZ(); diff = diff - diff.dot(getNormal())*getNormal(); // Avoid potential division by zero if(diff.length2()==0) *next_xyz = getXYZ() - getNormal()*m_previous_height; else { float dt = stk_config->ticks2Time(ticks); *next_xyz = getXYZ() - getNormal()*m_previous_height + (dt*m_speed / diff.length())*diff; } // If ball is close to the target, then explode if (diff.length() < m_target->getKartLength()) hit((AbstractKart*)m_target); assert(!std::isnan((*next_xyz)[0])); assert(!std::isnan((*next_xyz)[1])); assert(!std::isnan((*next_xyz)[2])); } // moveTowardsTarget // ---------------------------------------------------------------------------- /** Uses Hermite splines (Catmull-Rom) to interpolate the position of the * ball between the control points. If the next point would be outside of * the spline between control_points[1] and [2], a new control point is * added. * \param next_xyz Returns the new position. * \param The time step size. */ void RubberBall::interpolate(Vec3 *next_xyz, int ticks) { // If we have reached or overshot the next control point, move to the // the next section of the spline float dt = stk_config->ticks2Time(ticks); m_t += m_t_increase * dt; if(m_t > 1.0f) { // Move the control points and estimated distance forward. for(unsigned int i=1; i<4; i++) m_control_points[i-1] = m_control_points[i]; m_length_cp_1_2 = m_length_cp_2_3; // This automatically sets m_control_points[3] getNextControlPoint(); m_t_increase = m_speed/m_length_cp_1_2; m_t -= 1.0f; } *next_xyz = 0.5f * ((- m_control_points[0] + 3*m_control_points[1] -3*m_control_points[2] + m_control_points[3] ) *m_t*m_t*m_t + ( 2*m_control_points[0] -5*m_control_points[1] +4*m_control_points[2] - m_control_points[3])*m_t*m_t + (- m_control_points[0] + m_control_points[2])*m_t + 2*m_control_points[1] ); assert(!std::isnan((*next_xyz)[0])); assert(!std::isnan((*next_xyz)[1])); assert(!std::isnan((*next_xyz)[2])); } // interpolate // ---------------------------------------------------------------------------- /** Checks if the line from the previous ball position to the new position * hits something, which indicates that the ball is tunneling through. If * this happens, the ball position is adjusted so that it is just before * the hit point. If tunneling happens four frames in a row the ball is * considered stuck and explodes (e.g. the ball might try to tunnel through * a wall to get to a 'close' target. In this case the ball would not * move much anymore and be stuck). * \return True if the ball tunneled often enough to be removed. */ bool RubberBall::checkTunneling() { const TriangleMesh &tm = Track::getCurrentTrack()->getTriangleMesh(); Vec3 hit_point; const Material *material; tm.castRay(m_previous_xyz, getXYZ(), &hit_point, &material); if(material) { // If there are three consecutive tunnelling m_tunnel_count++; if(m_tunnel_count > 3) { #ifdef PRINT_BALL_REMOVE_INFO Log::debug("[RubberBall]", "Ball %d nearly tunneled at %f %f %f -> %f %f %f", m_id, m_previous_xyz.getX(),m_previous_xyz.getY(), m_previous_xyz.getZ(), getXYZ().getX(),getXYZ().getY(),getXYZ().getZ()); #endif hit(NULL); return true; } // In case of a hit, move the hit point towards the // previous point by the radius of the ball --> this // point will just allow the ball to avoid tunneling. Vec3 diff = m_previous_xyz - hit_point; hit_point += diff * (1.1f*m_extend.getY()/diff.length()); setXYZ(hit_point); return false; } else m_tunnel_count = 0; return false; } // checkTunneling // ---------------------------------------------------------------------------- /** Updates the height of the rubber ball, and if necessary also adjusts the * maximum height of the ball depending on distance from the target. The * height is decreased when the ball is getting closer to the target so it * hops faster and faster. This function modifies m_current_max_height. * \return Returns the new height of the ball. */ float RubberBall::updateHeight() { // When the ball hits the floor, we adjust maximum height and // interval so that the ball bounces faster when it is getting // closer to the target. if(m_height_timer>m_interval) { m_height_timer -= m_interval; if (m_ping_sfx->getStatus()!=SFXBase::SFX_PLAYING && !RewindManager::get()->isRewinding()) { m_ping_sfx->setPosition(getXYZ()); m_ping_sfx->play(); } if(m_fast_ping) { // Some experimental formulas m_current_max_height = 0.5f*sqrt(m_distance_to_target); // If the ball just missed the target, m_distance_to_target // can be huge (close to track length) due to the order in // which a lost target is detected. Avoid this by clamping // m_current_max_height. if(m_current_max_height>m_max_height) m_current_max_height = m_max_height; m_interval = m_current_max_height / 10.0f; // Avoid too small hops and esp. a division by zero if(m_interval<0.01f) m_interval = 0.1f; } else { // Reset the values in case that the ball was already trying // to get closer to the target, and then the target disappears // (e.g. is eliminated or finishes the race). m_interval = m_st_interval; m_current_max_height = m_max_height; } } // if m_height_timer > m_interval // Determine the height of the ball // ================================ // Consider f(x) = s * x*(x-m_intervall), which is a parabolic function // with f(0) = 0, f(m_intervall)=0. We then scale this function to // fulfill: f(m_intervall/2) = max_height, or: // f(m_interval/2) = s*(-m_interval^2)/4 = max_height // --> s = 4*max_height / -m_interval^2 float s = 4.0f * m_current_max_height / (-m_interval*m_interval); return m_height_timer * (m_height_timer-m_interval) * s; } // updateHeight // ---------------------------------------------------------------------------- /** When the ball is in a tunnel, this will return the tunnel height. * NOTE: When this function is called next_xyz is usually the interpolated point * on the track and not the ball's current location. Look at updateAndDelete(). * * \param vertical_offset A vertical offset which is added to the current * position in order to avoid hitting the track when doing a raycast up. * \returns The distance to the terrain element found by raycast in the up direction. If no terrain found, it returns 99990 */ float RubberBall::getTunnelHeight(const Vec3 &next_xyz, const float vertical_offset) const { const TriangleMesh &tm = Track::getCurrentTrack()->getTriangleMesh(); Vec3 from(next_xyz + vertical_offset*getNormal()); Vec3 to(next_xyz + 10000.0f*getNormal()); Vec3 hit_point; const Material *material; tm.castRay(from, to, &hit_point, &material); return (material) ? (hit_point - next_xyz).length() : 99999.f; } // getTunnelHeight // ---------------------------------------------------------------------------- /** Determines the distance to the target kart. If the target is close, the * rubber ball will switch from following the quad graph structure to * directly aim at the target. */ void RubberBall::updateDistanceToTarget() { const LinearWorld *world = dynamic_cast(World::getWorld()); float target_distance = world->getDistanceDownTrackForKart(m_target->getWorldKartId(), true); float ball_distance = getDistanceFromStart(/* account for checklines */ false); m_distance_to_target = target_distance - ball_distance; if(m_distance_to_target < 0) { m_distance_to_target += Track::getCurrentTrack()->getTrackLength(); } if(UserConfigParams::logFlyable()) Log::debug("[RubberBall]", "ball %d: target %f %f %f distance_2_target %f", m_id, m_target->getXYZ().getX(),m_target->getXYZ().getY(), m_target->getXYZ().getZ(),m_distance_to_target ); float height_diff = fabsf((m_target->getXYZ() - getXYZ()).dot(getNormal().normalized())); if(m_distance_to_target < m_st_fast_ping_distance && height_diff < m_st_max_height_difference) { m_fast_ping = true; } if(m_distance_to_target < m_st_target_distance && height_diff < m_st_max_height_difference) { m_aiming_at_target = true; return; } else if(m_aiming_at_target) { // It appears that we have lost the target. It was within // the target distance, and now it isn't. That means either // the original target escaped, or perhaps that there is a // new target. If the new distance is nearly the full track // length, assume that the rubber ball has overtaken the // original target, and start deleting it. if(m_distance_to_target > 0.9f * Track::getCurrentTrack()->getTrackLength()) { m_delete_ticks = m_st_delete_ticks; #ifdef PRINT_BALL_REMOVE_INFO Log::debug("[RubberBall]", "ball %d lost target (overtook?).", m_id); #endif } // Otherwise (target disappeared, e.g. has finished the race or // was eliminated) we have to reset the control points, since // it's likely that the ball is (after some time going directly // towards the target) far outside of the old control points. // We use the previous XYZ point to define the first control // point, which results in a smooth transition from aiming // directly at the target back to interpolating again. initializeControlPoints(m_previous_xyz); m_aiming_at_target = false; } return; } // updateDistanceToTarget // ---------------------------------------------------------------------------- /** Callback from the physics in case that a kart or object is hit. The rubber * ball will only be removed if it hits it target, all other karts it might * hit earlier will only be flattened. * \params kart The kart hit (NULL if no kart was hit). * \params object The object that was hit (NULL if none). * \returns True if */ bool RubberBall::hit(AbstractKart* kart, PhysicalObject* object) { #ifdef PRINT_BALL_REMOVE_INFO if(kart) Log::debug("[RuberBall]", "ball %d hit kart.", m_id); #endif if(kart && kart!=m_target) { // If the squashed kart has a bomb, explode it. if(kart->getAttachment()->getType()==Attachment::ATTACH_BOMB) { // make bomb explode kart->getAttachment()->update(10000); return false; } kart->setSquash(m_st_squash_duration, m_st_squash_slowdown); return false; } bool was_real_hit = Flyable::hit(kart, object); if(was_real_hit) { if(kart && kart->isShielded()) { kart->decreaseShieldTime(); } else explode(kart, object); } return was_real_hit; } // hit // ---------------------------------------------------------------------------- BareNetworkString* RubberBall::saveState(std::vector* ru) { BareNetworkString* buffer = Flyable::saveState(ru); buffer->addUInt32(m_last_aimed_graph_node); buffer->add(m_control_points[0]); buffer->add(m_control_points[1]); buffer->add(m_control_points[2]); buffer->add(m_control_points[3]); buffer->add(m_previous_xyz); buffer->addFloat(m_previous_height); buffer->addFloat(m_length_cp_1_2); buffer->addFloat(m_length_cp_2_3); buffer->addFloat(m_t); buffer->addFloat(m_t_increase); buffer->addFloat(m_interval); buffer->addUInt8(m_fast_ping ? 1 : 0); buffer->addFloat(m_distance_to_target); buffer->addFloat(m_height_timer); buffer->addUInt32(m_delete_ticks); buffer->addFloat(m_current_max_height); buffer->addUInt8(m_aiming_at_target ? 1 : 0); buffer->addUInt32(m_tunnel_count); TrackSector::saveState(buffer); return buffer; } // saveState // ---------------------------------------------------------------------------- void RubberBall::restoreState(BareNetworkString *buffer, int count) { Flyable::restoreState(buffer, count); m_last_aimed_graph_node = buffer->getUInt32(); m_control_points[0] = buffer->getVec3(); m_control_points[1] = buffer->getVec3(); m_control_points[2] = buffer->getVec3(); m_control_points[3] = buffer->getVec3(); m_previous_xyz = buffer->getVec3(); m_previous_height = buffer->getFloat(); m_length_cp_1_2 = buffer->getFloat(); m_length_cp_2_3 = buffer->getFloat(); m_t = buffer->getFloat(); m_t_increase = buffer->getFloat(); m_interval = buffer->getFloat(); m_fast_ping = buffer->getUInt8() == 1; m_distance_to_target = buffer->getFloat(); m_height_timer = buffer->getFloat(); m_delete_ticks = buffer->getUInt32(); m_current_max_height = buffer->getFloat(); m_aiming_at_target = buffer->getUInt8() == 1; m_tunnel_count = buffer->getUInt32(); TrackSector::rewindTo(buffer); } // restoreState