The probability of which item is collected now depends on the race
position. The actual distribution is defined in powerup.xml (and probably needs some adjustments). git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/trunk@5320 178a84e3-b1eb-0310-8ba1-8eac791a3b58
This commit is contained in:
@@ -21,5 +21,15 @@
|
||||
min-height="0.2" max-height="1.0"
|
||||
force-updown="35" force-to-target="15"
|
||||
max-distance="25" />
|
||||
|
||||
<!-- Distribution of the items depending on position of the
|
||||
kart. The order must correspond to powerup_manager.hpp -->
|
||||
<!-- bubble cake bowl zipper plunger switch para anvil -->
|
||||
<first w="1 0 0 0 0 1 0 0" />
|
||||
<top33 w="1 1 1 1 1 1 1 0" />
|
||||
<mid33 w="1 1 1 1 1 1 1 0" />
|
||||
<end33 w="0 1 1 1 1 1 1 1" />
|
||||
<last w="0 1 1 2 2 0 2 2" />
|
||||
|
||||
</powerup>
|
||||
|
||||
|
||||
@@ -289,107 +289,18 @@ void Powerup::use()
|
||||
*/
|
||||
void Powerup::hitBonusBox(int n, const Item &item, int add_info)
|
||||
{
|
||||
unsigned int position = m_owner->getPosition();
|
||||
|
||||
PowerupManager::PowerupType new_powerup =
|
||||
powerup_manager->getRandomPowerup(position);
|
||||
|
||||
World *world = World::getWorld();
|
||||
//The probabilities of getting the anvil or the parachute increase
|
||||
//depending on how bad the owner's position is. For the first
|
||||
//driver the posibility is none, for the last player is 15 %.
|
||||
if(m_owner->getPosition() != 1 &&
|
||||
m_type == PowerupManager::POWERUP_NOTHING &&
|
||||
world->acceptPowerup(PowerupManager::POWERUP_PARACHUTE) &&
|
||||
world->acceptPowerup(PowerupManager::POWERUP_ANVIL))
|
||||
{
|
||||
// On client: just set the value
|
||||
if(network_manager->getMode()==NetworkManager::NW_CLIENT)
|
||||
{
|
||||
m_random.get(100); // keep random numbers in sync
|
||||
set( (PowerupManager::PowerupType)add_info, 1);
|
||||
return;
|
||||
}
|
||||
const int SPECIAL_PROB = (int)(15.0 / ((float)world->getCurrentNumKarts() /
|
||||
(float)m_owner->getPosition()));
|
||||
const int RAND_NUM = m_random.get(100);
|
||||
if(RAND_NUM <= SPECIAL_PROB)
|
||||
{
|
||||
//If the driver in the first position has finished, give the driver
|
||||
//the parachute.
|
||||
for(unsigned int i=0; i < world->getNumKarts(); ++i)
|
||||
{
|
||||
Kart *kart = world->getKart(i);
|
||||
if(kart->isEliminated() || kart == m_owner) continue;
|
||||
if(kart->getPosition() == 1 && kart->hasFinishedRace())
|
||||
{
|
||||
set(PowerupManager::POWERUP_PARACHUTE, 1);
|
||||
if(network_manager->getMode()==NetworkManager::NW_SERVER)
|
||||
{
|
||||
race_state->itemCollected(m_owner->getWorldKartId(),
|
||||
item.getItemId(),
|
||||
m_type);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
set( (m_random.get(2) == 0 ? PowerupManager::POWERUP_ANVIL
|
||||
: PowerupManager::POWERUP_PARACHUTE),1);
|
||||
|
||||
if(network_manager->getMode()==NetworkManager::NW_SERVER)
|
||||
{
|
||||
race_state->itemCollected(m_owner->getWorldKartId(),
|
||||
item.getItemId(),
|
||||
(char)m_type);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If no special case is done: on the client just adjust the number
|
||||
// dependent on the server informaion:
|
||||
if(network_manager->getMode()==NetworkManager::NW_CLIENT)
|
||||
{
|
||||
if(m_type==PowerupManager::POWERUP_NOTHING)
|
||||
{
|
||||
set( (PowerupManager::PowerupType)add_info, n );
|
||||
}
|
||||
else if((PowerupManager::PowerupType)add_info==m_type)
|
||||
{
|
||||
m_number+=n;
|
||||
if(m_number > MAX_POWERUPS) m_number = MAX_POWERUPS;
|
||||
}
|
||||
// Ignore new powerup if it is different from the current one
|
||||
m_random.get(100); // keep random numbers in synch
|
||||
|
||||
return;
|
||||
} // if network client
|
||||
|
||||
// Otherwise (server or no network): determine powerup randomly
|
||||
|
||||
//(POWERUP_MAX - 1) is the last valid id. We substract 2 because because we have to
|
||||
//exclude the anvil and the parachute which are handled above, but later we
|
||||
//have to add 1 to prevent having a value of 0 since that isn't a valid powerup.
|
||||
PowerupManager::PowerupType newC;
|
||||
while(true)
|
||||
{
|
||||
newC = (PowerupManager::PowerupType)
|
||||
(m_random.get(PowerupManager::POWERUP_MAX - 1 - 2) + 1);
|
||||
// allow the game mode to allow or disallow this type of powerup
|
||||
if(world->acceptPowerup(newC)) break;
|
||||
}
|
||||
|
||||
// Save the information about the powerup in the race state
|
||||
// so that the clients can be updated.
|
||||
if(network_manager->getMode()==NetworkManager::NW_SERVER)
|
||||
{
|
||||
race_state->itemCollected(m_owner->getWorldKartId(),
|
||||
item.getItemId(),
|
||||
newC);
|
||||
}
|
||||
|
||||
if(m_type==PowerupManager::POWERUP_NOTHING)
|
||||
{
|
||||
set( newC, n );
|
||||
set( new_powerup, n );
|
||||
}
|
||||
else if(newC==m_type)
|
||||
else if(new_powerup==m_type)
|
||||
{
|
||||
m_number+=n;
|
||||
if(m_number > MAX_POWERUPS)
|
||||
|
||||
@@ -35,12 +35,19 @@ class SFXBase;
|
||||
class Powerup
|
||||
{
|
||||
private:
|
||||
/** A synchronised random number generator for network games. */
|
||||
RandomGenerator m_random;
|
||||
|
||||
/** Sound effect that is being played. */
|
||||
SFXBase *m_sound_use;
|
||||
|
||||
/** The powerup type. */
|
||||
PowerupManager::PowerupType m_type;
|
||||
|
||||
/** Number of collected powerups. */
|
||||
int m_number;
|
||||
|
||||
protected:
|
||||
|
||||
/** The owner (kart) of this powerup. */
|
||||
Kart* m_owner;
|
||||
|
||||
public:
|
||||
@@ -50,6 +57,7 @@ public:
|
||||
void reset ();
|
||||
Material* getIcon () const;
|
||||
void use ();
|
||||
void hitBonusBox (int n, const Item &item, int newC=-1);
|
||||
|
||||
/** Returns the number of powerups. */
|
||||
int getNum () const {return m_number;}
|
||||
@@ -58,7 +66,6 @@ public:
|
||||
PowerupManager::PowerupType
|
||||
getType () const {return m_type; }
|
||||
// ------------------------------------------------------------------------
|
||||
void hitBonusBox (int n, const Item &item, int newC=-1);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
#include "items/bowling.hpp"
|
||||
#include "items/cake.hpp"
|
||||
#include "items/plunger.hpp"
|
||||
#include "modes/world.hpp"
|
||||
#include "utils/constants.hpp"
|
||||
#include "utils/string_utils.hpp"
|
||||
|
||||
PowerupManager* powerup_manager=0;
|
||||
|
||||
@@ -97,8 +100,15 @@ void PowerupManager::loadAllPowerups()
|
||||
std::string name;
|
||||
node->get("name", &name);
|
||||
PowerupType type = getPowerupType(name);
|
||||
LoadPowerup(type, *node);
|
||||
// The weight nodes will be also included in this list, so ignore those
|
||||
if(type!=POWERUP_NOTHING)
|
||||
LoadPowerup(type, *node);
|
||||
}
|
||||
loadWeights(*root, "first", POSITION_FIRST);
|
||||
loadWeights(*root, "top33", POSITION_TOP33);
|
||||
loadWeights(*root, "mid33", POSITION_MID33);
|
||||
loadWeights(*root, "end33", POSITION_END33);
|
||||
loadWeights(*root, "last" , POSITION_LAST );
|
||||
} // loadAllPowerups
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@@ -142,15 +152,127 @@ void PowerupManager::LoadPowerup(PowerupType type, const XMLNode &node)
|
||||
Cake::init(node, m_all_meshes[type]); break;
|
||||
default:;
|
||||
} // switch
|
||||
|
||||
const XMLNode *n1 = node.getNode("first"); loadWeights(*n1);
|
||||
const XMLNode *n2 = node.getNode("top33"); loadWeights(*n2);
|
||||
const XMLNode *n3 = node.getNode("mid33"); loadWeights(*n3);
|
||||
const XMLNode *n4 = node.getNode("end33"); loadWeights(*n4);
|
||||
const XMLNode *n5 = node.getNode("last"); loadWeights(*n5);
|
||||
} // LoadNode
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
void PowerupManager::loadWeights(const XMLNode &node)
|
||||
/** Loads a weight list specified in powerup.xml. The different position
|
||||
* classes must be loaded in the right order
|
||||
* \param root The root node of powerup.xml
|
||||
* \param class_name The name of the position class to load.
|
||||
* \param position_class The class for which the weights are read.
|
||||
*/
|
||||
void PowerupManager::loadWeights(const XMLNode &root,
|
||||
const std::string &class_name,
|
||||
PositionClass position_class)
|
||||
{
|
||||
const XMLNode *node = root.getNode(class_name);
|
||||
std::string s("");
|
||||
if(node) node->get("w", &s);
|
||||
|
||||
if(!node || s=="")
|
||||
{
|
||||
printf("No weights found for class '%s' - probabilities will be incorrect.\n",
|
||||
class_name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> weight_list = StringUtils::split(s, ' ');
|
||||
|
||||
std::vector<std::string>::iterator i=weight_list.begin();
|
||||
while(i!=weight_list.end())
|
||||
{
|
||||
if(*i=="")
|
||||
{
|
||||
std::vector<std::string>::iterator next=weight_list.erase(i);
|
||||
i=next;
|
||||
}
|
||||
else
|
||||
i++;
|
||||
}
|
||||
// Fill missing entries with 0s
|
||||
while(weight_list.size()<(int)POWERUP_LAST)
|
||||
weight_list.push_back(0);
|
||||
|
||||
if(weight_list.size()!=(int)POWERUP_LAST)
|
||||
{
|
||||
printf("Incorrect number of weights found in class '%s':\n",
|
||||
class_name.c_str());
|
||||
printf("%d instead of %d - probabilities will be incorrect.\n",
|
||||
weight_list.size(), (int)POWERUP_LAST);
|
||||
return;
|
||||
}
|
||||
|
||||
for(unsigned int i=0; i<weight_list.size(); i++)
|
||||
{
|
||||
int w = atoi(weight_list[i].c_str());
|
||||
m_weights[position_class].push_back(w);
|
||||
}
|
||||
|
||||
} // loadWeights
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** This function set up various arrays for faster lookup later. It first
|
||||
* determines which race position correspond to which position class, and
|
||||
* then filters which powerups are available (not all powerups might be
|
||||
* available in all races). It sets up two arrays: m_position_to_class
|
||||
* which maps which race position corresponds to which position class
|
||||
* \param num_kart Number of karts.
|
||||
*/
|
||||
void PowerupManager::updateWeightsForRace(unsigned int num_karts)
|
||||
{
|
||||
m_position_to_class.clear();
|
||||
const World *world = World::getWorld();
|
||||
for(unsigned int position =1; position <= num_karts; position++)
|
||||
{
|
||||
// Set up the mapping of position to position class:
|
||||
// -------------------------------------------------
|
||||
PositionClass pos_class = convertPositionToClass(num_karts, position);
|
||||
m_position_to_class.push_back(pos_class);
|
||||
|
||||
// Then determine which items are available:
|
||||
m_powerups_for_position[pos_class].clear();
|
||||
unsigned int sum = 0;
|
||||
for(unsigned int i= POWERUP_FIRST; i<=POWERUP_LAST; i++)
|
||||
{
|
||||
PowerupType type=(PowerupType)i;
|
||||
if(!world->acceptPowerup(type)) continue;
|
||||
unsigned int w =m_weights[pos_class][i-POWERUP_FIRST];
|
||||
sum += w;
|
||||
for(unsigned int i=0; i<w; i++)
|
||||
m_powerups_for_position[pos_class].push_back(type);
|
||||
} // for type in [POWERUP_FIRST, POWERUP_LAST]
|
||||
}
|
||||
|
||||
} // updateWeightsForRace
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Determines the position class for a
|
||||
*/
|
||||
PowerupManager::PositionClass
|
||||
PowerupManager::convertPositionToClass(unsigned int num_karts,
|
||||
unsigned int position)
|
||||
{
|
||||
if(position==1) return POSITION_FIRST;
|
||||
if(position==num_karts) return POSITION_LAST;
|
||||
|
||||
// Now num_karts must be >2, since position <=num_players
|
||||
|
||||
unsigned int third = (unsigned int)floor((float)(num_karts-1)/3.0f);
|
||||
// 1 < Position <= 1+third is top33
|
||||
if(position <= 1 + third) return POSITION_TOP33;
|
||||
|
||||
// num_players-third < position is end33
|
||||
if(num_karts - third <= position) return POSITION_END33;
|
||||
|
||||
return POSITION_MID33;
|
||||
} // convertPositionToClass
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
PowerupManager::PowerupType PowerupManager::getRandomPowerup(unsigned int pos)
|
||||
{
|
||||
// Positions start with 1, while the index starts with 0 - so subtract 1
|
||||
PositionClass pos_class = m_position_to_class[pos-1];
|
||||
|
||||
int random = rand()%m_powerups_for_position[pos_class].size();
|
||||
return m_powerups_for_position[pos_class][random];
|
||||
} // getRandomPowerup
|
||||
@@ -33,6 +33,37 @@ class XMLNode;
|
||||
/**
|
||||
* \ingroup items
|
||||
*/
|
||||
|
||||
/** This class manages all powerups. It reads in powerup.xml to get the data,
|
||||
* initialise the static member of some flyables (i.e. powerup.xml contains
|
||||
* info about cakes, plunger etc which needs to be stored), and maintains
|
||||
* the 'weights' (used in randomly chosing which item was collected) for all
|
||||
* items depending on position. The latter is done so that as the first player
|
||||
* you get less advantageous items (but no useless ones either, e.g. anchor),
|
||||
* while as the last you get more useful ones.
|
||||
* The weight distribution works as follow:
|
||||
* The position in a race is mapped to one of five position classes:
|
||||
* first, top, middle, bottom, last - e.g. for a 6 player game the distribution
|
||||
* is:
|
||||
* position 1 2 3 4 5 6
|
||||
* class first top middle middle bottom last
|
||||
* For each class the weight distribution is read in from powerup.xml:
|
||||
* <!-- bubble cake bowl zipper plunger switch para anvil -->
|
||||
* <last w="0 1 1 2 2 0 2 2" />
|
||||
* So a (well, in this case 'the') player belonging to the class 'last'
|
||||
* will not get a bubble gum or switch. Cakes and bowling balls have
|
||||
* lower probability.
|
||||
* At the start of each race two mappings are computed in updateWeightsForRace:
|
||||
* m_position_to_class maps each postion to the class using the function
|
||||
* convertPositionToClass.
|
||||
* m_powerups_for_position contains a list of items for each class. A item
|
||||
* with higher weight is included more than once, so at runtime we can
|
||||
* just pick a random item from this list to get the right distribution.
|
||||
* In the example above the list for 'last' will be:
|
||||
* [cake, bowling,zipper,zipper,plunger,plunger,parachute,parachute,
|
||||
* anvil,anvil.
|
||||
*/
|
||||
|
||||
class PowerupManager
|
||||
{
|
||||
public:
|
||||
@@ -50,6 +81,15 @@ public:
|
||||
POWERUP_MAX
|
||||
};
|
||||
|
||||
/** The different position classes, used to map a kart's position to a
|
||||
* weight distribution for the different powerups. */
|
||||
enum PositionClass {POSITION_FIRST,
|
||||
POSITION_TOP33,
|
||||
POSITION_MID33,
|
||||
POSITION_END33,
|
||||
POSITION_LAST,
|
||||
POSITION_COUNT};
|
||||
|
||||
private:
|
||||
/** The icon for each powerup. */
|
||||
Material* m_all_icons [POWERUP_MAX];
|
||||
@@ -72,26 +112,46 @@ private:
|
||||
|
||||
/** For each powerup the weight (probability) used depending on the
|
||||
* number of players. */
|
||||
//std::vector<int> m_weight[POWERUP_MAX];
|
||||
std::vector<int> m_weights[POSITION_COUNT];
|
||||
|
||||
/** A list of all powerups for a specific class. If a powerup
|
||||
* has weight 5, it will be listed 5 times in this list, so
|
||||
* randomly picking an entry from this for a position class will
|
||||
* result in the right distribution of items. */
|
||||
std::vector<PowerupType> m_powerups_for_position[POSITION_COUNT];
|
||||
|
||||
/** The mapping of each position to the corresponding position class.
|
||||
* There is one map for each different number of players, so it is
|
||||
* used like m_position_to_class[number_players][position] */
|
||||
std::vector<PositionClass> m_position_to_class;
|
||||
|
||||
PowerupType getPowerupType(const std::string &name) const;
|
||||
|
||||
void loadWeights(const XMLNode &node);
|
||||
|
||||
void loadWeights(const XMLNode &root,
|
||||
const std::string &class_name,
|
||||
PositionClass position_class);
|
||||
PositionClass convertPositionToClass(unsigned int num_karts,
|
||||
unsigned int position);
|
||||
public:
|
||||
PowerupManager ();
|
||||
~PowerupManager ();
|
||||
void loadAllPowerups ();
|
||||
void removeTextures ();
|
||||
void LoadPowerup (PowerupType type, const XMLNode &node);
|
||||
Material* getIcon (int type) const {return m_all_icons [type]; }
|
||||
void updateWeightsForRace(unsigned int num_karts);
|
||||
Material* getIcon (int type) const {return m_all_icons [type];}
|
||||
PowerupManager::PowerupType
|
||||
getRandomPowerup(unsigned int pos);
|
||||
/** Returns the mesh for a certain powerup.
|
||||
* \param type Mesh type for which the model is returned. */
|
||||
irr::scene::IMesh *getMesh (int type) const {return m_all_meshes[type]; }
|
||||
float getForceToTarget(int type) const {return m_all_force_to_target[type]; }
|
||||
irr::scene::IMesh
|
||||
*getMesh (int type) const {return m_all_meshes[type];}
|
||||
float getForceToTarget(int type) const {return m_all_force_to_target[type];}
|
||||
float getMaxDistance (int type) const {return m_all_max_distance[type];}
|
||||
float getMaxTurnAngle (int type) const {return m_all_max_turn_angle[type];}
|
||||
const btVector3& getExtend (int type) const {return m_all_extends[type]; }
|
||||
const btVector3&
|
||||
getExtend (int type) const {return m_all_extends[type];}
|
||||
|
||||
};
|
||||
|
||||
extern PowerupManager* powerup_manager;
|
||||
|
||||
@@ -152,6 +152,7 @@ void World::init()
|
||||
if(!history->replayHistory()) history->initRecording();
|
||||
network_manager->worldLoaded();
|
||||
|
||||
powerup_manager->updateWeightsForRace(num_karts);
|
||||
// erase messages left over
|
||||
RaceGUI* m = World::getWorld()->getRaceGUI();
|
||||
if (m) m->clearAllMessages();
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace StringUtils
|
||||
|
||||
start=i+1;
|
||||
}
|
||||
else
|
||||
else // end of string reached
|
||||
{
|
||||
if (keepSplitChar) result.push_back(std::string(s,start-1));
|
||||
else result.push_back(std::string(s,start));
|
||||
|
||||
Reference in New Issue
Block a user