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:
hikerstk
2010-04-30 01:11:22 +00:00
parent e6e38adf59
commit e9413ccf46
7 changed files with 226 additions and 115 deletions

View File

@@ -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>

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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();

View File

@@ -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));