Improved implementation of powerup collection weight.

This commit is contained in:
hiker 2018-06-17 01:03:37 +10:00
parent 87da438bf3
commit 5207e9a38b
4 changed files with 495 additions and 542 deletions

View File

@ -74,131 +74,169 @@
force-updown="35" force-to-target="15"
max-distance="25" />
<!-- This defines the number of karts for which there is a
defined weight list. The value between two nodes will be
interpolated as a linear average of the value at each node,
except under the minimum node and above the maximum node
(in which case it stays identical to the minimum/maximum node)
The num of karts associated with each reference point should be
sorted in ascending order to guarantee correct behavior.
This doesn't apply to follow the leader, battle & tutorial weights
If the max karts number is increased, think to add values for
higher kart numbers. Otherwhise, it will default to value for 20. -->
<reference_points ref1="1" ref2="5" ref3="9" ref4="14" ref5="20" />
<!-- This defines the probabilities to get each type of item depending on
the position of the kart.
There are defined value for first kart, last kart, and
at three equally spaced points between those.
For example, with 5 karts those points will match positions 2, 3, 4.
With 10 karts, they will correspond to 3,25 ; 5,5 ; 7,75.
It is not an issue for it to be a float, as the weights for positions
are linearly averaged.
The order of items must correspond to powerup_manager.hpp.
the position of the kart and number of karts in the race.
For each race mode (race, time-trial, soccer etc) there is one
weight-list entry (e.g. race-weight-list etc). Each of those lists
contains a list (1 or more entries) of weights for a certain number
of karts in the race (which is used to reduce the frequency of
'global' items). Each of those weight tagsis stored into a
WeightsData object.
At race time, a new WeightsData object is created from the list for
the current race type depending on number of karts in the race.
If there is a WeightsData object for the same kart number, it is
copied, otherwise an interpolated WeightsData object is created
from the two WeightsData objects that are closest to the current
number of karts.
Each Weights entry itself can contain one or more weight tags:
The first entry is for the kart with rank 1, the last weight entry
for the last kart. The remaining entries are evenly distributed
between the first and last kart (exception is follow-the-leader,
where the first entry is for the leader, the second entry for
the first non-leader kart, and the last entry for the last
kart. So the 3rd till second-last entries are distributed
evenly instead of the 2nd till second-last).
The first line (w1=...) corresponds to the weights of
getting a specific item, the second line (w-multi1) to
the weight at which it will yeld a triple item
rather than a single one
w1 corresponds to the weights at ref1, w2 to the weights at ref2, etc.
When the number of karts in the race fall between refi and refj,
the weights used are a weighted average between wi and wj.
For example, with 5 karts and 5 entries those points will
match positions 2, 3, 4. With 10 karts (and 5 entries), they
will correspond to 3,25 ; 5,5 ; 7,75. It is not an issue if
the kart number is not an integer value, since the actual
weights will be interpolated.
wf and w-multif are the weights associated with the follow-the-leader
mode. Those are the same for all kart numbers.
The probability to get an item is its weight divided by
sum of weights of all items (single AND multi). It is recommended
to keep that sum equal to 200 to easily keep track of probabilities.
The order of items must correspond to powerup_manager.hpp.
Each weight entry contains two list of integer values that
represent the probability that a particular item is picked
(the integer weight is divided by the sum of all weights to
get the actual probability). The first line (single=...)
corresponds to the weights of getting one specific item,
the second line (multi) to the weight at which it will yeld
a triple item rather than a single one. The probability to get
an item is its weight divided by sum of weights of all items
(single AND multi). It is recommended to keep that sum equal
to 200 to easily keep track of probabilities.
'Global' items which affect all karts (switch, parachute) should
be quite rare, since otherwise the item might be used
too often (compared with many items which will only
affect a karts or two).
affect a karts or two) - especially with increasing number of
karts in a race.
The distribution should give more similar items to different ranks
when there are few karts, but have more important differences when
there are more karts. -->
<!-- bubble cake bowl zipper plunger switch swattr rubber para anvil -->
<first w1="28 0 60 20 45 15 32 0 0 0"
w-multi1=" 0 0 0 0 0 0 0 0 0 0"
w2="26 16 52 15 46 12 33 0 0 0"
w-multi2=" 0 0 0 0 0 0 0 0 0 0"
w3="24 12 58 10 54 8 34 0 0 0"
w-multi3=" 0 0 0 0 0 0 0 0 0 0"
w4="22 8 64 5 60 6 35 0 0 0"
w-multi4=" 0 0 0 0 0 0 0 0 0 0"
w5="20 0 74 0 66 4 36 0 0 0"
w-multi5=" 0 0 0 0 0 0 0 0 0 0"
wf="35 0 25 35 25 15 25 0 0 0"
w-multif="20 0 0 20 0 0 0 0 0 0" />
<!-- bubble cake bowl zipper plunger switch swattr rubber para anvil -->
<top33 w1="30 0 59 26 40 14 27 0 0 0"
w-multi1=" 0 0 4 0 0 0 0 0 0 0"
w2="30 27 48 25 28 10 27 0 0 0"
w-multi2=" 0 0 5 0 0 0 0 0 0 0"
w3="29 30 45 24 32 7 27 0 0 0"
w-multi3=" 0 0 6 0 0 0 0 0 0 0"
w4="28 31 48 22 34 4 27 0 0 0"
w-multi4=" 0 0 6 0 0 0 0 0 0 0"
w5="27 32 48 20 37 3 27 0 0 0"
w-multi5=" 0 0 6 0 0 0 0 0 0 0"
wf="25 0 60 25 58 2 30 0 0 0"
w-multif=" 0 0 0 0 0 0 0 0 0 0" />
<race-weight-list>
<!-- The entry for '0' karts is special and means 'first kart' -->
<weights num-karts="1">
<!-- bubble cake bowl zipper plunger switch swattr rubber para anvil -->
<weight single ="28 0 60 20 45 15 32 0 0 0"
multi =" 0 0 0 0 0 0 0 0 0 0" />
<weight single ="30 0 59 26 40 14 27 0 0 0"
multi =" 0 0 4 0 0 0 0 0 0 0" />
<weight single ="30 0 62 27 36 13 27 0 0 0"
multi =" 0 0 5 0 0 0 0 0 0 0" />
<weight single ="31 0 56 36 34 12 25 0 0 0"
multi =" 0 0 6 0 0 0 0 0 0 0" />
<weight single ="34 0 36 55 30 10 17 0 0 0"
multi =" 0 0 18 0 0 0 0 0 0 0" />
</weights>
<!-- bubble cake bowl zipper plunger switch swattr rubber para anvil -->
<mid33 w1="30 0 62 27 36 13 27 0 0 0"
w-multi1=" 0 0 5 0 0 0 0 0 0 0"
w2="30 27 45 27 27 9 27 3 0 0"
w-multi2=" 0 0 5 0 0 0 0 0 0 0"
w3="30 26 41 28 26 6 26 10 0 0"
w-multi3=" 0 0 7 0 0 0 0 0 0 0"
w4="30 25 42 29 29 3 24 10 0 0"
w-multi4=" 0 0 8 0 0 0 0 0 0 0"
w5="30 24 40 30 28 2 21 10 0 0"
w-multi5=" 0 0 10 0 5 0 0 0 0 0"
wf="35 0 55 35 25 3 25 0 0 0"
w-multif=" 0 0 10 0 12 0 0 0 0 0" />
<weights num-karts="5">
<!-- bubble cake bowl zipper plunger switch swattr rubber para anvil -->
<weight single ="26 16 52 15 46 12 33 0 0 0"
multi =" 0 0 0 0 0 0 0 0 0 0" />
<weight single ="30 27 48 25 28 10 27 0 0 0"
multi =" 0 0 5 0 0 0 0 0 0 0" />
<weight single ="30 27 45 27 27 9 27 3 0 0"
multi =" 0 0 5 0 0 0 0 0 0 0" />
<weight single ="32 24 28 38 22 7 20 16 6 0"
multi =" 0 0 7 0 0 0 0 0 0 0" />
<weight single ="28 21 9 45 0 6 0 10 18 0"
multi =" 8 0 16 35 4 0 0 0 0 0" />
</weights>
<weights num-karts="9">
<!-- bubble cake bowl zipper plunger switch swattr rubber para anvil -->
<weight single ="24 12 58 10 54 8 34 0 0 0"
multi =" 0 0 0 0 0 0 0 0 0 0" />
<weight single ="29 30 45 24 32 7 27 0 0 0"
multi =" 0 0 6 0 0 0 0 0 0 0" />
<weight single ="30 26 41 28 26 6 26 10 0 0"
multi =" 0 0 7 0 0 0 0 0 0 0" />
<weight single ="33 23 26 45 14 5 16 12 8 0"
multi =" 0 0 12 0 6 0 0 0 0 0" />
<weight single ="20 16 7 37 0 3 0 4 15 0"
multi ="18 0 18 58 4 0 0 0 0 0" />
</weights>
<weights num-karts="14">
<!-- bubble cake bowl zipper plunger switch swattr rubber para anvil -->
<weight single ="22 8 64 5 60 6 35 0 0 0"
multi =" 0 0 0 0 0 0 0 0 0 0" />
<weight single ="28 31 48 22 34 4 27 0 0 0"
multi =" 0 0 6 0 0 0 0 0 0 0" />
<weight single ="30 25 42 29 29 3 24 10 0 0"
multi =" 0 0 8 0 0 0 0 0 0 0" />
<weight single ="27 21 23 44 12 3 14 8 6 0"
multi =" 8 0 16 8 10 0 0 0 0 0" />
<weight single ="18 14 3 35 0 0 0 0 10 0"
multi ="24 0 25 65 6 0 0 0 0 0" />
</weights>
<weights num-karts="20">
<!-- bubble cake bowl zipper plunger switch swattr rubber para anvil -->
<weight single ="20 0 74 0 66 4 36 0 0 0"
multi =" 0 0 0 0 0 0 0 0 0 0" />
<weight single ="27 32 48 20 37 3 27 0 0 0"
multi =" 0 0 6 0 0 0 0 0 0 0" />
<weight single ="30 24 40 30 28 2 21 10 0 0"
multi =" 0 0 10 0 5 0 0 0 0 0" />
<weight single ="25 18 20 50 10 2 10 6 3 0"
multi ="10 0 20 10 16 0 0 0 0 0" />
<weight single ="15 12 0 25 0 0 0 0 7 0"
multi ="30 0 31 80 0 0 0 0 0 0" />
</weights>
</race-weight-list>
<!-- bubble cake bowl zipper plunger switch swattr rubber para anvil -->
<end33 w1="31 0 56 36 34 12 25 0 0 0"
w-multi1=" 0 0 6 0 0 0 0 0 0 0"
w2="32 24 28 38 22 7 20 16 6 0"
w-multi2=" 0 0 7 0 0 0 0 0 0 0"
w3="33 23 26 45 14 5 16 12 8 0"
w-multi3=" 0 0 12 0 6 0 0 0 0 0"
w4="27 21 23 44 12 3 14 8 6 0"
w-multi4=" 8 0 16 8 10 0 0 0 0 0"
w5="25 18 20 50 10 2 10 6 3 0"
w-multi5="10 0 20 10 16 0 0 0 0 0"
wf="25 0 40 45 15 5 15 10 5 0"
w-multif="10 0 15 15 0 0 0 0 0 0" />
<ftl-weight-list>
<weights num-karts="1">
<!-- bubble cake bowl zipper plunger switch swattr rubber para anvil -->
<!-- This is the entry for the leader: -->
<weight single ="35 0 25 35 25 15 25 0 0 0"
multi ="20 0 0 20 0 0 0 0 0 0" />
<!-- This is the entry for the first non-leader karts: -->
<weight single ="25 0 60 25 58 2 30 0 0 0"
multi =" 0 0 0 0 0 0 0 0 0 0" />
<weight single ="35 0 55 35 25 3 25 0 0 0"
multi =" 0 0 10 0 12 0 0 0 0 0" />
<weight single ="25 0 40 45 15 5 15 10 5 0"
multi ="10 0 15 15 0 0 0 0 0 0" />
<!-- This is the entry for the last kart: -->
<weight single ="20 0 15 25 0 0 0 0 15 0"
multi ="20 0 25 80 0 0 0 0 0 0" />
</weights>
</ftl-weight-list>
<battle-weight-list>
<weights num-karts="1">
<!-- bubble cake bowl zipper plunger switch swattr rubber para anvil -->
<weight single ="10 30 60 0 0 10 30 0 0 0"
multi =" 0 0 5 0 0 0 0 0 0 0" />
</weights>
</battle-weight-list>
<!-- bubble cake bowl zipper plunger switch swattr rubber para anvil -->
<last w1="34 0 36 55 30 10 17 0 0 0"
w-multi1=" 0 0 18 0 0 0 0 0 0 0"
w2="28 21 9 45 0 6 0 10 18 0"
w-multi2=" 8 0 16 35 4 0 0 0 0 0"
w3="20 16 7 37 0 3 0 4 15 0"
w-multi3="18 0 18 58 4 0 0 0 0 0"
w4="18 14 3 35 0 0 0 0 10 0"
w-multi4="24 0 25 65 6 0 0 0 0 0"
w5="15 12 0 25 0 0 0 0 7 0"
w-multi5="30 0 31 80 0 0 0 0 0 0"
wf="20 0 15 25 0 0 0 0 15 0"
w-multif="20 0 25 80 0 0 0 0 0 0" />
<soccer-weight-list>
<weights num-karts="1">
<!-- bubble cake bowl zipper plunger switch swattr rubber para anvil -->
<weight single =" 0 30 60 0 0 10 30 0 0 0"
multi =" 0 0 5 0 0 0 0 0 0 0" />
</weights>
</soccer-weight-list>
<!-- bubble cake bowl zipper plunger switch swattr rubber para anvil -->
<battle w="10 30 60 0 0 10 30 0 0 0"
w-multi=" 0 0 5 0 0 0 0 0 0 0" />
<soccer w=" 0 30 60 0 0 10 30 0 0 0"
w-multi=" 0 0 5 0 0 0 0 0 0 0" />
<tuto w=" 0 0 0 0 0 0 0 0 0 0"
w-multi=" 0 0 100 0 0 0 0 0 0 0" />
<tutorial-weight-list>
<weights num-karts="0">
<!-- bubble cake bowl zipper plunger switch swattr rubber para anvil -->
<weight single =" 0 0 0 0 0 0 0 0 0 0"
multi =" 0 0 100 0 0 0 0 0 0 0" /> />
</weights>
</tutorial-weight-list>
</powerup>

