714 lines
25 KiB
C++
714 lines
25 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/item_manager.hpp"
|
|
|
|
#include "config/stk_config.hpp"
|
|
#include "config/user_config.hpp"
|
|
#include "graphics/irr_driver.hpp"
|
|
#include "graphics/material.hpp"
|
|
#include "graphics/material_manager.hpp"
|
|
#include "graphics/sp/sp_base.hpp"
|
|
#include "io/file_manager.hpp"
|
|
#include "karts/abstract_kart.hpp"
|
|
#include "karts/controller/spare_tire_ai.hpp"
|
|
#include "modes/easter_egg_hunt.hpp"
|
|
#include "modes/profile_world.hpp"
|
|
#include "network/network_config.hpp"
|
|
#include "network/race_event_manager.hpp"
|
|
#include "physics/triangle_mesh.hpp"
|
|
#include "tracks/arena_graph.hpp"
|
|
#include "tracks/arena_node.hpp"
|
|
#include "tracks/track.hpp"
|
|
#include "utils/string_utils.hpp"
|
|
|
|
#include <IMesh.h>
|
|
#include <IAnimatedMesh.h>
|
|
|
|
#include <assert.h>
|
|
#include <stdexcept>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
|
|
std::vector<scene::IMesh *> ItemManager::m_item_mesh;
|
|
std::vector<scene::IMesh *> ItemManager::m_item_lowres_mesh;
|
|
std::vector<video::SColorf> ItemManager::m_glow_color;
|
|
std::vector<std::string> ItemManager::m_icon;
|
|
bool ItemManager::m_disable_item_collection = false;
|
|
std::mt19937 ItemManager::m_random_engine;
|
|
uint32_t ItemManager::m_random_seed = 0;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Loads the default item meshes (high- and low-resolution).
|
|
*/
|
|
void ItemManager::loadDefaultItemMeshes()
|
|
{
|
|
m_item_mesh.clear();
|
|
m_item_lowres_mesh.clear();
|
|
m_glow_color.clear();
|
|
m_icon.clear();
|
|
m_item_mesh.resize(ItemState::ITEM_LAST-ItemState::ITEM_FIRST+1, NULL);
|
|
m_glow_color.resize(ItemState::ITEM_LAST-ItemState::ITEM_FIRST+1,
|
|
video::SColorf(255.0f, 255.0f, 255.0f) );
|
|
m_icon.resize(ItemState::ITEM_LAST-ItemState::ITEM_FIRST+1, "");
|
|
|
|
m_item_lowres_mesh.resize(ItemState::ITEM_LAST-ItemState::ITEM_FIRST+1, NULL);
|
|
|
|
// A temporary mapping of items to names used in the XML file:
|
|
std::map<ItemState::ItemType, std::string> item_names;
|
|
item_names[ItemState::ITEM_BANANA ] = "banana";
|
|
item_names[ItemState::ITEM_BONUS_BOX ] = "bonus-box";
|
|
item_names[ItemState::ITEM_BUBBLEGUM ] = "bubblegum";
|
|
item_names[ItemState::ITEM_NITRO_BIG ] = "nitro-big";
|
|
item_names[ItemState::ITEM_NITRO_SMALL] = "nitro-small";
|
|
item_names[ItemState::ITEM_BUBBLEGUM_NOLOK] = "bubblegum-nolok";
|
|
item_names[ItemState::ITEM_EASTER_EGG ] = "easter-egg";
|
|
|
|
const std::string file_name = file_manager->getAsset("items.xml");
|
|
const XMLNode *root = file_manager->createXMLTree(file_name);
|
|
for(unsigned int i=ItemState::ITEM_FIRST; i<=ItemState::ITEM_LAST; i++)
|
|
{
|
|
const std::string &name = item_names[(ItemState::ItemType)i];
|
|
const XMLNode *node = root->getNode(name);
|
|
if (!node) continue;
|
|
|
|
std::string model_filename;
|
|
node->get("model", &model_filename);
|
|
|
|
scene::IMesh *mesh = irr_driver->getAnimatedMesh(model_filename);
|
|
if(!node || model_filename.size()==0 || !mesh)
|
|
{
|
|
Log::fatal("[ItemManager]", "Item model '%s' in items.xml could not be loaded "
|
|
"- aborting", name.c_str());
|
|
exit(-1);
|
|
}
|
|
#ifndef SERVER_ONLY
|
|
SP::uploadSPM(mesh);
|
|
#endif
|
|
mesh->grab();
|
|
m_item_mesh[i] = mesh;
|
|
node->get("glow", &(m_glow_color[i]));
|
|
|
|
std::string lowres_model_filename;
|
|
node->get("lowmodel", &lowres_model_filename);
|
|
m_item_lowres_mesh[i] = lowres_model_filename.size() == 0
|
|
? NULL
|
|
: irr_driver->getMesh(lowres_model_filename);
|
|
|
|
if (m_item_lowres_mesh[i])
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
SP::uploadSPM(m_item_lowres_mesh[i]);
|
|
#endif
|
|
m_item_lowres_mesh[i]->grab();
|
|
}
|
|
std::string icon = "icon-" + item_names[(ItemState::ItemType)i] + ".png";
|
|
if (preloadIcon(icon))
|
|
m_icon[i] = icon;
|
|
} // for i
|
|
delete root;
|
|
preloadIcon("item_spark.png");
|
|
} // loadDefaultItemMeshes
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Preload icon materials to avoid hangs when firstly insert item
|
|
*/
|
|
bool ItemManager::preloadIcon(const std::string& name)
|
|
{
|
|
// From IrrDriver::addBillboard
|
|
Material* m = material_manager->getMaterial(name, false/*full_path*/,
|
|
/*make_permanent*/true, /*complain_if_not_found*/true,
|
|
/*strip_path*/false, /*install*/false);
|
|
return m->getTexture(true/*srgb*/, m->getShaderName() == "additive" ||
|
|
m->getShaderName() == "alphablend" ? true : false/*premul_alpha*/) !=
|
|
NULL;
|
|
} // preloadIcon
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Clean up all textures. This is necessary when switching resolution etc.
|
|
*/
|
|
void ItemManager::removeTextures()
|
|
{
|
|
if (m_item_mesh.empty() && m_item_lowres_mesh.empty())
|
|
return;
|
|
for(unsigned int i=0; i<ItemState::ITEM_LAST-ItemState::ITEM_FIRST+1; i++)
|
|
{
|
|
if(m_item_mesh[i])
|
|
{
|
|
m_item_mesh[i]->drop();
|
|
irr_driver->removeMeshFromCache(m_item_mesh[i]);
|
|
}
|
|
m_item_mesh[i] = NULL;
|
|
if(m_item_lowres_mesh[i])
|
|
{
|
|
m_item_lowres_mesh[i]->drop();
|
|
irr_driver->removeMeshFromCache(m_item_lowres_mesh[i]);
|
|
}
|
|
m_item_lowres_mesh[i] = NULL;
|
|
}
|
|
} // removeTextures
|
|
|
|
|
|
// ============================================================================
|
|
/** Creates a new instance of the item manager. This is done at startup
|
|
* of each race. */
|
|
ItemManager::ItemManager()
|
|
{
|
|
m_switch_ticks = -1;
|
|
// The actual loading is done in loadDefaultItems
|
|
|
|
// Prepare the switch to array, which stores which item should be
|
|
// switched to what other item. Initialise it with a mapping that
|
|
// each item is switched to itself, so basically a no-op.
|
|
m_switch_to.reserve(ItemState::ITEM_COUNT);
|
|
for(unsigned int i=ItemState::ITEM_FIRST; i<ItemState::ITEM_COUNT; i++)
|
|
m_switch_to.push_back((ItemState::ItemType)i);
|
|
setSwitchItems(stk_config->m_switch_items);
|
|
|
|
if(Graph::get())
|
|
{
|
|
m_items_in_quads = new std::vector<AllItemTypes>;
|
|
// Entries 0 to n-1 are for the quads, entry
|
|
// n is for all items that are not on a quad.
|
|
m_items_in_quads->resize(Graph::get()->getNumNodes()+1);
|
|
}
|
|
else
|
|
{
|
|
m_items_in_quads = NULL;
|
|
}
|
|
} // ItemManager
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Sets which objects is getting switched to what.
|
|
* \param switch A mapping of items types to item types for the mapping.
|
|
* must contain one entry for each item.
|
|
*/
|
|
void ItemManager::setSwitchItems(const std::vector<int> &switch_items)
|
|
{
|
|
for(unsigned int i=ItemState::ITEM_FIRST; i<ItemState::ITEM_COUNT; i++)
|
|
m_switch_to[i]=(ItemState::ItemType)switch_items[i];
|
|
} // setSwitchItems
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Destructor. Cleans up all items and meshes stored.
|
|
*/
|
|
ItemManager::~ItemManager()
|
|
{
|
|
if(m_items_in_quads)
|
|
delete m_items_in_quads;
|
|
for(AllItemTypes::iterator i =m_all_items.begin();
|
|
i!=m_all_items.end(); i++)
|
|
{
|
|
if(*i)
|
|
delete *i;
|
|
}
|
|
|
|
m_all_items.clear();
|
|
} // ~ItemManager
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Inserts the new item into the items management data structures, if possible
|
|
* reusing an existing, unused entry (e.g. due to a removed bubble gum). Then
|
|
* the item is also added to the quad-wise list of items.
|
|
* \param item The item to be added.
|
|
* \return Index of the newly added item in the list of all items.
|
|
*/
|
|
unsigned int ItemManager::insertItem(Item *item)
|
|
{
|
|
// Find where the item can be stored in the index list: either in a
|
|
// previously deleted entry, otherwise at the end.
|
|
int index = -1;
|
|
for(index=(int)m_all_items.size()-1; index>=0 && m_all_items[index]; index--) {}
|
|
if (index == -1)
|
|
{
|
|
index = (int)m_all_items.size();
|
|
m_all_items.push_back(item);
|
|
}
|
|
else
|
|
{
|
|
m_all_items[index] = item;
|
|
}
|
|
item->setItemId(index);
|
|
insertItemInQuad(item);
|
|
// Now insert into the appropriate quad list, if there is a quad list
|
|
// (i.e. race mode has a quad graph).
|
|
return index;
|
|
} // insertItem
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Insert into the appropriate quad list, if there is a quad list
|
|
* (i.e. race mode has a quad graph).
|
|
*/
|
|
void ItemManager::insertItemInQuad(Item *item)
|
|
{
|
|
if(m_items_in_quads)
|
|
{
|
|
int graph_node = item->getGraphNode();
|
|
// If the item is on the graph, store it at the appropriate index
|
|
if(graph_node > -1)
|
|
{
|
|
(*m_items_in_quads)[graph_node].push_back(item);
|
|
}
|
|
else // otherwise store it in the 'outside' index
|
|
(*m_items_in_quads)[m_items_in_quads->size()-1].push_back(item);
|
|
} // if m_items_in_quads
|
|
} // insertItemInQuad
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Creates a new item at the location of the kart (e.g. kart drops a
|
|
* bubblegum).
|
|
* \param type Type of the item.
|
|
* \param kart The kart that drops the new item.
|
|
* \param server_xyz Can be used to overwrite the item location.
|
|
* \param server_normal The normal as seen on the server.
|
|
*/
|
|
Item* ItemManager::dropNewItem(ItemState::ItemType type,
|
|
const AbstractKart *kart,
|
|
const Vec3 *server_xyz,
|
|
const Vec3 *server_normal)
|
|
{
|
|
Vec3 normal, pos;
|
|
const Material* material_hit;
|
|
if (!server_xyz)
|
|
{
|
|
// We are doing a new drop locally, i.e. not based on
|
|
// server data. So we need a raycast to find the correct
|
|
// location and normal of the item:
|
|
pos = server_xyz ? *server_xyz : kart->getXYZ();
|
|
Vec3 to = pos + kart->getTrans().getBasis() * Vec3(0, -10000, 0);
|
|
Vec3 hit_point;
|
|
Track::getCurrentTrack()->getTriangleMesh().castRay(pos, to,
|
|
&hit_point,
|
|
&material_hit,
|
|
&normal);
|
|
|
|
// We will get no material if the kart is 'over nothing' when dropping
|
|
// the bubble gum. In most cases this means that the item does not need
|
|
// to be created (and we just return NULL).
|
|
if (!material_hit) return NULL;
|
|
normal.normalize();
|
|
pos = hit_point + kart->getTrans().getBasis() * Vec3(0, -0.05f, 0);
|
|
}
|
|
else
|
|
{
|
|
// We are on a client which has received a new item event from the
|
|
// server. So use the server's data for the new item:
|
|
normal = *server_normal;
|
|
pos = *server_xyz;
|
|
}
|
|
|
|
ItemState::ItemType mesh_type = type;
|
|
if (type == ItemState::ITEM_BUBBLEGUM && kart->getIdent() == "nolok")
|
|
{
|
|
mesh_type = ItemState::ITEM_BUBBLEGUM_NOLOK;
|
|
}
|
|
|
|
Item* item = new Item(type, pos, normal, m_item_mesh[mesh_type],
|
|
m_item_lowres_mesh[mesh_type], m_icon[mesh_type],
|
|
/*prev_owner*/kart);
|
|
|
|
// restoreState in NetworkItemManager will handle the insert item
|
|
if (!server_xyz)
|
|
insertItem(item);
|
|
if(m_switch_ticks>=0)
|
|
{
|
|
ItemState::ItemType new_type = m_switch_to[item->getType()];
|
|
item->switchTo(new_type);
|
|
}
|
|
return item;
|
|
} // dropNewItem
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Places a new item on the track/arena. It is used for the initial placement
|
|
* of the items - either according to the scene.xml file, or random item
|
|
* placement.
|
|
* \param type Type of the item.
|
|
* \param xyz Position of the item.
|
|
* \param normal The normal of the terrain to set roll and pitch.
|
|
*/
|
|
Item* ItemManager::placeItem(ItemState::ItemType type, const Vec3& xyz,
|
|
const Vec3 &normal)
|
|
{
|
|
// Make sure this subroutine is not used otherwise (since networking
|
|
// needs to be aware of items added to the track, so this would need
|
|
// to be added).
|
|
assert(World::getWorld()->getPhase() == WorldStatus::SETUP_PHASE ||
|
|
ProfileWorld::isProfileMode() );
|
|
ItemState::ItemType mesh_type = type;
|
|
|
|
Item* item = new Item(type, xyz, normal, m_item_mesh[mesh_type],
|
|
m_item_lowres_mesh[mesh_type], m_icon[mesh_type],
|
|
/*prev_owner*/NULL);
|
|
|
|
insertItem(item);
|
|
if (m_switch_ticks >= 0)
|
|
{
|
|
ItemState::ItemType new_type = m_switch_to[item->getType()];
|
|
item->switchTo(new_type);
|
|
}
|
|
return item;
|
|
} // placeItem
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Set an item as collected.
|
|
* This function is called on the server when an item is collected, or on
|
|
* the client upon receiving information about collected items.
|
|
* \param item The item that was collected.
|
|
* \param kart The kart that collected the item.
|
|
*/
|
|
void ItemManager::collectedItem(ItemState *item, AbstractKart *kart)
|
|
{
|
|
assert(item);
|
|
item->collected(kart);
|
|
// Inform the world - used for Easter egg hunt
|
|
World::getWorld()->collectedItem(kart, item);
|
|
kart->collectedItem(item);
|
|
} // collectedItem
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Checks if any item was collected by the given kart. This function calls
|
|
* collectedItem if an item was collected.
|
|
* \param kart Pointer to the kart.
|
|
*/
|
|
void ItemManager::checkItemHit(AbstractKart* kart)
|
|
{
|
|
// We could use m_items_in_quads to to check for item hits: take the quad
|
|
// of the graph node of the kart, and only check items in that quad. But
|
|
// then we also need to check for any adjacent quads (since an item just
|
|
// on the order of one quad might get hit from an adjacent quad). Then
|
|
// it is possible that a quad is that short that we need to test adjacent
|
|
// of adjacent quads. And check for items outside of the track.
|
|
// Since at this stace item detection is by far not a bottle neck,
|
|
// the original, simple and stable algorithm is left in place.
|
|
|
|
/** Disable item collection detection for debug purposes. */
|
|
if(m_disable_item_collection) return;
|
|
|
|
// Spare tire karts don't collect items
|
|
if ( dynamic_cast<SpareTireAI*>(kart->getController()) ) return;
|
|
|
|
for(AllItemTypes::iterator i =m_all_items.begin();
|
|
i!=m_all_items.end(); i++)
|
|
{
|
|
// Ignore items that have been collected or are not available atm
|
|
if ((!*i) || !(*i)->isAvailable() || (*i)->isUsedUp()) continue;
|
|
|
|
// Shielded karts can simply drive over bubble gums without any effect
|
|
if ( kart->isShielded() &&
|
|
( (*i)->getType() == ItemState::ITEM_BUBBLEGUM ||
|
|
(*i)->getType() == ItemState::ITEM_BUBBLEGUM_NOLOK ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
// To allow inlining and avoid including kart.hpp in item.hpp,
|
|
// we pass the kart and the position separately.
|
|
if((*i)->hitKart(kart->getXYZ(), kart))
|
|
{
|
|
collectedItem(*i, kart);
|
|
} // if hit
|
|
} // for m_all_items
|
|
} // checkItemHit
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Resets all items and removes bubble gum that is stuck on the track.
|
|
* This is done when a race is (re)started.
|
|
*/
|
|
void ItemManager::reset()
|
|
{
|
|
// If items are switched, switch them back first.
|
|
if(m_switch_ticks>=0)
|
|
{
|
|
for(AllItemTypes::iterator i =m_all_items.begin();
|
|
i!=m_all_items.end(); i++)
|
|
{
|
|
if(*i) (*i)->switchBack();
|
|
}
|
|
m_switch_ticks = -1;
|
|
|
|
}
|
|
|
|
// We can't simply erase items in the list: in this case the indicies
|
|
// stored in each item are invalid, and lead to incorrect result/crashes
|
|
// in deleteItem
|
|
AllItemTypes::iterator i=m_all_items.begin();
|
|
while(i!=m_all_items.end())
|
|
{
|
|
if(!*i)
|
|
{
|
|
i++;
|
|
continue;
|
|
}
|
|
if((*i)->canBeUsedUp() || (*i)->getType()==ItemState::ITEM_BUBBLEGUM)
|
|
{
|
|
deleteItem( *i );
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
(*i)->reset();
|
|
i++;
|
|
}
|
|
} // whilem_all_items.end() i
|
|
|
|
m_switch_ticks = -1;
|
|
} // reset
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Updates all items, and handles switching items back if the switch time
|
|
* is over.
|
|
* \param ticks Number of physics time steps - should be 1.
|
|
*/
|
|
void ItemManager::update(int ticks)
|
|
{
|
|
// If switch time is over, switch all items back
|
|
if(m_switch_ticks>=0)
|
|
{
|
|
m_switch_ticks -= ticks;
|
|
if(m_switch_ticks<0)
|
|
{
|
|
for(AllItemTypes::iterator i = m_all_items.begin();
|
|
i!= m_all_items.end(); i++)
|
|
{
|
|
if(*i) (*i)->switchBack();
|
|
} // for m_all_items
|
|
} // m_switch_ticks < 0
|
|
} // m_switch_ticks>=0
|
|
|
|
for(AllItemTypes::iterator i =m_all_items.begin();
|
|
i!=m_all_items.end(); i++)
|
|
{
|
|
if(*i)
|
|
{
|
|
(*i)->update(ticks);
|
|
if( (*i)->isUsedUp())
|
|
{
|
|
deleteItem( *i );
|
|
} // if usedUp
|
|
} // if *i
|
|
} // for m_all_items
|
|
} // update
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Updates the graphics, called once per rendered frame.
|
|
* \param dt Time based on frame rate.
|
|
*/
|
|
void ItemManager::updateGraphics(float dt)
|
|
{
|
|
for (AllItemTypes::iterator i = m_all_items.begin();
|
|
i != m_all_items.end(); i++)
|
|
{
|
|
if (*i) (*i)->updateGraphics(dt);
|
|
} // for m_all_items
|
|
|
|
} // updateGraphics
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Removes an items from the items-in-quad list, from the list of all
|
|
* items, and then frees the item itself.
|
|
* \param The item to delete.
|
|
*/
|
|
void ItemManager::deleteItem(ItemState *item)
|
|
{
|
|
// First check if the item needs to be removed from the items-in-quad list
|
|
deleteItemInQuad(item);
|
|
int index = item->getItemId();
|
|
m_all_items[index] = NULL;
|
|
delete item;
|
|
} // delete item
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Removes an items from the items-in-quad list only
|
|
* \param The item to delete.
|
|
*/
|
|
void ItemManager::deleteItemInQuad(ItemState* item)
|
|
{
|
|
if(m_items_in_quads)
|
|
{
|
|
int sector = item->getGraphNode();
|
|
unsigned int indx = sector==Graph::UNKNOWN_SECTOR
|
|
? (unsigned int) m_items_in_quads->size()-1
|
|
: sector;
|
|
AllItemTypes &items = (*m_items_in_quads)[indx];
|
|
AllItemTypes::iterator it = std::find(items.begin(), items.end(),item);
|
|
assert(it!=items.end());
|
|
items.erase(it);
|
|
} // if m_items_in_quads
|
|
} // deleteItemInQuad
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Switches all items: boxes become bananas and vice versa for a certain
|
|
* amount of time (as defined in stk_config.xml).
|
|
*/
|
|
void ItemManager::switchItems()
|
|
{
|
|
switchItemsInternal(m_all_items);
|
|
} // switchItems
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Switches all items: boxes become bananas and vice versa for a certain
|
|
* amount of time (as defined in stk_config.xml).
|
|
*/
|
|
void ItemManager::switchItemsInternal(std::vector<ItemState*> &all_items)
|
|
{
|
|
for(AllItemTypes::iterator i = all_items.begin();
|
|
i != all_items.end(); i++)
|
|
{
|
|
if(!*i) continue;
|
|
|
|
ItemState::ItemType new_type = m_switch_to[(*i)->getType()];
|
|
|
|
if (new_type == (*i)->getType())
|
|
continue;
|
|
if(m_switch_ticks<0)
|
|
(*i)->switchTo(new_type);
|
|
else
|
|
(*i)->switchBack();
|
|
} // for all_items
|
|
|
|
// if the items are already switched (m_switch_ticks >=0)
|
|
// then switch back, and set m_switch_ticks to -1 to indicate
|
|
// that the items are now back to normal.
|
|
m_switch_ticks = m_switch_ticks < 0 ? stk_config->m_item_switch_ticks : -1;
|
|
|
|
} // switchItems
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool ItemManager::randomItemsForArena(const AlignedArray<btTransform>& pos)
|
|
{
|
|
if (!UserConfigParams::m_random_arena_item) return false;
|
|
if (!ArenaGraph::get()) return false;
|
|
|
|
const ArenaGraph* ag = ArenaGraph::get();
|
|
std::vector<int> used_location;
|
|
std::vector<int> invalid_location;
|
|
for (unsigned int i = 0; i < pos.size(); i++)
|
|
{
|
|
// Load all starting positions of arena, so no items will be near them
|
|
int node = -1;
|
|
ag->findRoadSector(pos[i].getOrigin(), &node, NULL, true);
|
|
assert(node != -1);
|
|
used_location.push_back(node);
|
|
invalid_location.push_back(node);
|
|
}
|
|
|
|
const unsigned int ALL_NODES = ag->getNumNodes();
|
|
const unsigned int MIN_DIST = int(sqrt(ALL_NODES));
|
|
const unsigned int TOTAL_ITEM = MIN_DIST / 2;
|
|
|
|
std::vector<uint32_t> random_numbers;
|
|
Log::info("[ItemManager]","Creating %d random items for arena", TOTAL_ITEM);
|
|
for (unsigned int i = 0; i < TOTAL_ITEM; i++)
|
|
{
|
|
int chosen_node = -1;
|
|
while(true)
|
|
{
|
|
if (used_location.size() - pos.size() +
|
|
invalid_location.size() == ALL_NODES)
|
|
{
|
|
Log::warn("[ItemManager]","Can't place more random items! "
|
|
"Use default item location.");
|
|
return false;
|
|
}
|
|
uint32_t number = m_random_engine();
|
|
Log::debug("[ItemManager]", "%u from random engine.", number);
|
|
const int node = number % ALL_NODES;
|
|
|
|
// Check if tried
|
|
std::vector<int>::iterator it = std::find(invalid_location.begin(),
|
|
invalid_location.end(), node);
|
|
if (it != invalid_location.end())
|
|
continue;
|
|
|
|
// Check if near edge
|
|
if (ag->getNode(node)->isNearEdge())
|
|
{
|
|
invalid_location.push_back(node);
|
|
continue;
|
|
}
|
|
// Check if too close
|
|
bool found = true;
|
|
for (unsigned int j = 0; j < used_location.size(); j++)
|
|
{
|
|
if (!found) continue;
|
|
float test_distance = ag->getDistance(used_location[j], node);
|
|
found = test_distance > MIN_DIST;
|
|
}
|
|
if (found)
|
|
{
|
|
chosen_node = node;
|
|
invalid_location.push_back(node);
|
|
random_numbers.push_back(number);
|
|
break;
|
|
}
|
|
else
|
|
invalid_location.push_back(node);
|
|
}
|
|
|
|
assert(chosen_node != -1);
|
|
used_location.push_back(chosen_node);
|
|
}
|
|
|
|
for (unsigned int i = 0; i < pos.size(); i++)
|
|
used_location.erase(used_location.begin());
|
|
|
|
assert(used_location.size() == TOTAL_ITEM);
|
|
assert(random_numbers.size() == TOTAL_ITEM);
|
|
|
|
// Hard-coded ratio for now
|
|
const int BONUS_BOX = 4;
|
|
const int NITRO_BIG = 2;
|
|
const int NITRO_SMALL = 1;
|
|
|
|
for (unsigned int i = 0; i < TOTAL_ITEM; i++)
|
|
{
|
|
const unsigned j = random_numbers[i] % 10;
|
|
ItemState::ItemType type = (j > BONUS_BOX ? ItemState::ITEM_BONUS_BOX :
|
|
j > NITRO_BIG ? ItemState::ITEM_NITRO_BIG :
|
|
j > NITRO_SMALL ? ItemState::ITEM_NITRO_SMALL : ItemState::ITEM_BANANA);
|
|
|
|
ArenaNode* an = ag->getNode(used_location[i]);
|
|
Vec3 loc = an->getCenter();
|
|
Vec3 quad_normal = an->getNormal();
|
|
loc += quad_normal;
|
|
|
|
// Do a raycast to help place it fully on the surface
|
|
const Material* m;
|
|
Vec3 normal;
|
|
Vec3 hit_point;
|
|
const TriangleMesh& tm = Track::getCurrentTrack()->getTriangleMesh();
|
|
bool success = tm.castRay(loc, an->getCenter() + (-10000*quad_normal),
|
|
&hit_point, &m, &normal);
|
|
|
|
if (success)
|
|
{
|
|
placeItem(type, hit_point, normal);
|
|
}
|
|
else
|
|
{
|
|
Log::warn("[ItemManager]","Raycast to surface failed"
|
|
"from node %d", used_location[i]);
|
|
placeItem(type, an->getCenter(), quad_normal);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
} // randomItemsForArena
|