stk-code_catmod/src/items/projectile_manager.cpp
2020-08-21 19:43:17 +02:00

375 lines
12 KiB
C++

//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2006-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/projectile_manager.hpp"
#include "graphics/explosion.hpp"
#include "graphics/hit_effect.hpp"
#include "items/bowling.hpp"
#include "items/cake.hpp"
#include "items/plunger.hpp"
#include "items/powerup_manager.hpp"
#include "items/powerup.hpp"
#include "items/rubber_ball.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/controller.hpp"
#include "modes/world.hpp"
#include "network/network_config.hpp"
#include "network/network_string.hpp"
#include "network/rewind_manager.hpp"
#include "utils/stk_process.hpp"
#include "utils/string_utils.hpp"
#include <typeinfo>
//=============================================================================================
ProjectileManager* g_projectile_manager[PT_COUNT];
//---------------------------------------------------------------------------------------------
ProjectileManager* ProjectileManager::get()
{
ProcessType type = STKProcess::getType();
return g_projectile_manager[type];
} // get
//---------------------------------------------------------------------------------------------
void ProjectileManager::create()
{
ProcessType type = STKProcess::getType();
g_projectile_manager[type] = new ProjectileManager();
} // create
//---------------------------------------------------------------------------------------------
void ProjectileManager::destroy()
{
ProcessType type = STKProcess::getType();
delete g_projectile_manager[type];
g_projectile_manager[type] = NULL;
} // destroy
//---------------------------------------------------------------------------------------------
void ProjectileManager::clear()
{
memset(g_projectile_manager, 0, sizeof(g_projectile_manager));
} // clear
//---------------------------------------------------------------------------------------------
void ProjectileManager::loadData()
{
} // loadData
//-----------------------------------------------------------------------------
void ProjectileManager::removeTextures()
{
cleanup();
// Only the explosion is here, all other models are actually managed
// by powerup_manager.
} // removeTextures
//-----------------------------------------------------------------------------
void ProjectileManager::cleanup()
{
m_active_projectiles.clear();
for(HitEffects::iterator i = m_active_hit_effects.begin();
i != m_active_hit_effects.end(); ++i)
{
delete *i;
}
m_active_hit_effects.clear();
} // cleanup
// -----------------------------------------------------------------------------
/** Called once per rendered frame. It is used to only update any graphical
* effects, and calls updateGraphics in any flyable objects.
* \param dt Time step size (since last call).
*/
void ProjectileManager::updateGraphics(float dt)
{
for (auto& p : m_active_projectiles)
p.second->updateGraphics(dt);
} // updateGraphics
// -----------------------------------------------------------------------------
/** General projectile update call. */
void ProjectileManager::update(int ticks)
{
updateServer(ticks);
if (RewindManager::get()->isRewinding())
return;
HitEffects::iterator he = m_active_hit_effects.begin();
while(he!=m_active_hit_effects.end())
{
// While this shouldn't happen, we had one crash because of this
if(!(*he))
{
HitEffects::iterator next = m_active_hit_effects.erase(he);
he = next;
}
// Update this hit effect. If it can be removed, remove it.
else if((*he)->updateAndDelete(ticks))
{
delete *he;
HitEffects::iterator next = m_active_hit_effects.erase(he);
he = next;
} // if hit effect finished
else // hit effect not finished, go to next one.
he++;
} // while hit effect != end
} // update
// -----------------------------------------------------------------------------
/** Updates all rockets on the server (or no networking). */
void ProjectileManager::updateServer(int ticks)
{
auto p = m_active_projectiles.begin();
while (p != m_active_projectiles.end())
{
if (!p->second->hasServerState())
{
p++;
continue;
}
bool can_be_deleted = p->second->updateAndDelete(ticks);
if (can_be_deleted)
{
HitEffect* he = p->second->getHitEffect();
if (he)
addHitEffect(he);
p->second->onDeleteFlyable();
// Flyables will be deleted by computeError in client
if (!NetworkConfig::get()->isNetworking() ||
NetworkConfig::get()->isServer())
p = m_active_projectiles.erase(p);
}
else
p++;
} // while p!=m_active_projectiles.end()
} // updateServer
// -----------------------------------------------------------------------------
/** Creates a new projectile of the given type.
* \param kart The kart which shoots the projectile.
* \param type Type of projectile.
*/
std::shared_ptr<Flyable>
ProjectileManager::newProjectile(AbstractKart *kart,
PowerupManager::PowerupType type)
{
const std::string& uid = getUniqueIdentity(kart, type);
auto it = m_active_projectiles.find(uid);
// Flyable has already created before and now rewinding, re-fire it
if (it != m_active_projectiles.end())
{
it->second->onFireFlyable();
return it->second;
}
std::shared_ptr<Flyable> f;
switch(type)
{
case PowerupManager::POWERUP_BOWLING:
f = std::make_shared<Bowling>(kart);
break;
case PowerupManager::POWERUP_PLUNGER:
f = std::make_shared<Plunger>(kart);
break;
case PowerupManager::POWERUP_CAKE:
f = std::make_shared<Cake>(kart);
break;
case PowerupManager::POWERUP_RUBBERBALL:
f = std::make_shared<RubberBall>(kart);
break;
default:
return nullptr;
}
// This cannot be done in constructor because of virtual function
f->onFireFlyable();
m_active_projectiles[uid] = f;
if (RewindManager::get()->isEnabled())
f->addForRewind(uid);
return f;
} // newProjectile
// -----------------------------------------------------------------------------
/** Returns true if a projectile is within the given distance of the specified
* kart.
* \param kart The kart for which the test is done.
* \param radius Distance within which the projectile must be.
*/
bool ProjectileManager::projectileIsClose(const AbstractKart * const kart,
float radius)
{
float r2 = radius * radius;
for (auto i = m_active_projectiles.begin(); i != m_active_projectiles.end(); i++)
{
if (!i->second->hasServerState())
continue;
float dist2 = i->second->getXYZ().distance2(kart->getXYZ());
if (dist2 < r2)
return true;
}
return false;
} // projectileIsClose
// -----------------------------------------------------------------------------
/** Returns an int containing the numbers of a given flyable in a given radius
* around the kart
* \param kart The kart for which the test is done.
* \param radius Distance within which the projectile must be.
* \param type The type of projectile checked
*/
int ProjectileManager::getNearbyProjectileCount(const AbstractKart * const kart,
float radius, PowerupManager::PowerupType type,
bool exclude_owned)
{
float r2 = radius * radius;
int projectile_count = 0;
for (auto i = m_active_projectiles.begin(); i != m_active_projectiles.end(); i++)
{
if (!i->second->hasServerState())
continue;
if (i->second->getType() == type)
{
if (exclude_owned && (i->second->getOwner() == kart))
continue;
float dist2 = i->second->getXYZ().distance2(kart->getXYZ());
if (dist2 < r2)
{
projectile_count++;
}
}
}
return projectile_count;
} // getNearbyProjectileCount
// -----------------------------------------------------------------------------
std::vector<Vec3> ProjectileManager::getBasketballPositions()
{
std::vector<Vec3> positions;
for (auto i = m_active_projectiles.begin(); i != m_active_projectiles.end(); i++)
{
if (!i->second->hasServerState())
continue;
if (i->second->getType() == PowerupManager::POWERUP_RUBBERBALL)
positions.emplace_back(i->second->getXYZ());
} // loop over projectiles
return positions;
} // getBasketballPositions
// -----------------------------------------------------------------------------
std::string ProjectileManager::getUniqueIdentity(AbstractKart* kart,
PowerupManager::PowerupType t)
{
BareNetworkString uid;
switch (t)
{
case PowerupManager::POWERUP_BOWLING:
{
uid.addUInt8(RN_BOWLING);
break;
}
case PowerupManager::POWERUP_PLUNGER:
{
uid.addUInt8(RN_PLUNGER);
break;
}
case PowerupManager::POWERUP_CAKE:
{
uid.addUInt8(RN_CAKE);
break;
}
case PowerupManager::POWERUP_RUBBERBALL:
{
uid.addUInt8(RN_RUBBERBALL);
break;
}
default:
assert(false);
return "";
}
uid.addUInt8((uint8_t)kart->getWorldKartId())
.addUInt32(World::getWorld()->getTicksSinceStart());
return std::string((char*)uid.getBuffer().data(), uid.getBuffer().size());
} // getUniqueIdentity
// -----------------------------------------------------------------------------
/* If any flyable is not found in current game state, create it with respect to
* its uid as below. */
std::shared_ptr<Rewinder>
ProjectileManager::addRewinderFromNetworkState(const std::string& uid)
{
if (uid.size() != 6)
return nullptr;
BareNetworkString data(uid.data(), (int)uid.size());
RewinderName rn = (RewinderName)data.getUInt8();
if (!(rn == RN_BOWLING || rn == RN_PLUNGER ||
rn == RN_CAKE || rn == RN_RUBBERBALL))
return nullptr;
AbstractKart* kart = World::getWorld()->getKart(data.getUInt8());
int created_ticks = data.getUInt32();
std::shared_ptr<Flyable> f;
switch (rn)
{
case RN_BOWLING:
{
f = std::make_shared<Bowling>(kart);
break;
}
case RN_PLUNGER:
{
f = std::make_shared<Plunger>(kart);
break;
}
case RN_CAKE:
{
f = std::make_shared<Cake>(kart);
break;
}
case RN_RUBBERBALL:
{
f = std::make_shared<RubberBall>(kart);
break;
}
default:
{
break;
}
}
assert(f);
f->setCreatedTicks(created_ticks);
f->onFireFlyable();
f->addForRewind(uid);
Flyable* flyable = f.get();
Log::debug("ProjectileManager", "Missed a firing event, "
"add the flyable %s by %s created at %d manually.",
typeid(*flyable).name(),
StringUtils::wideToUtf8(kart->getController()->getName()).c_str(),
created_ticks);
m_active_projectiles[uid] = f;
return f;
} // addProjectileFromNetworkState