688
src/items/powerup_manager.cpp Executable file → Normal file
View File

@ -67,6 +67,12 @@ PowerupManager::~PowerupManager()
irr_driver->removeMeshFromCache(mesh);
}
}
for(auto key: m_all_weights)
{
for(auto p: key.second )
delete p;
}
} // ~PowerupManager
//-----------------------------------------------------------------------------
@ -132,34 +138,255 @@ void PowerupManager::loadPowerupsModels()
exit(-1);
}
}
loadWeights(root, "race-weight-list" );
loadWeights(root, "ftl-weight-list" );
loadWeights(root, "battle-weight-list" );
loadWeights(root, "soccer-weight-list" );
loadWeights(root, "tutorial-weight-list");
delete root;
} // loadPowerupsModels
//-----------------------------------------------------------------------------
/** Loads all powerups weights from the powerup.xml file.
* Uses num_karts to get the good weights
/** Loads the powerups weights for a given category (race, ft, ...). The data
* is stored in m_all_weights.
* \param node The top node of the powerup xml file.
* \param class_name The name of the attribute with the weights for the
* class.
*/
void PowerupManager::loadAllPowerups(unsigned int num_karts)
void PowerupManager::loadWeights(const XMLNode *powerup_node,
const std::string &class_name)
{
const std::string file_name = file_manager->getAsset("powerup.xml");
XMLNode *root = file_manager->createXMLTree(file_name);
const XMLNode *node = powerup_node->getNode(class_name);
if(!node)
{
Log::fatal("PowerupManager",
"Cannot find node '%s' in powerup.xml file.",
class_name.c_str());
}
//loadWeights takes care of loading specific weight in follow-the-leader
//They also vary depending on rank in race, so they use the usual position classes
loadWeights(*root, num_karts, "first", POSITION_FIRST );
loadWeights(*root, num_karts, "top33", POSITION_TOP33 );
loadWeights(*root, num_karts, "mid33", POSITION_MID33 );
loadWeights(*root, num_karts, "end33", POSITION_END33 );
loadWeights(*root, num_karts, "last" , POSITION_LAST );
loadWeights(*root, num_karts, "battle" , POSITION_BATTLE_MODE);
loadWeights(*root, num_karts, "soccer" , POSITION_SOCCER_MODE);
loadWeights(*root, num_karts, "tuto", POSITION_TUTORIAL_MODE);
for (unsigned int i = 0; i < node->getNumNodes(); i++)
{
const XMLNode *weights = node->getNode(i);
int num_karts;
weights->get("num-karts", &num_karts);
WeightsData *wd = new WeightsData();
wd->readData(num_karts, weights);
m_all_weights[class_name].push_back(wd);
} // for i in node->getNumNodes
delete root;
} // loadWeights
} // loadAllPowerups
// ============================================================================
// Implement of WeightsData
/** Deletes all data stored in a WeightsData objects.
*/
void PowerupManager::WeightsData::reset()
{
m_weights_for_section.clear();
m_summed_weights_for_rank.clear();
m_num_karts = 0;
} // reset
//-----------------------------------------------------------------------------
/** Reads in all weights for a given category and number of karts.
* \param num_karts Number of karts for this set of data.
* \param node The XML node with the data to read.
*/
void PowerupManager::WeightsData::readData(int num_karts, const XMLNode *node)
{
m_num_karts = num_karts;
for (unsigned int i = 0; i < node->getNumNodes(); i++)
{
m_weights_for_section.emplace_back();
const XMLNode *w = node->getNode(i);
std::string single_item;
w->get("single", &single_item);
std::string multi_item;
w->get("multi", &multi_item);
std::vector<std::string> l_string =
StringUtils::split(single_item+" "+multi_item,' ');
// Keep a reference for shorter access to the list
std::vector<int> &l = m_weights_for_section.back();
for(unsigned int i=0; i<l_string.size(); i++)
{
if(l_string[i]=="") continue;
int n;
StringUtils::fromString(l_string[i], n);
l.push_back(n);
}
// Make sure we have the right number of entries
while(l.size()<2*(int)POWERUP_LAST) l.push_back(0);
if(l.size()>2*(int)POWERUP_LAST)
{
Log::error("PowerupManager",
"Too many entries for '%s' in powerup.xml.",
node->getName().c_str());
}
} // for i in getNumNodes()
} // WeightsData::readData
// ----------------------------------------------------------------------------
/** Defines the weights for this WeightsData object based on a linear
* interpolation between the previous and next WeightsData class (depending
* on the number of karts in this race and in previous and next).
* \param prev The WeightsData object for less karts.
* \param next The WeightData object for more karts.
* \param num_karts Number of karts to extrapolate for.
*/
void PowerupManager::WeightsData::interpolate(WeightsData *prev,
WeightsData *next, int num_karts)
{
m_num_karts = num_karts;
m_weights_for_section.clear();
float f = float(num_karts - prev->getNumKarts())
/ (next->getNumKarts() - prev->getNumKarts());
// Outer loop over all classes. Note that 'this' is empty atm, but
// since all WeightsData have the same number of elements, we use
// the previous one for loop boundaries and push_back the interpolated
// values to 'this'.
for (unsigned int cl = 0; cl < prev->m_weights_for_section.size(); cl++)
{
std::vector<int> & w_prev = prev->m_weights_for_section[cl];
std::vector<int> & w_next = next->m_weights_for_section[cl];
m_weights_for_section.emplace_back();
std::vector<int> &l = m_weights_for_section.back();
for (unsigned int i = 0; i < w_prev.size(); i++)
{
float interpolated_weight = w_prev[i] * f + w_next[i] * (1 - f);
l.push_back(int(interpolated_weight + 0.5f));
}
} // for l < prev->m_weights_for_section.size()
} // WeightsData::interpolate
// ----------------------------------------------------------------------------
/** For a given rank in the current race this computed the previous and
* next entry in the weight list, and the weight necessary to interpolate
* between these two values. If the requested rank should exactly match
* one entries, previous and next entry will be identical, and weight set
* to 1.0.
* \param rank Rank that is to be interpolated.
* \param prev On return contains the index of the closest weight field
* smaller than the given rank.
* \param next On return contains the index of the closest weight field
* bigger than the given rank.
* \param weight On return contains the weight to use to interpolate between
* next and previous.
*/
int PowerupManager::WeightsData::convertRankToSection(int rank, int *prev,
int *next, float *weight)
{
// If there is only one section (e.g. in soccer mode etc), use it.
// If the rank is first, always use the first entry as well.
if (m_weights_for_section.size() == 1 || rank == 1)
{
*prev = *next = 0;
*weight = 1.0f;
return 1;
}
// The last kart always uses the data for the last section
if (rank == m_num_karts)
{
*prev = *next = m_weights_for_section.size() - 1;
*weight = 1.0f;
return 1;
}
// In FTL mode the first section is for the leader, the
// second section is used for the first non-leader kart.
if (race_manager->isFollowMode() && rank == 2)
{
*prev = *next = 1;
*weight = 1.0f;
return 1;
}
// Now we have a rank that needs to be interpolated between
// two sections.
// Get the first index that is used for a section (FTL is
// special since index 2 is for the first non-leader kart):
int first_section_index = race_manager->isFollowMode() ? 2 : 1;
// Get the lowest rank for which the grouping applies:
int first_section_rank = race_manager->isFollowMode() ? 3 : 2;
// If we have three points, we get 4 sections etc.
int num_sections = (m_weights_for_section.size() - first_section_index);
float karts_per_fraction = (m_num_karts - first_section_index)
/ float(num_sections);
int count = 0;
while (rank - 1 > (count + 1) * karts_per_fraction)
{
count++;
}
*prev = first_section_index + count - 1;
*next = *prev + 1;
*weight = (rank - first_section_index - count * karts_per_fraction)
/ karts_per_fraction;
return 1;
} // WeightsData::convertRankToSection
// ----------------------------------------------------------------------------
/** This function computes the item distribution for each possible rank in the
* race. It creates a list which sums for each item the weights of all
* previous items. E.g. if the weight list starts with 20, 30, 0, 10,
* the summed array will contains 20, 50, 50, 60. This allows for a quick
* look up based on a single random number.
*/
void PowerupManager::WeightsData::precomputeWeights()
{
m_summed_weights_for_rank.clear();
for (unsigned int i = 0; i<m_num_karts; i++)
{
m_summed_weights_for_rank.emplace_back();
int prev, next;
float weight;
convertRankToSection(i + 1, &prev, &next, &weight);
int section = i;
int sum = 0;
for (unsigned int j = 0;
j <= 2 * POWERUP_LAST - POWERUP_FIRST; j++)
{
float av = (1.0f - weight) * m_weights_for_section[prev][j]
+ weight * m_weights_for_section[next][j];
sum += int(av + 0.5f);
m_summed_weights_for_rank[i].push_back(sum);
}
}
} // WeightsData::precomputeWeights
//-----------------------------------------------------------------------------
/** Computes a random item dependent on the rank of the kart and a given
* random number.
* \param rank The rank for which an item needs to be picked.
* \param random_number A random number used to 'randomly' select the item
* that was picked.
*/
int PowerupManager::WeightsData::getRandomItem(int rank, int random_number)
{
// E.g. for battle mode with only one entry
if(rank>(int)m_summed_weights_for_rank.size())
rank = m_summed_weights_for_rank.size()-1;
else if (rank<0) rank = 0; // E.g. battle mode
const std::vector<int> &summed_weights = m_summed_weights_for_rank[rank];
// The last entry is the sum of all previous entries, i.e. the maximum
// value
random_number = random_number % summed_weights.back();
int powerup = 0;
while ( random_number > summed_weights[powerup] )
powerup++;
return powerup+POWERUP_FIRST;
} // WeightsData::getRandomItem
// ============================================================================
/** Loads the data for one particular powerup. For bowling ball, plunger, and
* cake static members in the appropriate classes are called to store
* additional information for those objects.
@ -222,375 +449,67 @@ void PowerupManager::LoadPowerup(PowerupType type, const XMLNode &node)
} // LoadNode
// ----------------------------------------------------------------------------
/** Loads a weight list specified in powerup.xml.
* Calculates a linear average of the weight specified for the reference points
* of num_karts higher and lower (unless it exactly matches one)
* \param root The root node of powerup.xml
* \param num_karts The number of karts in the race
* \param class_name The name of the position class to load.
* \param position_class The class for which the weights are read.
/** Create a (potentially interpolated) WeightsData objects for the current
* race based on the number of karts.
* \param num_karts Number of karts in the current race.
*/
void PowerupManager::loadWeights(const XMLNode &root,
unsigned int num_karts,
const std::string &class_name,
PositionClass position_class)
void PowerupManager::computeWeightsForRace(int num_karts)
{
const XMLNode *class_node = root.getNode(class_name), *reference_points_node = root.getNode("reference_points");
std::string ref;
// Those are the strings which will contain the names of the nodes to load inside the class node
std::string w_inf("w"), w_sup("w"), w_inf_multi("w-multi"), w_sup_multi("w-multi");
// Those are the strings where the raw weight values will be put when read from the XML nodes
std::string values_inf(""), values_inf_multi(""), values_sup(""), values_sup_multi("");
int inf_num = 1, sup_num = 1; //The number of karts associated with the reference points
if (num_karts == 0) return;
/** Adds to w_inf and w_sup the suffixe of the closest num_karts reference classes
*/
if(position_class!=POSITION_BATTLE_MODE &&
position_class!=POSITION_TUTORIAL_MODE &&
position_class!=POSITION_SOCCER_MODE)
std::string class_name="";
switch (race_manager->getMinorMode())
{
//First step : get the reference points
case RaceManager::MINOR_MODE_TIME_TRIAL: /* fall through */
case RaceManager::MINOR_MODE_NORMAL_RACE: class_name="race"; break;
case RaceManager::MINOR_MODE_FOLLOW_LEADER: class_name="ftl"; break;
case RaceManager::MINOR_MODE_3_STRIKES: class_name="battle"; break;
case RaceManager::MINOR_MODE_EASTER_EGG: /* fall through */
case RaceManager::MINOR_MODE_SOCCER: class_name="soccer"; break;
}
class_name +="-weight-list";
if(!reference_points_node)
std::vector<WeightsData*> wd = m_all_weights[class_name];
// Find the two indices closest to the current number of karts
// so that the right number can be interpolated between the
// two values.
int prev_index=-1, next_index=-1;
for (unsigned int i = 0; i < wd.size(); i++)
{
int n = wd[i]->getNumKarts();
if (n <= num_karts &&
(prev_index < 0 || n > wd[prev_index]->getNumKarts()) )
{
Log::error("[PowerupManager]","No reference points found, "
"probabilities will be incorrect");
prev_index = i;
}
if (reference_points_node)
if (n >= num_karts &&
(next_index < 0 || n < wd[next_index]->getNumKarts()))
{
if(race_manager->isFollowMode())
{
w_inf = w_sup = "wf";
}
else
{
int i = 0;
while(1)
{
i++;
ref = "ref" + StringUtils::toString(i);
std::string ref_pos;
if (!reference_points_node->get(ref, &ref_pos))
break;
unsigned int ref_value;
if (!StringUtils::parseString(ref_pos.c_str(), &ref_value))
{
Log::error("[PowerupManager]","Incorrect reference point values, "
"probabilities will be incorrect");
continue;
}
if (ref_value <= num_karts)
{
inf_num = ref_value;
//Useful if config is edited so num_kart > highest reference position
sup_num = ref_value;
w_inf = "w" + StringUtils::toString(i);
w_inf_multi = "w-multi" + StringUtils::toString(i);
}
//No else if, it can be equal
if (ref_value >= num_karts)
{
sup_num = ref_value;
w_sup = w_sup + StringUtils::toString(i);
w_sup_multi = w_sup_multi + StringUtils::toString(i);
break;
}
}
} //else
}
if(w_inf=="w" || w_sup=="w")
{
w_inf = "w3"; //fallback values
w_inf_multi = "w-multi3";//fallback values
w_sup = "w3"; //fallback values
w_sup_multi = "w-multi3";//fallback values
inf_num = sup_num = num_karts;//fallback values
Log::error("[PowerupManager]","Uncorrect reference points in powerup.xml."
"%d karts - fallback probabilities will be used.",
num_karts);
}
// Now w_inf and w_sup contain the weight classes to use
// for the weights calculations
}
//Second step : load the class_node values
if(class_node)
{
class_node->get(w_inf, &values_inf );
class_node->get(w_inf_multi, &values_inf_multi );
class_node->get(w_sup, &values_sup );
class_node->get(w_sup_multi, &values_sup_multi );
}
if(!class_node || values_inf=="" || values_inf_multi==""
|| values_sup=="" || values_sup_multi=="")
{
Log::error("[PowerupManager]", "No weights found for class '%s'"
" - probabilities will be incorrect.",
class_name.c_str());
return;
}
//Third step : split in discrete values
std::vector<std::string> weight_list_inf = StringUtils::split(values_inf+" "+values_inf_multi,' ');
std::vector<std::string> weight_list_sup = StringUtils::split(values_sup+" "+values_sup_multi,' ');
std::vector<std::string>::iterator i=weight_list_inf.begin();
while(i!=weight_list_inf.end())
{
if(*i=="")
{
std::vector<std::string>::iterator next=weight_list_inf.erase(i);
i=next;
}
else
i++;
}
i=weight_list_sup.begin();
while(i!=weight_list_sup.end())
{
if(*i=="")
{
std::vector<std::string>::iterator next=weight_list_sup.erase(i);
i=next;
}
else
i++;
}
// Fill missing entries with 0s
while(weight_list_inf.size()<2*(int)POWERUP_LAST)
weight_list_inf.push_back(0);
while(weight_list_sup.size()<2*(int)POWERUP_LAST)
weight_list_sup.push_back(0);
//This also tests that the two lists are of equal size
if(weight_list_inf.size()!=2*(int)POWERUP_LAST ||
weight_list_sup.size()!=2*(int)POWERUP_LAST)
{
Log::error("[PowerupManager]", "Incorrect number of weights found in class '%s':",
class_name.c_str());
Log::error("[PowerupManager]", "%d and %d instead of twice %d - probabilities will be incorrect.",
(int)weight_list_inf.size(), (int)weight_list_sup.size(), (int)POWERUP_LAST);
return;
}
//Fourth step : finally compute the searched values
for(unsigned int i=0; i<2*(int)POWERUP_LAST; i++)
{
int weight_inf, weight_sup;
if (!StringUtils::parseString(weight_list_inf[i].c_str(), &weight_inf))
{
Log::error("[PowerupManager]","Incorrect powerup weight values, "
"probabilities will be incorrect");
weight_inf = 0;//default to zero
}
if (!StringUtils::parseString(weight_list_sup[i].c_str(), &weight_sup))
{
Log::error("[PowerupManager]","Incorrect powerup weight values, "
"probabilities will be incorrect");
weight_sup = 0;
}
//Do a linear average of the inf and sup values
//Use float calculations to reduce the rounding errors
float ref_diff = (float) (sup_num - inf_num);
float sup_diff = (float) (sup_num - num_karts);
float inf_diff = (float) (num_karts - inf_num);
float weight;
if (ref_diff != 0)
weight = (sup_diff/ref_diff)*weight_inf
+(inf_diff/ref_diff)*weight_sup;
//the sup and inf weights are the same here, take one of them
else
weight = weight_inf;
int rounded_weight = (int) weight;
m_weights[position_class].push_back(rounded_weight);
}
} // 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_[inf/sup]
* which maps which race position corresponds to which position class
* \param num_kart Number of karts.
*/
void PowerupManager::updateWeightsForRace(unsigned int num_karts)
{
if (num_karts == 0)
return;
//This loads the appropriate weights
loadAllPowerups(num_karts);
// In battle mode no positions exist, so use only position 1
unsigned int end_position = (race_manager->isBattleMode() ||
race_manager->isSoccerMode()) ? 1 : num_karts;
m_position_to_class_inf.clear();
m_position_to_class_sup.clear();
m_position_to_class_cutoff.clear();
for(unsigned int position =1; position <= end_position; position++)
{
// Set up the mapping of position to position class,
// Using a linear average of the superior and inferor position class
// -------------------------------------------------
m_position_to_class_inf.push_back(convertPositionToClass(num_karts, position, false));
m_position_to_class_sup.push_back(convertPositionToClass(num_karts, position, true));
m_position_to_class_cutoff.push_back(convertPositionToClassWeight(num_karts, position));
}
// Then determine which items are available.
if (race_manager->isBattleMode())
updatePowerupClass(POSITION_BATTLE_MODE);
else if(race_manager->isSoccerMode())
updatePowerupClass(POSITION_SOCCER_MODE);
else if(race_manager->isTutorialMode())
updatePowerupClass(POSITION_TUTORIAL_MODE);
else
{
//TODO : replace this by a nice loop
updatePowerupClass(POSITION_FIRST);
updatePowerupClass(POSITION_TOP33);
updatePowerupClass(POSITION_MID33);
updatePowerupClass(POSITION_END33);
updatePowerupClass(POSITION_LAST);
}
} // updateWeightsForRace
// ----------------------------------------------------------------------------
/** This function update the powerup array of a position class
* \param pos_class The position class to update
*/
void PowerupManager::updatePowerupClass(PowerupManager::PositionClass pos_class)
{
m_powerups_for_reference_pos[pos_class].clear();
// This loop actually goes over the powerups twice: first the single item version,
// then the multi-item version. The assignment to type takes care of this
for(unsigned int i= POWERUP_FIRST; i<=2*POWERUP_LAST; i++)
{
PowerupType type =
(PowerupType) ((i<=POWERUP_LAST) ? i
: i+POWERUP_FIRST);
unsigned int w =m_weights[pos_class][i-POWERUP_FIRST];
for(unsigned int j=0; j<w; j++)
m_powerups_for_reference_pos[pos_class].push_back(type);
} // for type in [POWERUP_FIRST, POWERUP_LAST]
} //updatePowerupClass
// ----------------------------------------------------------------------------
/** Determines the position class for a given position. If the race is a
* battle mode (in which case we don't have a position), always return
* 'POSITION_BATTLE_MODE' (and in this case only position 1 will be used
* for all karts).
* \param num_karts Number of karts in the race.
* \param position The position for which to determine the position class.
* \param class_sup true if it should send a class matching a >= position
* false if it should match a <= position
*/
PowerupManager::PositionClass
PowerupManager::convertPositionToClass(unsigned int num_karts,
unsigned int position, bool class_sup)
{
if(race_manager->isBattleMode()) return POSITION_BATTLE_MODE;
if(race_manager->isSoccerMode()) return POSITION_SOCCER_MODE;
if(race_manager->isTutorialMode()) return POSITION_TUTORIAL_MODE;
if(position==1) return POSITION_FIRST;
if(position==num_karts) return POSITION_LAST;
// Now num_karts must be >=3, since position <=num_players
//Special case for Follow the Leader : top33 is mapped to the first non-leader kart
if (race_manager->isFollowMode())
{
float third = (float) (num_karts-2)/3.0f;
if(position == 2) return POSITION_TOP33;
if (class_sup)
{
if(position <= 2 + third) return POSITION_MID33;
else if(position <= 2 + 2*third) return POSITION_END33;
else return POSITION_LAST;
}
else
{
if(position >= 2 + 2*third) return POSITION_END33;
else if(position >= 2 + third) return POSITION_MID33;
else return POSITION_TOP33;
next_index = i;
}
}
// For battle mode etc where we only have one index
if(prev_index < 0) prev_index = 0;
if(next_index < 0) next_index = 0;
//Three points divide a line in 4 sections
float quarter = (float) (num_karts-1)/4.0f;
if (class_sup)
// Check if we have exactly one entry (e.g. either class with only one
// set of data specified, or an exact match):
m_current_item_weights.reset();
if(prev_index == next_index)
{
if(position <= 1 + quarter) return POSITION_TOP33;
else if(position <= 1 + 2*quarter) return POSITION_MID33;
else if(position <= 1 + 3*quarter) return POSITION_END33;
else return POSITION_LAST;
// Just create a copy of this entry:
m_current_item_weights = *wd[prev_index];
m_current_item_weights.setNumKarts(num_karts);
}
else
{
if(position >= 1 + 3*quarter) return POSITION_END33;
else if(position >= 1 + 2*quarter) return POSITION_MID33;
else if(position >= 1 + 1*quarter) return POSITION_TOP33;
else return POSITION_FIRST;
// We need to interpolate between prev_index and next_index
m_current_item_weights.interpolate(wd[prev_index], wd[next_index],
num_karts );
}
} // convertPositionToClass
// ----------------------------------------------------------------------------
/** Computes the proportion of the inf position class for a given position.
* \param num_karts Number of karts in the race.
* \param position The position for which to determine the proportion.
*/
unsigned int PowerupManager::convertPositionToClassWeight(unsigned int num_karts,
unsigned int position)
{
if(race_manager->isBattleMode() || race_manager->isSoccerMode() ||
race_manager->isTutorialMode() || position==1 || position==num_karts)
return 1;
// Now num_karts must be >=3, since position <=num_players
//Special case for Follow the Leader : top33 is mapped to the first non-leader kart
if (race_manager->isFollowMode())
{
float third = (float) (num_karts-2)/3.0f;
if(position == 2) return 1;
if(position <= 2 + third) return (int) RAND_CLASS_RANGE-((RAND_CLASS_RANGE*(position - 2))/third);
else if(position <= 2 + 2*third) return (int) RAND_CLASS_RANGE-((RAND_CLASS_RANGE*(position - (2+third)))/third);
else return (int) RAND_CLASS_RANGE-((RAND_CLASS_RANGE*(position - (2+2*third)))/third);
}
//Three points divide a line in 4 sections
float quarter = (float) (num_karts-1)/4.0f;
if(position <= 1 + quarter) return (int) RAND_CLASS_RANGE-((RAND_CLASS_RANGE*(position - 1))/quarter);
else if(position <= 1 + 2*quarter) return (int) RAND_CLASS_RANGE-((RAND_CLASS_RANGE*(position - (1+quarter)))/quarter);
else if(position <= 1 + 3*quarter) return (int) RAND_CLASS_RANGE-((RAND_CLASS_RANGE*(position - (1+2*quarter)))/quarter);
else return (int) RAND_CLASS_RANGE-(RAND_CLASS_RANGE*((position - (1+3*quarter)))/quarter);
} // convertPositionToClassWeight
m_current_item_weights.precomputeWeights();
} // computeWeightsForRace
// ----------------------------------------------------------------------------
/** Returns a random powerup for a kart at a given position. If the race mode
@ -608,30 +527,17 @@ PowerupManager::PowerupType PowerupManager::getRandomPowerup(unsigned int pos,
unsigned int *n,
int random_number)
{
int random_class = random_number%RAND_CLASS_RANGE;
//First step : select at random a class according to the weights of class for this position
// Positions start with 1, while the index starts with 0 - so subtract 1
PositionClass pos_class =
(race_manager->isBattleMode() ? POSITION_BATTLE_MODE :
race_manager->isSoccerMode() ? POSITION_SOCCER_MODE :
race_manager->isTutorialMode() ? POSITION_TUTORIAL_MODE :
random_class > m_position_to_class_cutoff[pos-1] ? m_position_to_class_inf[pos-1] :
m_position_to_class_sup[pos-1]);
//Second step : select an item at random
int random = random_number % m_powerups_for_reference_pos[pos_class].size();
int i = m_powerups_for_reference_pos[pos_class][random];
//int random_item = (random / RAND_CLASS_RANGE) % m_powerups_for_reference_pos[pos_class].size();
//int i=m_powerups_for_reference_pos[pos_class][random_item];
if(i>=POWERUP_MAX)
int powerup = m_current_item_weights.getRandomItem(pos-1, random_number);
#ifdef ITEM_DISTRIBUTION_DEBUG
Log::verbose("Powerup", "World %d pos %d random %d iten %d",
World::getWorld()->getTimeTicks(), pos, random_number, powerup);
#endif
if(powerup>=POWERUP_MAX)
{
i -= POWERUP_MAX;
powerup -= POWERUP_MAX;
*n = 3;
}
else
*n=1;
return (PowerupType)i;
return (PowerupType)powerup;
} // getRandomPowerup

View File

@ -20,9 +20,11 @@
#define HEADER_POWERUPMANAGER_HPP
#include "utils/no_copy.hpp"
#include "utils/leak_check.hpp"
#include "btBulletDynamicsCommon.h"
#include <map>
#include <string>
#include <vector>
@ -70,6 +72,50 @@ namespace irr
class PowerupManager : public NoCopy
{
public:
LEAK_CHECK();
private:
// ------------------------------------------------------------------------
/** This object stores the weights for each 'section' for a certain
* number of karts. */
class WeightsData
{
private:
/** The number of karts for which this entry is to be used. */
unsigned int m_num_karts;
/** Stores for each of the sections the weights from the XML file. */
std::vector < std::vector<int> > m_weights_for_section;
/** This field is only populated for the WeightData class that
* is used during a race. It contains for each rank the summed
* weights for easy lookup during a race. */
std::vector < std::vector<int> > m_summed_weights_for_rank;
public:
WeightsData() { m_num_karts = 0; }
void reset();
void readData(int num_karts, const XMLNode *node);
void interpolate(WeightsData *prev, WeightsData *next, int num_karts);
int convertRankToSection(int rank, int *prev, int *next,
float *weight);
void precomputeWeights();
int getRandomItem(int rank, int random_number);
// --------------------------------------------------------------------
/** Sets the number of karts. */
void setNumKarts(int num_karts) { m_num_karts = num_karts; }
// --------------------------------------------------------------------
/** Returns for how many karts this entry is meant for. */
int getNumKarts() const { return m_num_karts; }
}; // class WeightsData
// ------------------------------------------------------------------------
/** The first key is the race type: race, battle, soccer etc.
* The key then contains a mapping from the kart numbers to the
* WeightsData object that stores all data for the give kart number.
*/
std::map<std::string, std::vector<WeightsData*> > m_all_weights;
public:
// The anvil and parachute must be at the end of the enum, and the
// zipper just before them (see Powerup::hitBonusBox).
@ -85,21 +131,6 @@ public:
POWERUP_MAX
};
/** The different position classes, used to map a kart's position to a
* weight distribution for the different powerups. The battle mode is
* listed as a separate 'position' - this way the same implementation
* as used for normal racing can be used to define which items are
* available in battle mode*/
enum PositionClass {POSITION_FIRST,
POSITION_TOP33,
POSITION_MID33,
POSITION_END33,
POSITION_LAST,
POSITION_BATTLE_MODE,
POSITION_SOCCER_MODE,
POSITION_TUTORIAL_MODE,
POSITION_COUNT};
private:
const int RAND_CLASS_RANGE = 1000;
@ -113,40 +144,18 @@ private:
has none. */
irr::scene::IMesh *m_all_meshes[POWERUP_MAX];
/** For each powerup the weight (probability) used depending on the
* number of players. */
std::vector<unsigned 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_reference_pos[POSITION_COUNT];
/** The mapping of each position to the corresponding position class. */
std::vector<PositionClass> m_position_to_class_inf;
std::vector<PositionClass> m_position_to_class_sup;
std::vector<int> m_position_to_class_cutoff;
/** The weight distribution to be used for the current race. */
WeightsData m_current_item_weights;
PowerupType getPowerupType(const std::string &name) const;
void loadWeights(const XMLNode &root,
unsigned int num_karts,
const std::string &class_name,
PositionClass position_class);
void updatePowerupClass(PowerupManager::PositionClass pos_class);
PositionClass convertPositionToClass(unsigned int num_karts,
unsigned int position, bool class_sup = false);
unsigned int convertPositionToClassWeight(unsigned int num_karts,
unsigned int position);
public:
PowerupManager ();
~PowerupManager ();
void loadPowerupsModels ();
void loadAllPowerups (unsigned int num_karts);
void loadWeights(const XMLNode *node, const std::string &category);
void unloadPowerups ();
void computeWeightsForRace(int num_karts);
void LoadPowerup (PowerupType type, const XMLNode &node);
void updateWeightsForRace(unsigned int num_karts);
PowerupManager::PowerupType
getRandomPowerup(unsigned int pos, unsigned int *n, int random_number);
// ------------------------------------------------------------------------

View File

@ -225,7 +225,7 @@ void World::init()
// Must be called after all karts are created
m_race_gui->init();
powerup_manager->updateWeightsForRace(race_manager->getNumberOfKarts());
powerup_manager->computeWeightsForRace(race_manager->getNumberOfKarts());
if (UserConfigParams::m_particles_effects > 1)
{