328 lines
12 KiB
C++
328 lines
12 KiB
C++
//
|
|
// 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.
|
|
|
|
// done: be able to squash karts
|
|
// TODO: use a proportional corrector for avoiding brutal movements
|
|
// TODO: make the swatter (and other items) appear and disappear progressively
|
|
// done: remove the maximum number of squashes
|
|
// TODO: add a swatter music
|
|
// TODO: be able to squash items
|
|
// TODO: move some constants to KartProperties, use all constants from KartProperties
|
|
|
|
#include "items/swatter.hpp"
|
|
#include "achievements/achievement_info.hpp"
|
|
#include "audio/sfx_base.hpp"
|
|
#include "audio/sfx_manager.hpp"
|
|
#include "config/player_manager.hpp"
|
|
#include "graphics/explosion.hpp"
|
|
#include "graphics/irr_driver.hpp"
|
|
#include "io/file_manager.hpp"
|
|
#include "items/attachment_manager.hpp"
|
|
#include "items/projectile_manager.hpp"
|
|
#include "karts/controller/controller.hpp"
|
|
#include "karts/explosion_animation.hpp"
|
|
#include "karts/kart_properties.hpp"
|
|
#include "modes/world.hpp"
|
|
#include "modes/soccer_world.hpp"
|
|
#include "karts/abstract_kart.hpp"
|
|
|
|
#define SWAT_POS_OFFSET core::vector3df(0.0, 0.2f, -0.4f)
|
|
#define SWAT_ANGLE_MIN 45
|
|
#define SWAT_ANGLE_MAX 135
|
|
#define SWAT_ANGLE_OFFSET (90.0f + 15.0f)
|
|
#define SWATTER_ANIMATION_SPEED 100.0f
|
|
|
|
/** Constructor: creates a swatter at a given attachment for a kart. If there
|
|
* was a bomb attached, it triggers the replace bomb animations.
|
|
* \param attachment The attachment instance where the swatter is attached to.
|
|
* \param kart The kart to which the swatter is attached.
|
|
* \param was_bomb True if the kart had a bomb as attachment.
|
|
* \param bomb_scene_node The scene node of the bomb (i.e. the previous
|
|
* attachment scene node).
|
|
*/
|
|
Swatter::Swatter(AbstractKart *kart, bool was_bomb,
|
|
scene::ISceneNode* bomb_scene_node)
|
|
: AttachmentPlugin(kart)
|
|
{
|
|
m_animation_phase = SWATTER_AIMING;
|
|
m_discard_now = false;
|
|
m_discard_timeout = 0.0f;
|
|
m_target = NULL;
|
|
m_closest_kart = NULL;
|
|
m_removing_bomb = was_bomb;
|
|
m_bomb_scene_node = bomb_scene_node;
|
|
m_swat_bomb_frame = 0.0f;
|
|
|
|
// Setup the node
|
|
m_scene_node = kart->getAttachment()->getNode();
|
|
m_scene_node->setPosition(SWAT_POS_OFFSET);
|
|
|
|
if (m_removing_bomb)
|
|
{
|
|
m_scene_node->setMesh(attachment_manager
|
|
->getMesh(Attachment::ATTACH_SWATTER_ANIM));
|
|
m_scene_node->setRotation(core::vector3df(0.0, -180.0, 0.0));
|
|
m_scene_node->setAnimationSpeed(0.9f);
|
|
m_scene_node->setCurrentFrame(0.0f);
|
|
m_scene_node->setLoopMode(false);
|
|
}
|
|
else
|
|
{
|
|
m_scene_node->setAnimationSpeed(0);
|
|
}
|
|
|
|
if (kart->getIdent() == "nolok")
|
|
m_swat_sound = SFXManager::get()->createSoundSource("hammer");
|
|
else
|
|
m_swat_sound = SFXManager::get()->createSoundSource("swatter");
|
|
} // Swatter
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Destructor, stops any playing sfx.
|
|
*/
|
|
Swatter::~Swatter()
|
|
{
|
|
if(m_bomb_scene_node)
|
|
{
|
|
irr_driver->removeNode(m_bomb_scene_node);
|
|
m_bomb_scene_node = NULL;
|
|
}
|
|
if (m_swat_sound)
|
|
{
|
|
m_swat_sound->deleteSFX();
|
|
}
|
|
} // ~Swatter
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Updates an armed swatter: it checks for any karts that are close enough
|
|
* and not invulnerable, it swats the kart.
|
|
* \param dt Time step size.
|
|
* \return True if the attachment should be discarded.
|
|
*/
|
|
bool Swatter::updateAndTestFinished(int ticks)
|
|
{
|
|
float dt = stk_config->ticks2Time(ticks);
|
|
if (!m_discard_now)
|
|
{
|
|
if (m_removing_bomb)
|
|
{
|
|
m_swat_bomb_frame += dt*25.0f;
|
|
m_scene_node->setRotation(core::vector3df(0.0, -180.0, 0.0));
|
|
|
|
m_scene_node->setCurrentFrame(m_swat_bomb_frame);
|
|
|
|
if (m_swat_bomb_frame >= 32.5f && m_bomb_scene_node != NULL)
|
|
{
|
|
m_bomb_scene_node->setPosition(m_bomb_scene_node
|
|
->getPosition() + core::vector3df(-dt*15.0f, 0.0f, 0.0f) );
|
|
m_bomb_scene_node->setRotation(m_bomb_scene_node
|
|
->getRotation() + core::vector3df(-dt*15.0f, 0.0f, 0.0f) );
|
|
}
|
|
|
|
if (m_swat_bomb_frame >= m_scene_node->getEndFrame())
|
|
{
|
|
return true;
|
|
}
|
|
else if (m_swat_bomb_frame >= 35)
|
|
{
|
|
if (m_bomb_scene_node != NULL)
|
|
{
|
|
irr_driver->removeNode(m_bomb_scene_node);
|
|
m_bomb_scene_node = NULL;
|
|
}
|
|
} // bom_frame > 35
|
|
|
|
return false;
|
|
} // if removing bomb
|
|
|
|
switch(m_animation_phase)
|
|
{
|
|
case SWATTER_AIMING:
|
|
{
|
|
chooseTarget();
|
|
pointToTarget();
|
|
if(!m_target || !m_closest_kart) break;
|
|
|
|
// Get the node corresponding to the joint at the center of the
|
|
// swatter (by swatter, I mean the thing hold in the hand, not
|
|
// the whole thing)
|
|
scene::ISceneNode* swatter_node =
|
|
m_scene_node->getJointNode("Swatter");
|
|
assert(swatter_node);
|
|
Vec3 swatter_pos = swatter_node->getAbsolutePosition();
|
|
float dist2 = (m_closest_kart->getXYZ()-swatter_pos).length2();
|
|
float min_dist2
|
|
= m_kart->getKartProperties()->getSwatterDistance();
|
|
|
|
if(dist2 < min_dist2)
|
|
{
|
|
// Start squashing
|
|
m_animation_phase = SWATTER_TO_TARGET;
|
|
|
|
// Setup the animation
|
|
m_scene_node->setCurrentFrame(0.0f);
|
|
m_scene_node->setLoopMode(false);
|
|
m_scene_node->setAnimationSpeed(SWATTER_ANIMATION_SPEED);
|
|
|
|
// Play swat sound
|
|
m_swat_sound->setPosition(swatter_pos);
|
|
m_swat_sound->play();
|
|
}
|
|
}
|
|
break;
|
|
case SWATTER_TO_TARGET:
|
|
{
|
|
pointToTarget();
|
|
const float middle_frame = m_scene_node->getEndFrame()/2.0f;
|
|
float current_frame = m_scene_node->getFrameNr();
|
|
|
|
// Did we just finish the first part of the movement?
|
|
if(current_frame >= middle_frame)
|
|
{
|
|
// Squash the karts and items around and
|
|
// change the current phase
|
|
squashThingsAround();
|
|
m_animation_phase = SWATTER_FROM_TARGET;
|
|
if (race_manager
|
|
->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES ||
|
|
race_manager
|
|
->getMinorMode()==RaceManager::MINOR_MODE_SOCCER)
|
|
{
|
|
// Remove swatter from kart in arena gameplay
|
|
// after one successful hit
|
|
m_discard_now = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SWATTER_FROM_TARGET:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
m_discard_timeout += dt;
|
|
|
|
return (m_discard_now && m_discard_timeout > 0.5f ? true : false);
|
|
} // updateAndTestFinished
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** When the animation ends, the swatter is ready again.
|
|
*/
|
|
void Swatter::onAnimationEnd()
|
|
{
|
|
m_animation_phase = SWATTER_AIMING;
|
|
} // onAnimationEnd
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Determine the nearest kart or item and update the current target
|
|
* accordingly.
|
|
*/
|
|
void Swatter::chooseTarget()
|
|
{
|
|
// TODO: for the moment, only handle karts...
|
|
const World* world = World::getWorld();
|
|
AbstractKart* closest_kart = NULL;
|
|
float min_dist2 = FLT_MAX;
|
|
|
|
for(unsigned int i=0; i<world->getNumKarts(); i++)
|
|
{
|
|
AbstractKart *kart = world->getKart(i);
|
|
// TODO: isSwatterReady(), isSquashable()?
|
|
if(kart->isEliminated() || kart==m_kart)
|
|
continue;
|
|
// don't squash an already hurt kart
|
|
if (kart->isInvulnerable() || kart->isSquashed())
|
|
continue;
|
|
|
|
const SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
|
|
if (sw)
|
|
{
|
|
// Don't hit teammates in soccer world
|
|
if (sw->getKartTeam(kart->getWorldKartId()) == sw
|
|
->getKartTeam(m_kart->getWorldKartId()))
|
|
continue;
|
|
}
|
|
|
|
float dist2 = (kart->getXYZ()-m_kart->getXYZ()).length2();
|
|
if(dist2<min_dist2)
|
|
{
|
|
min_dist2 = dist2;
|
|
closest_kart = kart;
|
|
}
|
|
}
|
|
m_target = closest_kart; // may be NULL
|
|
m_closest_kart = closest_kart;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** If there is a current target, point in its direction, otherwise adopt the
|
|
* default position. */
|
|
void Swatter::pointToTarget()
|
|
{
|
|
if(!m_target)
|
|
{
|
|
m_scene_node->setRotation(core::vector3df());
|
|
}
|
|
else
|
|
{
|
|
Vec3 swatter_to_target =
|
|
m_kart->getTrans().inverse()(m_target->getXYZ());
|
|
float dy = -swatter_to_target.getZ();
|
|
float dx = swatter_to_target.getX();
|
|
float angle = SWAT_ANGLE_OFFSET + atan2f(dy, dx) * 180 / M_PI;
|
|
m_scene_node->setRotation(core::vector3df(0.0, angle, 0.0));
|
|
}
|
|
} // pointToTarget
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Squash karts or items that are around the end position (determined using
|
|
* a joint) of the swatter.
|
|
*/
|
|
void Swatter::squashThingsAround()
|
|
{
|
|
const KartProperties *kp = m_kart->getKartProperties();
|
|
|
|
m_closest_kart->setSquash(kp->getSwatterSquashDuration(),
|
|
kp->getSwatterSquashSlowdown());
|
|
|
|
// Handle achievement if the swatter is used by the current player
|
|
if (m_kart->getController()->canGetAchievements())
|
|
{
|
|
PlayerManager::increaseAchievement(AchievementInfo::ACHIEVE_MOSQUITO,
|
|
"swatter", 1);
|
|
}
|
|
|
|
if (m_closest_kart->getAttachment()->getType()==Attachment::ATTACH_BOMB)
|
|
{ // make bomb explode
|
|
m_closest_kart->getAttachment()->update(10000);
|
|
HitEffect *he = new Explosion(m_kart->getXYZ(), "explosion", "explosion.xml");
|
|
if(m_kart->getController()->isLocalPlayerController())
|
|
he->setLocalPlayerKartHit();
|
|
projectile_manager->addHitEffect(he);
|
|
ExplosionAnimation::create(m_closest_kart);
|
|
} // if kart has bomb attached
|
|
if (m_closest_kart->isSquashed())
|
|
{
|
|
// The kart may not be squashed if it was protected by a bubblegum shield
|
|
World::getWorld()->kartHit(m_closest_kart->getWorldKartId());
|
|
}
|
|
|
|
// TODO: squash items
|
|
} // squashThingsAround
|