Files
stk-code_catmod/src/items/swatter.cpp
2023-11-12 11:29:41 +08:00

491 lines
18 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/achievements_status.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/abstract_kart.hpp"
#include "karts/controller/controller.hpp"
#include "karts/explosion_animation.hpp"
#include "karts/kart_properties.hpp"
#include "modes/capture_the_flag.hpp"
#include "network/network_string.hpp"
#include "network/rewind_manager.hpp"
#include <IAnimatedMeshSceneNode.h>
#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 bomb_ticks Remaining bomb time in ticks, -1 if none.
* \param ticks Swatter duration.
* \param attachment class attachment from karts.
*/
Swatter::Swatter(AbstractKart *kart, int16_t bomb_ticks, int ticks,
Attachment* attachment)
: AttachmentPlugin(kart, attachment)
{
m_animation_phase = SWATTER_AIMING;
m_discard_now = false;
m_closest_kart = NULL;
m_discard_ticks = World::getWorld()->getTicksSinceStart() + ticks;
m_bomb_remaining = bomb_ticks;
m_scene_node = NULL;
m_bomb_scene_node = NULL;
m_swatter_duration = stk_config->time2Ticks(
kart->getKartProperties()->getSwatterDuration());
if (m_bomb_remaining != -1)
{
// There are 40 frames in blender for swatter_anim.blender
// so 40 / 25 * 120
m_discard_ticks =
World::getWorld()->getTicksSinceStart() +
stk_config->time2Ticks(40.0f / 25.0f);
}
m_swat_sound = NULL;
m_swatter_animation_ticks = 0;
m_played_swatter_animation = false;
} // 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;
}
#ifndef SERVER_ONLY
if (m_swat_sound)
{
m_swat_sound->deleteSFX();
}
#endif
} // ~Swatter
// ----------------------------------------------------------------------------
void Swatter::updateGraphics(float dt)
{
#ifndef SERVER_ONLY
if (m_bomb_remaining != -1)
{
if (!m_scene_node)
{
m_scene_node = m_kart->getAttachment()->getNode();
m_scene_node->setPosition(SWAT_POS_OFFSET);
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);
}
if (!m_bomb_scene_node)
{
m_bomb_scene_node = irr_driver->addAnimatedMesh(
attachment_manager->getMesh(Attachment::ATTACH_BOMB), "bomb");
#ifdef DEBUG
std::string debug_name = m_kart->getIdent() + " (attachment)";
m_bomb_scene_node->setName(debug_name.c_str());
#endif
m_bomb_scene_node->setParent(m_kart->getNode());
float time_left = stk_config->ticks2Time(m_bomb_remaining);
if (time_left <= (m_bomb_scene_node->getEndFrame() -
m_bomb_scene_node->getStartFrame() - 1))
{
m_bomb_scene_node->setCurrentFrame(
m_bomb_scene_node->getEndFrame()
- m_bomb_scene_node->getStartFrame() - 1 - time_left);
}
m_bomb_scene_node->setAnimationSpeed(0.0f);
}
float swat_bomb_frame = stk_config->ticks2Time(
World::getWorld()->getTicksSinceStart() -
(m_discard_ticks - stk_config->time2Ticks(40.0f / 25.0f)))
* 25.0f;
if (swat_bomb_frame >= (float)m_scene_node->getEndFrame())
swat_bomb_frame = (float)m_scene_node->getEndFrame();
m_scene_node->setRotation(core::vector3df(0.0, -180.0, 0.0));
m_scene_node->setCurrentFrame(swat_bomb_frame);
if (swat_bomb_frame >= 32.5f && m_bomb_scene_node != NULL)
{
m_bomb_scene_node->setPosition(
core::vector3df(-(swat_bomb_frame - 32.5f), 0.0f, 0.0f));
m_bomb_scene_node->setRotation(
core::vector3df(-(swat_bomb_frame - 32.5f), 0.0f, 0.0f));
}
if (swat_bomb_frame >= 35)
{
m_bomb_scene_node->setVisible(false);
} // bom_frame > 35
} // if removing bomb
else
{
if (!m_scene_node)
{
m_scene_node = m_kart->getAttachment()->getNode();
if (m_kart->getIdent() == "nolok")
{
m_scene_node->setMesh(attachment_manager
->getMesh(Attachment::ATTACH_NOLOKS_SWATTER));
}
else
{
m_scene_node->setMesh(attachment_manager
->getMesh(Attachment::ATTACH_SWATTER));
}
m_scene_node->setPosition(SWAT_POS_OFFSET);
m_scene_node->setLoopMode(false);
m_scene_node->setAnimationSpeed(0.0f);
}
if (!m_swat_sound)
{
if (m_kart->getIdent() == "nolok")
m_swat_sound = SFXManager::get()->createSoundSource("hammer");
else
m_swat_sound = SFXManager::get()->createSoundSource("swatter");
}
if (!m_discard_now)
{
switch (m_animation_phase)
{
case SWATTER_AIMING:
{
pointToTarget();
m_played_swatter_animation = false;
}
break;
case SWATTER_TO_TARGET:
{
if (!m_played_swatter_animation)
{
m_played_swatter_animation = true;
// Setup the animation
m_scene_node->setCurrentFrame(0.0f);
m_scene_node->setAnimationSpeed(SWATTER_ANIMATION_SPEED);
Vec3 swatter_pos =
m_kart->getTrans()(Vec3(SWAT_POS_OFFSET));
m_swat_sound->setPosition(swatter_pos);
m_swat_sound->play();
}
pointToTarget();
}
break;
case SWATTER_FROM_TARGET:
break;
}
}
}
#endif
} // updateGraphics
// ----------------------------------------------------------------------------
/** Updates an armed swatter: it checks for any karts that are close enough
* and not invulnerable, it swats the kart.
* \param ticks Time step size.
* \return True if the attachment should be discarded.
*/
bool Swatter::updateAndTestFinished()
{
const int ticks_start = World::getWorld()->getTicksSinceStart();
if (World::getWorld()->getTicksSinceStart() > m_discard_ticks)
return true;
// If removing bomb animation playing, it will only ends when
// m_discard_ticks > world ticks
if (m_bomb_remaining != -1)
return false;
if (!m_discard_now)
{
switch (m_animation_phase)
{
case SWATTER_AIMING:
{
// Avoid swatter near the start and the end lifetime of swatter
// to make sure all clients know the existence of swatter each other
if (m_swatter_duration - m_attachment->getTicksLeft() < stk_config->time2Ticks(0.5f) ||
m_attachment->getTicksLeft() < stk_config->time2Ticks(0.75f) ) // ~0.167f and ~0.5f below
return false;
chooseTarget();
if (!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)
// The joint node doesn't update in server without graphics,
// so an approximate position is used
//scene::ISceneNode* swatter_node =
// m_scene_node->getJointNode("Swatter");
//assert(swatter_node);
//Vec3 swatter_pos = swatter_node->getAbsolutePosition();
Vec3 swatter_pos = m_kart->getTrans()(Vec3(SWAT_POS_OFFSET));
float dist2 = (m_closest_kart->getXYZ()-swatter_pos).length2();
float min_dist2
= m_kart->getKartProperties()->getSwatterDistance();
if (dist2 < min_dist2 && !m_kart->isGhostKart())
{
// Start squashing
m_animation_phase = SWATTER_TO_TARGET;
m_swatter_animation_ticks =
m_attachment->getTicksLeft() - stk_config->time2Ticks(0.166666672f);
}
}
break;
case SWATTER_TO_TARGET:
{
// Did we just finish the first part of the movement?
if (m_attachment->getTicksLeft() < m_swatter_animation_ticks &&
m_attachment->getTicksLeft() > stk_config->time2Ticks(0.5f))
{
// Squash the karts and items around and
// change the current phase
squashThingsAround();
m_animation_phase = SWATTER_FROM_TARGET;
const int end_ticks = ticks_start + stk_config->time2Ticks(0.5f);
if (RaceManager::get()->isBattleMode() ||
RaceManager::get()->isSoccerMode())
{
// Remove swatter from kart in arena gameplay
// after one successful hit
m_discard_now = true;
m_discard_ticks = end_ticks;
}
m_swatter_animation_ticks =
m_attachment->getTicksLeft() - stk_config->time2Ticks(0.5f);
}
}
break;
case SWATTER_FROM_TARGET:
{
if (m_attachment->getTicksLeft() < m_swatter_animation_ticks &&
m_attachment->getTicksLeft() > 0)
m_animation_phase = SWATTER_AIMING;
break;
}
}
}
return false;
} // updateAndTestFinished
// ----------------------------------------------------------------------------
/** 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 || kart->getKartAnimation())
continue;
// don't squash an already hurt kart
if (kart->isInvulnerable() || kart->isSquashed())
continue;
// Don't hit teammates in team world
if (world->hasTeam() &&
world->getKartTeam(kart->getWorldKartId()) ==
world->getKartTeam(m_kart->getWorldKartId()))
continue;
float dist2 = (kart->getXYZ()-m_kart->getXYZ()).length2();
if(dist2<min_dist2)
{
min_dist2 = dist2;
closest_kart = kart;
}
}
// Not larger than 2^5 - 1 for kart id for optimizing state saving
if (closest_kart && closest_kart->getWorldKartId() < 31)
m_closest_kart = closest_kart;
else
m_closest_kart = NULL;
}
// ----------------------------------------------------------------------------
/** If there is a current target, point in its direction, otherwise adopt the
* default position. */
void Swatter::pointToTarget()
{
#ifndef SERVER_ONLY
if (m_kart->isGhostKart() || !m_scene_node)
return;
if (!m_closest_kart)
{
m_scene_node->setRotation(core::vector3df());
}
else
{
Vec3 swatter_to_target =
m_kart->getTrans().inverse()(m_closest_kart->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));
}
#endif
} // pointToTarget
// ----------------------------------------------------------------------------
/** Squash karts or items that are around the end position (determined using
* a joint) of the swatter.
*/
void Swatter::squashThingsAround()
{
if (m_kart->isGhostKart()) return;
const KartProperties *kp = m_kart->getKartProperties();
float duration = kp->getSwatterSquashDuration();
float slowdown = kp->getSwatterSquashSlowdown();
// The squash attempt may fail because of invulnerability, shield, etc.
// Making a bomb explode counts as a success
bool success = m_closest_kart->setSquash(duration, slowdown);
const bool has_created_explosion_animation =
success && m_closest_kart->getKartAnimation() != NULL;
if (success)
{
World::getWorld()->kartHit(m_closest_kart->getWorldKartId(),
m_kart->getWorldKartId());
CaptureTheFlag* ctf = dynamic_cast<CaptureTheFlag*>(World::getWorld());
if (ctf)
{
int reset_ticks = (ctf->getTicksSinceStart() / 10) * 10 + 80;
ctf->resetKartForSwatterHit(m_closest_kart->getWorldKartId(),
reset_ticks);
}
// Handle achievement if the swatter is used by the current player
if (m_kart->getController()->canGetAchievements())
{
PlayerManager::addKartHit(m_closest_kart->getWorldKartId());
PlayerManager::increaseAchievement(AchievementsStatus::SWATTER_HIT, 1);
PlayerManager::increaseAchievement(AchievementsStatus::ALL_HITS, 1);
if (RaceManager::get()->isLinearRaceMode())
{
PlayerManager::increaseAchievement(AchievementsStatus::SWATTER_HIT_1RACE, 1);
PlayerManager::increaseAchievement(AchievementsStatus::ALL_HITS_1RACE, 1);
}
}
}
if (!GUIEngine::isNoGraphics() && has_created_explosion_animation &&
!RewindManager::get()->isRewinding())
{
HitEffect *he = new Explosion(m_kart->getXYZ(), "explosion", "explosion.xml");
if(m_kart->getController()->isLocalPlayerController())
he->setLocalPlayerKartHit();
ProjectileManager::get()->addHitEffect(he);
} // if kart has bomb attached
// TODO: squash items
} // squashThingsAround
// ----------------------------------------------------------------------------
void Swatter::restoreState(BareNetworkString* buffer)
{
int16_t prev_bomb_remaing = m_bomb_remaining;
m_bomb_remaining = buffer->getUInt16();
if (prev_bomb_remaing != m_bomb_remaining)
{
// Wrong state, clear mesh and let updateGraphics reset itself
m_scene_node = NULL;
if (m_bomb_scene_node)
{
irr_driver->removeNode(m_bomb_scene_node);
m_bomb_scene_node = NULL;
}
}
if (m_bomb_remaining == -1)
{
uint8_t combined = buffer->getUInt8();
int kart_id = combined & 31;
if (kart_id == 31)
m_closest_kart = NULL;
else
m_closest_kart = World::getWorld()->getKart(kart_id);
m_animation_phase = AnimationPhase((combined >> 5) & 3);
m_discard_now = (combined >> 7) == 1;
m_discard_ticks = buffer->getUInt32();
m_swatter_animation_ticks = buffer->getUInt16();
}
else
m_discard_ticks = buffer->getUInt32();
} // restoreState
// ----------------------------------------------------------------------------
void Swatter::saveState(BareNetworkString* buffer) const
{
buffer->addUInt16(m_bomb_remaining);
if (m_bomb_remaining == -1)
{
uint8_t combined =
m_closest_kart ? (uint8_t)m_closest_kart->getWorldKartId() : 31;
combined |= m_animation_phase << 5;
combined |= (m_discard_now ? (1 << 7) : 0);
buffer->addUInt8(combined).addUInt32(m_discard_ticks)
.addUInt16(m_swatter_animation_ticks);
}
else
buffer->addUInt32(m_discard_ticks);
} // saveState