Store achievements in a goal tree ; update achievements progress entirely depending on config

This commit is contained in:
Alayan 2018-10-05 03:15:28 +02:00 committed by auriamg
parent 3eab9b11b6
commit a4d81beda4
9 changed files with 719 additions and 476 deletions

View File

@ -1,10 +1,42 @@
<?xml version="1.0"?>
<!-- List of counters the achievements can query.
The format to use is <name_of_the_counter value="X"/>
where X is the desired value of the counter ;
for example <won_races value="10"/>
TODO : support asking for sum of values
TODO : support logical relations (AND/OR) between values
The format to use is <goal type="name_of_the_counter" value="X"/>
where X is the desired value of the counter ;e.g. <won_races value="10"/>
-- Warning! -- If a goal node is malformed, it is ignored.
___________________________________________________________________________
S - Logical relations and subgoals
When you define multiple goals, the achievement will be completed
if they are all met, but they don't need to be met at once.
To have more possibilities, you can define subgoals and the
logical relationship they need to meet.
The available relations are :
AND // The subgoals have to be met, but not necessarily at once
AND-AT-ONCE // The subgoals have to be met at the same time.
OR // One of the subgoal has to be met
SUM // The subgoals sum must reach a certain (positive !) number.
The format to use for AND, AND-AT-ONCE and OR is :
<goal type="AND">
<goal type="name_of_counter" value="X"/>
<goal type="name_of_counter" value="Y"/>
</goal>
For SUM, it is :
<goal type="SUM" value="X">
<goal type="name_of_counter" operation="+"/>
<goal type="name_of_counter" operation="-"/>
</goal>
With the value of operation (+ or -) defining if the subgoal is added
or substracted from the total.
Sub-goals can also have their own sub-goals,
however a AND, AND-AT-ONCE or OR can't have a SUM goal for parent.
___________________________________________________________________________
I - Won races (normal, time-trial, FTL) counters.
Require to beat at least 3 AIs in any difficulty.
@ -66,6 +98,7 @@
swatter-hit-1race
all-hits // hits from bowling ball, cake and swatter
all-hits-1race
hit-same-kart-1race
V - Counters related to other race events.
@ -73,63 +106,73 @@
banana-1race
skidding
skidding-1race
skidding-1lap -->
skidding-1lap
VI - Per-track counters (at least one track reach the value)
race-started
race-finished
race-won
race-finished-reverse
race-finished-alone
less-laps
more-laps
twice-laps
egg-hunt-started
egg-hunt-finished
VII - Per-track counters (all non-addon tracks reach the value)
race-started-all
race-finished-all
race-won-all
race-finished-reverse-all
race-finished-alone-all
less-laps-all
more-laps-all
twice-laps-all
// For egg hunts, all non-addon tracks with egg hunt support
// must reach the value
egg-hunt-started-all
egg-hunt-finished-all
-->
<achievements>
<achievement id="1" name="Christoffel Columbus" description="Play every official track at least once." >
<candela_city goal="1"/>
<cocoa_temple goal="1"/>
<cornfield_crossing goal="1"/>
<fortmagma goal="1"/>
<gran_paradiso_island goal="1"/>
<greenvalley goal="1"/>
<hacienda goal="1"/>
<lighthouse goal="1"/>
<mansion goal="1"/>
<mines goal="1"/>
<minigolf goal="1"/>
<olivermath goal="1"/>
<sandtrack goal="1"/>
<scotland goal="1"/>
<snowmountain goal="1"/>
<snowtuxpeak goal="1"/>
<stk_enterprise goal="1"/>
<abyss goal="1"/>
<xr591 goal="1"/>
<zengarden goal="1"/>
<volcano_island goal="1"/>
<goal type="race-finished-all" value="1"/>
</achievement>
<achievement id="2" name="Strike!" description="Hit 10 karts with a bowling-ball.">
<ball goal="10"/>
<goal type="bowling-hit" value="10"/>
</achievement>
<achievement id="3" name="Arch Enemy" description="Hit the same kart at least 5 times in one race.">
<hit goal="5"/>
<goal type="hit-same-kart-1race" value="5"/>
</achievement>
<achievement id="4" name="Marathoner" description="Finish a race with at least twice the track's default lap number.">
<laps goal="1"/>
<goal type="twice-laps" value="1"/>
</achievement>
<achievement id="5" name="Skid-row" description="Make 5 skidding in a single lap.">
<skidding goal="5"/>
<goal type="skidding-1lap" value="5"/>
</achievement>
<achievement id="6" name="Gold driver" description="Win against at least 3 AIs in normal race, time-trial, and follow the leader.">
<standard goal="1"/>
<std_timetrial goal="1"/>
<follow_leader goal="1"/>
<goal type="won-normal-races" value="1"/>
<goal type="won-tt-races" value="1"/>
<goal type="won-ftl-races" value="1"/>
</achievement>
<achievement id="7" name="Powerup Love" description="Use 10 or more powerups in a race.">
<poweruplover goal="10"/>
<goal type="powerup-used-1race" value="10"/>
</achievement>
<achievement id="8" name="Beyond Luck" description="Win 5 single races in a row against at least 3 AIs. Beware, restarting a race counts as a loss.">
<wins goal="5"/>
<goal type="cons-won-races" value="5"/>
</achievement>
<achievement id="9" name="Banana Lover" description="Collect at least 5 bananas in one race.">
<banana goal="5"/>
<goal type="banana-1race" value="5"/>
</achievement>
<achievement id="10" name="It's secret" description="Really ... a secret.">
<achievement id="10" name="It's secret" description="Really ... a secret." secret="yes">
</achievement>
<achievement id="11" name="Mosquito Hunter" description="Take your opponents for mosquitos! With the swatter, squash them at least 5 times in a race.">
<swatter goal="5"/>
<goal type="swatter-hit-1race" value="5"/>
</achievement>
<achievement id="12" name="Unstoppable" description="Win 10 single races in a row in Expert or SuperTux against at least 5 AIs. Beware, restarting a race counts as a loss.">
<wins goal="10"/>
<goal type="cons-won-races-hard" value="10"/>
</achievement>
</achievements>

View File

@ -32,10 +32,11 @@
/** Constructur, initialises this object with the data from the
* corresponding AchievementInfo.
*/
Achievement::Achievement(const AchievementInfo * info)
Achievement::Achievement(AchievementInfo * info)
: m_achievement_info(info)
{
m_achieved = false;
m_achievement_info->copyGoalTree(m_progress_goal_tree, m_achievement_info->m_goal_tree, true /*set values to 0*/);
} // Achievement
// ----------------------------------------------------------------------------
@ -63,88 +64,169 @@ void Achievement::saveProgress(UTFWriter &out)
out << "/>\n";
} // save
// ----------------------------------------------------------------------------
/** Returns the value for a key.
*/
int Achievement::getValue(const std::string & key)
{
if (m_progress_map.find(key) != m_progress_map.end())
return m_progress_map[key];
return 0;
}
// ----------------------------------------------------------------------------
/** Resets all currently key values to 0. Called if the reset-after-race flag
* is set for the corresponding AchievementInfo.
*/
void Achievement::reset()
{
std::map<std::string, int>::iterator iter;
for (iter = m_progress_map.begin(); iter != m_progress_map.end(); ++iter)
{
iter->second = 0;
}
} // reset
// ----------------------------------------------------------------------------
/** Returns how much of an achievement has been achieved in the form n/m.
* The AchievementInfo adds up all goal values to get 'm', and this
* this class end up all current key values for 'n'.
* The AchievementInfo adds up the number of goals if there are several,
* or take the target value of the goal if there is only one.
* This do the same, but with achieved value or fullfilled goals.
*/
irr::core::stringw Achievement::getProgressAsString() const
irr::core::stringw Achievement::getProgressAsString()
{
//TODO : add a progress computation function.
int progress = 0;
std::map<std::string, int>::const_iterator iter;
irr::core::stringw target = getInfo()->toString();
// For now return N/N in case of an achieved achievement.
if (m_achieved)
return getInfo()->toString() +"/" + getInfo()->toString();
return target + "/" + target;
for (iter = m_progress_map.begin(); iter != m_progress_map.end(); ++iter)
{
progress += iter->second;
}
return StringUtils::toWString(progress) + "/" + getInfo()->toString();
return StringUtils::toWString(progress) + "/" + target;
} // getProgressAsString
// ----------------------------------------------------------------------------
/** Increases the value of a key by a specified amount, but make sure to not
* increase the value above the goal (otherwise the achievement progress
* could be 12/10 (e.g. if one track is used 12 times for the Christoffel
* achievement), even though the achievement is not achieved.
* \param key The key whose value is increased.
* \param increase Amount to add to the value of this key.
/** Set any leaf of the progress goal tree whose type matches the
* goal_string to the value passed as parameter.
* The goal string can contain a logical prefix.
* If it is LOGC- ; the update is for the current value of a
* resetable counter. It is applied if the parent node is of type
* SUM or AND-AT-ONCE, ignored otherwise.
* If it is LOGM- ; the update is for the highest achieved value of a
* resetable counter. It is appliedif the parent node is of type
* AND or OR, ignored otherwise.
* If there is no logical prefix, the new value is set in all cases.
*
* If the leaf has an operator defined, this will trigger an update of the
* relevant values.
* If the leaf's new value match or exceed its target goal value,
* a check for the achievement's completions is triggered.
*/
void Achievement::increase(const std::string & key,
const std::string &goal_key, int increase)
void Achievement::setGoalValue(std::string &goal_string, int value)
{
std::map<std::string, int>::iterator it;
it = m_progress_map.find(key);
if (it != m_progress_map.end())
if(m_achieved) // This should not happen, but it costs little to double-check
return;
bool AO = true;
bool SAAO = true;
if (goal_string.compare(0 /*start of sub-string*/,5/*length*/,"LOGC-") == 0)
{
it->second += increase;
if (it->second > m_achievement_info->getGoalValue(goal_key))
it->second = m_achievement_info->getGoalValue(goal_key);
AO = false;
goal_string = goal_string.substr(5,999);
}
else
else if (goal_string.compare(0 /*start of sub-string*/,5/*length*/,"LOGM-") == 0)
{
if (increase>m_achievement_info->getGoalValue(goal_key))
increase = m_achievement_info->getGoalValue(goal_key);
m_progress_map[key] = increase;
SAAO = false;
goal_string = goal_string.substr(5,999);
}
check();
} // increase
bool found = recursiveSetGoalValue(m_progress_goal_tree, goal_string, value, AO, SAAO);
// If a value has been updated, check for completion
if (found && recursiveCompletionCheck(m_progress_goal_tree, m_achievement_info->m_goal_tree))
{
setAchieved();
onCompletion();
}
} // setGoalValue
bool Achievement::recursiveSetGoalValue(AchievementInfo::goalTree &tree, const std::string &goal_string, int value,
bool and_or, bool sum_andatonce)
{
if (tree.type == goal_string)
{
// We don't update here, because it may yet be cancelled
// depending on the parent tree logical type.
return true;
}
bool can_set_child = ((and_or && (tree.type == "AND" || tree.type == "OR")) ||
(sum_andatonce && (tree.type == "SUM" || tree.type == "AND-AT-ONCE")));
bool value_set = false;
for (unsigned int i=0;i<tree.children.size();i++)
{
if(recursiveSetGoalValue(tree.children[i],goal_string,value, and_or, sum_andatonce))
{
// The value has already been set, pass on the information
if (tree.children[i].type == "AND" ||
tree.children[i].type == "OR" ||
tree.children[i].type == "SUM" ||
tree.children[i].type == "AND-AT-ONCE")
{
value_set = true;
}
// The child has the good type and we can increment the goal;
else if (can_set_child)
{
tree.children[i].value = value;
value_set = true;
}
}
}
// Recompute the sum
if (tree.type == "SUM" && value_set)
{
int new_value = 0;
for (unsigned int i=0;i<tree.children.size();i++)
{
if(tree.children[i].operation == AchievementInfo::OP_ADD)
new_value += tree.children[i].value;
else if(tree.children[i].operation == AchievementInfo::OP_SUBSTRACT)
new_value -= tree.children[i].value;
}
tree.value = new_value;
}
return value_set;
} // recursiveSetGoalValue
// ----------------------------------------------------------------------------
/** Checks if this achievement has been achieved.
*/
void Achievement::check()
bool Achievement::recursiveCompletionCheck(AchievementInfo::goalTree &progress, AchievementInfo::goalTree &reference)
{
if(m_achieved)
return;
if(m_achievement_info->checkCompletion(this))
bool completed = false;
if (progress.type == "AND" || progress.type == "AND-AT-ONCE")
{
completed = true;
for (unsigned int i=0;i<progress.children.size();i++)
{
if (!recursiveCompletionCheck(progress.children[i], reference.children[i]))
{
completed = false;
break;
}
}
}
else if (progress.type == "OR")
{
completed = false;
for (unsigned int i=0;i<progress.children.size();i++)
{
if (recursiveCompletionCheck(progress.children[i], reference.children[i]))
{
completed = true;
break;
}
}
}
// Whether a sum or a leaf node, it has a value.
// The value for sums are updated when the underlying values are,
// we don't need to do it again
else if (progress.value >= reference.value)
{
completed = true;
}
return completed;
} // recursiveCompletionCheck
// ----------------------------------------------------------------------------
/** Manages what needs to happen once the achievement is completed,
* like displaying the completion message to the player or synching
* with the server.
*/
void Achievement::onCompletion()
{
//show achievement
// Note: the "name" variable is required, see issue #2068
// calling _("...", info->getName()) is invalid because getName also calls
@ -164,8 +246,4 @@ void Achievement::check()
request->addParameter("achievementid", getID());
request->queue();
}
m_achieved = true;
}
} // check
} // onCompletion

View File

@ -33,12 +33,7 @@ class XMLNode;
// ============================================================================
/** This class tracks the progress of an achievement for a player, whose
* definition is stored by an associated AchievementInfo. It allows achievement
* status to be saved, and detects when an achievement is fulfilled. It provides
* storage for state information by a generic key-value mapping. The values
* are stored as strings, but can be used to store numerical values. E.g.
* you can call increase("key", 10) for an achievement, which will convert
* the string to int, add 10, then convert the result back to string for
* storage.
* status to be saved, and detects when an achievement is fulfilled.
* \ingroup achievements
*/
class AchievementInfo;
@ -49,36 +44,34 @@ private:
/** True if this achievement has been achieved. */
bool m_achieved;
/** The map of key-value pairs. */
std::map<std::string, int> m_progress_map;
/** The tree of goals. It is identical to the
* goal tree of the matching AchievementInfo,
* except that the stored values represent the
* achieved values instead of the values to meet. */
AchievementInfo::goalTree m_progress_goal_tree;
/** A pointer to the corresponding AchievementInfo instance. */
const AchievementInfo *m_achievement_info;
void check();
AchievementInfo *m_achievement_info;
void onCompletion();
bool recursiveSetGoalValue(AchievementInfo::goalTree &tree, const std::string &goal_string, int value,
bool and_or, bool sum_andatonce);
bool recursiveCompletionCheck(AchievementInfo::goalTree &progress, AchievementInfo::goalTree &reference);
public:
Achievement(const AchievementInfo * info);
Achievement(AchievementInfo * info);
virtual ~Achievement();
virtual void loadProgress(const XMLNode *node);
virtual void saveProgress(UTFWriter &out);
virtual int getValue(const std::string & key);
void increase(const std::string & key, const std::string &goal_key,
int increase = 1);
virtual void reset();
virtual irr::core::stringw getProgressAsString() const;
virtual irr::core::stringw getProgressAsString();
uint32_t getID() const { return m_achievement_info->getID(); }
const AchievementInfo * getInfo() const { return m_achievement_info; }
AchievementInfo * getInfo() { return m_achievement_info; }
void setAchieved() { m_achieved = true; };
bool isAchieved() const { return m_achieved; }
// ------------------------------------------------------------------------
const std::map<std::string, int>& getProgress() const
{
return m_progress_map;
} // getProgress
void setGoalValue(std::string &goal_string, int value);
}; // class Achievement
#endif

View File

@ -49,59 +49,124 @@ AchievementInfo::AchievementInfo(const XMLNode * input)
input->get("secret", &m_is_secret);
m_goal_tree.type = "AND";
m_goal_tree.value = -1;
m_goal_tree.operation = OP_NONE;
parseGoals(input, m_goal_tree);
} // AchievementInfo
// ----------------------------------------------------------------------------
/** Parses recursively the list of goals, to construct the tree of goals */
void AchievementInfo::parseGoals(const XMLNode * input, goalTree &parent)
{
// Now load the goal nodes
for (unsigned int n = 0; n < input->getNumNodes(); n++)
{
const XMLNode *node = input->getNode(n);
std::string key = node->getName();
int goal = 0;
node->get("goal", &goal);
m_goal_values[key] = goal;
if (node->getName() != "goal")
continue; // ignore incorrect node
std::string type;
if(!node->get("type", &type))
continue; // missing type, ignore node
int value;
if (!node->get("value", &value))
value = -1;
std::string operation;
if (!node->get("operation", &operation))
operation = "none";
goalTree child;
child.type = type;
child.value = value;
if (operation == "none")
child.operation = OP_NONE;
else if (operation == "+")
child.operation = OP_ADD;
else if (operation == "-")
child.operation = OP_SUBSTRACT;
else
continue; // incorrect operation type, ignore node
if (type=="AND" || type=="AND-AT-ONCE" || type=="OR" || type=="SUM")
{
if (type == "SUM")
{
if (value <= 0)
continue; // SUM nodes need a strictly positive value
}
if (m_goal_values.size() != input->getNumNodes())
Log::fatal("AchievementInfo",
"Duplicate keys for the entries of a MapAchievement found.");
} // AchievementInfo
else
{
// Logical operators don't have a value or operation defined
if (value != -1)
continue;
if (child.operation != OP_NONE)
continue;
if (parent.type == "SUM")
continue;
}
parseGoals(node, child);
if (child.children.size() == 0)
continue;
}
else
{
if (value <= 0)
continue; // Leaf nodes need a strictly positive value
if (parent.type == "SUM" && child.operation == OP_NONE)
continue; // Leaf nodes of a SUM node need an operator
}
parent.children.push_back(child);
}
if (parent.children.size() != input->getNumNodes())
Log::error("AchievementInfo",
"Incorrect goals for the entries of achievement \"%s\".", m_name.c_str());
} // parseGoals
// ----------------------------------------------------------------------------
/** Copy a goal tree to an EMPTY goal tree by recursion. */
void AchievementInfo::copyGoalTree(goalTree &copy, goalTree &model, bool set_values_to_zero)
{
copy.type = model.type;
copy.value = (set_values_to_zero) ? 0 : model.value;
copy.operation = model.operation;
for (unsigned int i=0;i<model.children.size();i++)
{
goalTree copy_child;
copyGoalTree(copy_child, model.children[i],set_values_to_zero);
copy.children.push_back(copy_child);
}
} // copyGoalTree
// ----------------------------------------------------------------------------
/** Returns a string with a numerical value to display the progress of
* this achievement. It adds up all the goal values
* this achievement.
* If it has multiple goal, it returns the number of goals. If it has
* only one (it can be a sum), it returns the required value for that goal.
* FIXME : don't work well for "all tracks" goals.
*/
irr::core::stringw AchievementInfo::toString() const
irr::core::stringw AchievementInfo::toString()
{
int count = 0;
std::map<std::string, int>::const_iterator iter;
// If all values need to be reached, add up all goal values
for (iter = m_goal_values.begin(); iter != m_goal_values.end(); iter++)
{
count += iter->second;
}
return StringUtils::toWString(count);
return StringUtils::toWString(recursiveGoalCount(m_goal_tree));
} // toString
// ----------------------------------------------------------------------------
bool AchievementInfo::checkCompletion(Achievement * achievement) const
int AchievementInfo::recursiveGoalCount(goalTree &parent)
{
std::map<std::string, int>::const_iterator iter;
for (iter = m_goal_values.begin(); iter != m_goal_values.end(); iter++)
{
if (achievement->getValue(iter->first) < iter->second)
return false;
}
return true;
}
// ----------------------------------------------------------------------------
int AchievementInfo::getGoalValue(const std::string &key) const
{
std::map<std::string, int>::const_iterator it;
it = m_goal_values.find(key);
if (it != m_goal_values.end())
return it->second;
if (parent.children.size() != 1)
return m_goal_tree.children.size();
else if (parent.children[0].type == "AND" ||
parent.children[0].type == "AND-AT-ONCE" ||
parent.children[0].type == "OR")
return recursiveGoalCount(parent.children[0]);
else
return 0;
} // getGoalValue
// ----------------------------------------------------------------------------
return parent.children[0].value;
} // recursiveGoalCount

View File

@ -33,27 +33,28 @@ class Achievement;
/** This class stores an achievement definition from the xml file, including
* title, description, but also how to achieve this achievement.
* Constrat with the Achievement class, which is a player-specific instance
* Contrast with the Achievement class, which is a player-specific instance
* tracking the progress of the achievement.
* \ingroup achievements
*/
class AchievementInfo
{
public:
//FIXME : try to get rid of this list
/** Some handy names for the various achievements. */
enum { ACHIEVE_COLUMBUS = 1,
ACHIEVE_FIRST = ACHIEVE_COLUMBUS,
ACHIEVE_STRIKE = 2,
ACHIEVE_ARCH_ENEMY = 3,
ACHIEVE_MARATHONER = 4,
ACHIEVE_SKIDDING = 5,
ACHIEVE_GOLD_DRIVER = 6,
ACHIEVE_POWERUP_LOVER = 7,
ACHIEVE_BEYOND_LUCK = 8,
ACHIEVE_BANANA = 9,
ACHIEVE_MOSQUITO = 11,
ACHIEVE_UNSTOPPABLE = 12
// The operations supported for a goal
enum operationType {
OP_NONE = 0,
OP_ADD = 1,
OP_SUBSTRACT = 2,
};
// We store goals in a recursive tree.
// This structure matching the algorithms
// we use to manipulate it simplify code.
struct goalTree {
std::string type;
int value;
operationType operation;
std::vector<goalTree> children;
};
private:
@ -66,24 +67,29 @@ private:
/** The description of this achievement. */
irr::core::stringw m_description;
/** The target values needed to be reached. */
std::map<std::string, int> m_goal_values;
/** A secret achievement has its progress not shown. */
bool m_is_secret;
void parseGoals(const XMLNode * input, goalTree &parent);
int recursiveGoalCount(goalTree &parent);
protected:
friend class Achievement;
/** The tree storing all goals */
goalTree m_goal_tree;
public:
AchievementInfo(const XMLNode * input);
virtual ~AchievementInfo() {};
virtual irr::core::stringw toString() const;
virtual bool checkCompletion(Achievement * achievement) const;
int getGoalValue(const std::string &key) const;
virtual irr::core::stringw toString();
uint32_t getID() const { return m_id; }
irr::core::stringw getDescription() const { return _(m_description.c_str()); }
irr::core::stringw getName() const { return _LTR(m_name.c_str()); }
bool isSecret() const { return m_is_secret; }
// This function should not be called if copy already has children
void copyGoalTree(goalTree &copy, goalTree &model, bool set_values_to_zero);
}; // class AchievementInfo

View File

@ -58,13 +58,15 @@ AchievementsStatus::AchievementsStatus()
TrackStats new_track;
new_track.ident = curr->getIdent();
new_track.race_started = new_track.race_finished = new_track.race_won = 0;
new_track.race_finished_reverse = new_track.race_finished_alone = 0;
new_track.less_laps = new_track.more_laps = new_track.min_twice_laps = 0;
new_track.egg_hunt_started = new_track.egg_hunt_finished = 0;
for (unsigned int i=0;i<TR_DATA_NUM;i++)
{
new_track.track_data[i] = 0;
}
m_track_stats.push_back(new_track);
} // for n<track_amount
setEnumToString();
} // AchievementsStatus
// ----------------------------------------------------------------------------
@ -79,6 +81,90 @@ AchievementsStatus::~AchievementsStatus()
m_achievements.clear();
} // ~AchievementsStatus
/** This function loads a table associating an enum identifier
* with the matching command in achievements.xml.
* counters with anassociated max version are prefixed to allow
* the achievement progress update to do the correct action. */
void AchievementsStatus::setEnumToString()
{
m_ach_enum_to_xml[(int)WON_RACES] = "won-races";
m_ach_enum_to_xml[(int)WON_NORMAL_RACES] = "won-normal-races";
m_ach_enum_to_xml[(int)WON_TT_RACES] = "won-tt-races";
m_ach_enum_to_xml[(int)WON_FTL_RACES] = "won-ftl-races";
m_ach_enum_to_xml[(int)CONS_WON_RACES] = "LOGC-cons-won-races";
m_ach_enum_to_xml[(int)CONS_WON_RACES_MAX] = "LOGM-cons-won-races";
m_ach_enum_to_xml[(int)CONS_WON_RACES_HARD] = "LOGC-cons-won-races-hard";
m_ach_enum_to_xml[(int)CONS_WON_RACES_HARD_MAX] = "LOGM-cons-won-races-hard";
m_ach_enum_to_xml[(int)EASY_STARTED] = "easy-started";
m_ach_enum_to_xml[(int)EASY_FINISHED] = "easy-finished";
m_ach_enum_to_xml[(int)MEDIUM_STARTED] = "medium-started";
m_ach_enum_to_xml[(int)MEDIUM_FINISHED] = "medium-finished";
m_ach_enum_to_xml[(int)HARD_STARTED] = "hard-started";
m_ach_enum_to_xml[(int)HARD_FINISHED] = "hard-finished";
m_ach_enum_to_xml[(int)BEST_STARTED] = "best-started";
m_ach_enum_to_xml[(int)BEST_FINISHED] = "best-finished";
m_ach_enum_to_xml[(int)NORMAL_STARTED] = "normal-started";
m_ach_enum_to_xml[(int)NORMAL_FINISHED] = "normal-finished";
m_ach_enum_to_xml[(int)TT_STARTED] = "tt-started";
m_ach_enum_to_xml[(int)TT_FINISHED] = "tt-finished";
m_ach_enum_to_xml[(int)FTL_STARTED] = "ftl-started";
m_ach_enum_to_xml[(int)FTL_FINISHED] = "ftl-finished";
m_ach_enum_to_xml[(int)THREE_STRIKES_STARTED] = "three-strikes-started";
m_ach_enum_to_xml[(int)THREE_STRIKES_FINISHED] = "three-strikes-finished";
m_ach_enum_to_xml[(int)SOCCER_STARTED] = "soccer-started";
m_ach_enum_to_xml[(int)SOCCER_FINISHED] = "soccer-finished";
m_ach_enum_to_xml[(int)EGG_HUNT_STARTED] = "egg-hunt-started";
m_ach_enum_to_xml[(int)EGG_HUNT_FINISHED] = "egg-hunt-finished";
m_ach_enum_to_xml[(int)WITH_GHOST_STARTED] = "with-ghost-started";
m_ach_enum_to_xml[(int)WITH_GHOST_FINISHED] = "with-ghost-finished";
m_ach_enum_to_xml[(int)CTF_STARTED] = "ctf-started";
m_ach_enum_to_xml[(int)CTF_FINISHED] = "ctf-finished";
m_ach_enum_to_xml[(int)FFA_STARTED] = "ffa-started";
m_ach_enum_to_xml[(int)FFA_FINISHED] = "ffa-finished";
m_ach_enum_to_xml[(int)POWERUP_USED] = "powerup-used";
m_ach_enum_to_xml[(int)POWERUP_USED_1RACE] = "LOGC-powerup-used-1race";
m_ach_enum_to_xml[(int)POWERUP_USED_1RACE_MAX] = "LOGM-powerup-used-1race";
m_ach_enum_to_xml[(int)BOWLING_HIT] = "bowling-hit";
m_ach_enum_to_xml[(int)BOWLING_HIT_1RACE] = "LOGC-bowling-hit-1race";
m_ach_enum_to_xml[(int)BOWLING_HIT_1RACE_MAX] = "LOGM-bowling-hit-1race";
m_ach_enum_to_xml[(int)SWATTER_HIT] = "swatter-hit";
m_ach_enum_to_xml[(int)SWATTER_HIT_1RACE] = "LOGC-swatter-hit-1race";
m_ach_enum_to_xml[(int)SWATTER_HIT_1RACE_MAX] = "LOGM-swatter-hit-1race";
m_ach_enum_to_xml[(int)ALL_HITS] = "all-hits";
m_ach_enum_to_xml[(int)ALL_HITS_1RACE] = "LOGC-all-hits-1race";
m_ach_enum_to_xml[(int)ALL_HITS_1RACE_MAX] = "LOGM-all-hits-1race";
m_ach_enum_to_xml[(int)BANANA] = "banana";
m_ach_enum_to_xml[(int)BANANA_1RACE] = "LOGC-banana-1race";
m_ach_enum_to_xml[(int)BANANA_1RACE_MAX] = "LOGM-banana-1race";
m_ach_enum_to_xml[(int)SKIDDING] = "skidding";
m_ach_enum_to_xml[(int)SKIDDING_1RACE] = "LOGC-skidding-1race";
m_ach_enum_to_xml[(int)SKIDDING_1RACE_MAX] = "LOGM-skidding-1race";
m_ach_enum_to_xml[(int)SKIDDING_1LAP] = "LOGC-skidding-1lap";
m_ach_enum_to_xml[(int)SKIDDING_1LAP_MAX] = "LOGM-skidding-1lap";
m_tr_enum_to_xml[(int)TR_STARTED] = "race-started";
m_tr_enum_to_xml[(int)TR_FINISHED] = "race-finished";
m_tr_enum_to_xml[(int)TR_WON] = "race-won";
m_tr_enum_to_xml[(int)TR_FINISHED_REVERSE] = "race-finished-reverse";
m_tr_enum_to_xml[(int)TR_LESS_LAPS] = "less-laps";
m_tr_enum_to_xml[(int)TR_MORE_LAPS] = "more-laps";
m_tr_enum_to_xml[(int)TR_MIN_TWICE_LAPS] = "twice-laps";
m_tr_enum_to_xml[(int)TR_FINISHED_ALONE] = "race-finished-alone";
m_tr_enum_to_xml[(int)TR_EGG_HUNT_STARTED] = "egg-hunt-started";
m_tr_enum_to_xml[(int)TR_EGG_HUNT_FINISHED] = "egg-hunt-started";
m_tr_enum_to_xml[(int)TR_STARTED + (int)TR_DATA_NUM] = "race-started-all";
m_tr_enum_to_xml[(int)TR_FINISHED + (int)TR_DATA_NUM] = "race-finished-all";
m_tr_enum_to_xml[(int)TR_WON + (int)TR_DATA_NUM] = "race-won-all";
m_tr_enum_to_xml[(int)TR_FINISHED_REVERSE + (int)TR_DATA_NUM] = "race-finished-reverse-all";
m_tr_enum_to_xml[(int)TR_LESS_LAPS + (int)TR_DATA_NUM] = "less-laps-all";
m_tr_enum_to_xml[(int)TR_MORE_LAPS + (int)TR_DATA_NUM] = "more-laps-all";
m_tr_enum_to_xml[(int)TR_MIN_TWICE_LAPS + (int)TR_DATA_NUM] = "twice-laps-all";
m_tr_enum_to_xml[(int)TR_FINISHED_ALONE + (int)TR_DATA_NUM] = "race-finished-alone-all";
m_tr_enum_to_xml[(int)TR_EGG_HUNT_STARTED + (int)TR_DATA_NUM] = "egg-hunt-started-all";
m_tr_enum_to_xml[(int)TR_EGG_HUNT_FINISHED + (int)TR_DATA_NUM] = "egg-hunt-started-all";
} // setEnumToString
// ----------------------------------------------------------------------------
/** Loads the saved state of all achievements from an XML file.
* \param input The XML node to load the data from.
@ -132,20 +218,21 @@ void AchievementsStatus::load(const XMLNode * input)
bool track_found = false;
std::string ident;
xml_achievement_tracks[i]->get("ident",&ident);
// We go over already loaded tracks to avoid duplicates
for (unsigned int j=0 ; j < m_track_stats.size(); j++)
{
if (ident == m_track_stats[j].ident)
{
xml_achievement_tracks[i]->get("sta",&m_track_stats[j].race_started);
xml_achievement_tracks[i]->get("fin",&m_track_stats[j].race_finished);
xml_achievement_tracks[i]->get("won",&m_track_stats[j].race_won);
xml_achievement_tracks[i]->get("fin_rev",&m_track_stats[j].race_finished_reverse);
xml_achievement_tracks[i]->get("fin_al",&m_track_stats[j].race_finished_alone);
xml_achievement_tracks[i]->get("less_laps",&m_track_stats[j].less_laps);
xml_achievement_tracks[i]->get("more_laps",&m_track_stats[j].more_laps);
xml_achievement_tracks[i]->get("twice_laps",&m_track_stats[j].min_twice_laps);
xml_achievement_tracks[i]->get("eh_sta",&m_track_stats[j].egg_hunt_started);
xml_achievement_tracks[i]->get("eh_fin",&m_track_stats[j].egg_hunt_finished);
xml_achievement_tracks[i]->get("sta",&m_track_stats[j].track_data[(int)TR_STARTED]);
xml_achievement_tracks[i]->get("fin",&m_track_stats[j].track_data[(int)TR_FINISHED]);
xml_achievement_tracks[i]->get("won",&m_track_stats[j].track_data[(int)TR_WON]);
xml_achievement_tracks[i]->get("fin_rev",&m_track_stats[j].track_data[(int)TR_FINISHED_REVERSE]);
xml_achievement_tracks[i]->get("fin_al",&m_track_stats[j].track_data[(int)TR_FINISHED_ALONE]);
xml_achievement_tracks[i]->get("l_laps",&m_track_stats[j].track_data[(int)TR_LESS_LAPS]);
xml_achievement_tracks[i]->get("m_laps",&m_track_stats[j].track_data[(int)TR_MORE_LAPS]);
xml_achievement_tracks[i]->get("t_laps",&m_track_stats[j].track_data[(int)TR_MIN_TWICE_LAPS]);
xml_achievement_tracks[i]->get("eh_sta",&m_track_stats[j].track_data[(int)TR_EGG_HUNT_STARTED]);
xml_achievement_tracks[i]->get("eh_fin",&m_track_stats[j].track_data[(int)TR_EGG_HUNT_FINISHED]);
track_found = true;
break;
}
@ -155,16 +242,16 @@ void AchievementsStatus::load(const XMLNode * input)
{
TrackStats new_track;
new_track.ident = ident;
xml_achievement_tracks[i]->get("sta",&new_track.race_started);
xml_achievement_tracks[i]->get("fin",&new_track.race_finished);
xml_achievement_tracks[i]->get("won",&new_track.race_won);
xml_achievement_tracks[i]->get("fin_rev",&new_track.race_finished_reverse);
xml_achievement_tracks[i]->get("fin_al",&new_track.race_finished_alone);
xml_achievement_tracks[i]->get("less_laps",&new_track.less_laps);
xml_achievement_tracks[i]->get("more_laps",&new_track.more_laps);
xml_achievement_tracks[i]->get("twice_laps",&new_track.min_twice_laps);
xml_achievement_tracks[i]->get("eh_sta",&new_track.egg_hunt_started);
xml_achievement_tracks[i]->get("eh_fin",&new_track.egg_hunt_finished);
xml_achievement_tracks[i]->get("sta",&new_track.track_data[(int)TR_STARTED]);
xml_achievement_tracks[i]->get("fin",&new_track.track_data[(int)TR_FINISHED]);
xml_achievement_tracks[i]->get("won",&new_track.track_data[(int)TR_WON]);
xml_achievement_tracks[i]->get("fin_rev",&new_track.track_data[(int)TR_FINISHED_REVERSE]);
xml_achievement_tracks[i]->get("fin_al",&new_track.track_data[(int)TR_FINISHED_ALONE]);
xml_achievement_tracks[i]->get("l_laps",&new_track.track_data[(int)TR_LESS_LAPS]);
xml_achievement_tracks[i]->get("m_laps",&new_track.track_data[(int)TR_MORE_LAPS]);
xml_achievement_tracks[i]->get("t_laps",&new_track.track_data[(int)TR_MIN_TWICE_LAPS]);
xml_achievement_tracks[i]->get("eh_sta",&new_track.track_data[(int)TR_EGG_HUNT_STARTED]);
xml_achievement_tracks[i]->get("eh_fin",&new_track.track_data[(int)TR_EGG_HUNT_FINISHED]);
m_track_stats.push_back(new_track);
}
@ -172,8 +259,24 @@ void AchievementsStatus::load(const XMLNode * input)
}
// If there is nothing valid to load ; we keep the init values
// Now that we have retrieved the counters data, we can set
// the progress of the achievements - it isn't saved directly.
updateAllAchievementsProgress();
} // load
void AchievementsStatus::updateAllAchievementsProgress()
{
for (int i=0;i<ACHIEVE_DATA_NUM;i++)
{
updateAchievementsProgress(UP_ACHIEVEMENT_DATA, i);
}
for (int i=0;i<TR_DATA_NUM;i++)
{
updateAchievementsProgress(UP_TRACK_DATA, i);
}
updateAchievementsProgress(UP_KART_HITS, 0);
}
// ----------------------------------------------------------------------------
void AchievementsStatus::add(Achievement *achievement)
{
@ -203,16 +306,16 @@ void AchievementsStatus::save(UTFWriter &out)
for (unsigned int n = 0; n < m_track_stats.size(); n++)
{
out << " <track_stats ident=\"" << m_track_stats[n].ident << "\"";
out << " sta=\"" << m_track_stats[n].race_started << "\"";
out << " fin=\"" << m_track_stats[n].race_finished << "\"";
out << " won=\"" << m_track_stats[n].race_won << "\"";
out << " fin_rev=\"" << m_track_stats[n].race_finished_reverse << "\"";
out << " fin_al=\"" << m_track_stats[n].race_finished_alone << "\"";
out << " less_laps=\"" << m_track_stats[n].less_laps << "\"";
out << " more_laps=\"" << m_track_stats[n].more_laps << "\"";
out << " twice_laps=\"" << m_track_stats[n].min_twice_laps << "\"";
out << " eh_sta=\"" << m_track_stats[n].egg_hunt_started << "\"";
out << " eh_fin=\"" << m_track_stats[n].egg_hunt_finished << "\"";
out << " sta=\"" << m_track_stats[n].track_data[(int)TR_STARTED] << "\"";
out << " fin=\"" << m_track_stats[n].track_data[(int)TR_FINISHED] << "\"";
out << " won=\"" << m_track_stats[n].track_data[(int)TR_WON] << "\"";
out << " fin_rev=\"" << m_track_stats[n].track_data[(int)TR_FINISHED_REVERSE] << "\"";
out << " fin_al=\"" << m_track_stats[n].track_data[(int)TR_FINISHED_ALONE] << "\"";
out << " l_laps=\"" << m_track_stats[n].track_data[(int)TR_LESS_LAPS] << "\"";
out << " m_laps=\"" << m_track_stats[n].track_data[(int)TR_MORE_LAPS] << "\"";
out << " t_laps=\"" << m_track_stats[n].track_data[(int)TR_MIN_TWICE_LAPS] << "\"";
out << " eh_sta=\"" << m_track_stats[n].track_data[(int)TR_EGG_HUNT_STARTED] << "\"";
out << " eh_fin=\"" << m_track_stats[n].track_data[(int)TR_EGG_HUNT_FINISHED] << "\"";
out << "/>\n";
} // for n<m_track_stats.size()
out << " </achievements>\n";
@ -275,117 +378,70 @@ void AchievementsStatus::sync(const std::vector<uint32_t> & achieved_ids)
* \param type - the data type triggering the update, used to know
* to what enum the enum_id refers to.
* \param enum_id - the id of the enum identifying the change triggering
* the update.
FIXME It is currently hard-coded to specific achievements,
until it can entirely supersedes the previous system and
removes its complications.
FIXME : a data id don't cover some situations, and the function itself ignores it */
* the update. */
void AchievementsStatus::updateAchievementsProgress(UpdateType type, unsigned int enum_id)
{
Achievement *beyond_luck = PlayerManager::getCurrentAchievementsStatus()->getAchievement(AchievementInfo::ACHIEVE_BEYOND_LUCK);
if (!beyond_luck->isAchieved())
std::string goal_string[2];
int max_across_tracks = -1;
int min_across_tracks = -1;
int max_kart_hits = -1;
if (type == UP_ACHIEVEMENT_DATA)
{
beyond_luck->reset();
beyond_luck->increase("wins", "wins", m_variables[CONS_WON_RACES].counter);
}
goal_string[0] = m_ach_enum_to_xml[(int)enum_id];
} // if type == UP_ACHIEVEMENT_DATA)
else if (type == UP_TRACK_DATA)
{
goal_string[0] = m_tr_enum_to_xml[(int)enum_id]; // The "one-track at least" goal
goal_string[1] = m_tr_enum_to_xml[(int)enum_id+(int)TR_DATA_NUM]; // The "all tracks" goal
Achievement *unstoppable = PlayerManager::getCurrentAchievementsStatus()->getAchievement(AchievementInfo::ACHIEVE_UNSTOPPABLE);
if (!unstoppable->isAchieved())
for (unsigned int i=0;i<m_track_stats.size();i++)
{
unstoppable->reset();
unstoppable->increase("wins", "wins", m_variables[CONS_WON_RACES_HARD].counter);
}
// ignore addons tracks (compare returns 0 when the values are equal)
// Note: non-official tracks installed directly in the tracks folder
// are considered as officials by this method.
if (m_track_stats[i].ident.compare(0 /*start of sub-string*/,5/*length*/,"addon") == 0)
continue;
Achievement *gold_driver = PlayerManager::getCurrentAchievementsStatus()->getAchievement(AchievementInfo::ACHIEVE_GOLD_DRIVER);
if (!gold_driver->isAchieved())
{
gold_driver->reset();
gold_driver->increase("standard", "standard", m_variables[WON_NORMAL_RACES].counter);
gold_driver->increase("std_timetrial", "std_timetrial", m_variables[WON_TT_RACES].counter);
gold_driver->increase("follow_leader", "follow_leader", m_variables[WON_FTL_RACES].counter);
if (m_track_stats[i].track_data[enum_id] > max_across_tracks)
max_across_tracks = m_track_stats[i].track_data[enum_id];
if (m_track_stats[i].track_data[enum_id] < min_across_tracks)
min_across_tracks = m_track_stats[i].track_data[enum_id];
}
Achievement *powerup_lover = PlayerManager::getCurrentAchievementsStatus()->getAchievement(AchievementInfo::ACHIEVE_POWERUP_LOVER);
if (!powerup_lover->isAchieved())
} // if type == UP_TRACK_DATA
else if (type == UP_KART_HITS)
{
powerup_lover->reset();
powerup_lover->increase("poweruplover", "poweruplover", m_variables[POWERUP_USED_1RACE].counter);
}
goal_string[0] = "hit-same-kart-1race";
Achievement *banana_lover = PlayerManager::getCurrentAchievementsStatus()->getAchievement(AchievementInfo::ACHIEVE_BANANA);
if (!banana_lover->isAchieved())
{
banana_lover->reset();
banana_lover->increase("banana", "banana", m_variables[BANANA_1RACE].counter);
}
Achievement *skidding = PlayerManager::getCurrentAchievementsStatus()->getAchievement(AchievementInfo::ACHIEVE_SKIDDING);
if (!skidding->isAchieved())
{
skidding->reset();
skidding->increase("skidding", "skidding", m_variables[SKIDDING_1LAP].counter);
}
Achievement *strike = PlayerManager::getCurrentAchievementsStatus()->getAchievement(AchievementInfo::ACHIEVE_STRIKE);
if (!strike->isAchieved())
{
strike->reset();
strike->increase("ball", "ball", m_variables[BOWLING_HIT].counter);
}
Achievement *mosquito = PlayerManager::getCurrentAchievementsStatus()->getAchievement(AchievementInfo::ACHIEVE_MOSQUITO);
if (!mosquito->isAchieved())
{
mosquito->reset();
mosquito->increase("swatter", "swatter", m_variables[SWATTER_HIT_1RACE].counter);
}
Achievement *arch_enemy = PlayerManager::getCurrentAchievementsStatus()->getAchievement(AchievementInfo::ACHIEVE_ARCH_ENEMY);
if (!arch_enemy->isAchieved())
{
arch_enemy->reset();
int max_hits = 0;
for (unsigned int i=0;i<m_kart_hits.size();i++)
{
if (m_kart_hits[i] > max_hits)
max_hits = m_kart_hits[i];
}
arch_enemy->increase("hit", "hit", max_hits);
if (m_kart_hits[i] > max_kart_hits)
max_kart_hits = m_kart_hits[i];
}
} // if type == UP_KART_HITS
Achievement *marathoner = PlayerManager::getCurrentAchievementsStatus()->getAchievement(AchievementInfo::ACHIEVE_MARATHONER);
if (!marathoner->isAchieved())
// Now that we know what string to look for, call an Achievement function
// which will look throughout the progress goalTree to update it
std::map<uint32_t, Achievement*>::const_iterator i;
for(i=m_achievements.begin(); i!=m_achievements.end(); i++)
{
marathoner->reset();
for (unsigned int i=0;i<m_track_stats.size();i++)
{
// ignore addons tracks (compare returns 0 when the values are equal)
if (m_track_stats[i].ident.compare(0 /*start of sub-string*/,5/*length*/,"addon") == 0)
// Don't bother checking again already completed achievements
if (i->second->isAchieved())
continue;
if (m_track_stats[i].min_twice_laps >= 1)
if (type == UP_ACHIEVEMENT_DATA)
{
marathoner->increase("laps", "laps", 1);
break;
i->second->setGoalValue(goal_string[0],m_variables[enum_id].counter);
}
}
}
Achievement *columbus = PlayerManager::getCurrentAchievementsStatus()->getAchievement(AchievementInfo::ACHIEVE_COLUMBUS);
if (!columbus->isAchieved())
else if (type == UP_TRACK_DATA)
{
columbus->reset();
for (unsigned int i=0;i<m_track_stats.size();i++)
i->second->setGoalValue(goal_string[0],max_across_tracks);
i->second->setGoalValue(goal_string[1],min_across_tracks);
}
else if (type == UP_KART_HITS)
{
// ignore addons tracks (compare returns 0 when the values are equal)
if (m_track_stats[i].ident.compare(0 /*start of sub-string*/,5/*length*/,"addon") == 0)
continue;
columbus->increase(m_track_stats[i].ident, m_track_stats[i].ident, std::min<int>(m_track_stats[i].race_finished,1));
i->second->setGoalValue(goal_string[0],max_kart_hits);
}
}
}
// ----------------------------------------------------------------------------
@ -429,7 +485,23 @@ void AchievementsStatus::onRaceEnd(bool aborted)
{
onLapEnd();
updateAchievementsProgress(UP_ACHIEVEMENT_DATA, 0);
if (m_variables[POWERUP_USED_1RACE].counter > m_variables[POWERUP_USED_1RACE_MAX].counter)
m_variables[POWERUP_USED_1RACE_MAX].counter = m_variables[POWERUP_USED_1RACE].counter;
if (m_variables[BANANA_1RACE].counter > m_variables[BANANA_1RACE_MAX].counter)
m_variables[BANANA_1RACE_MAX].counter = m_variables[BANANA_1RACE].counter;
if (m_variables[SKIDDING_1RACE].counter > m_variables[SKIDDING_1RACE_MAX].counter)
m_variables[SKIDDING_1RACE_MAX].counter = m_variables[SKIDDING_1RACE].counter;
if (m_variables[BOWLING_HIT_1RACE].counter > m_variables[BOWLING_HIT_1RACE_MAX].counter)
m_variables[BOWLING_HIT_1RACE_MAX].counter = m_variables[BOWLING_HIT_1RACE].counter;
if (m_variables[SWATTER_HIT_1RACE].counter > m_variables[SWATTER_HIT_1RACE_MAX].counter)
m_variables[SWATTER_HIT_1RACE_MAX].counter = m_variables[SWATTER_HIT_1RACE].counter;
if (m_variables[ALL_HITS_1RACE].counter > m_variables[ALL_HITS_1RACE_MAX].counter)
m_variables[ALL_HITS_1RACE_MAX].counter = m_variables[ALL_HITS_1RACE].counter;
m_variables[POWERUP_USED_1RACE].counter = 0;
m_variables[BANANA_1RACE].counter = 0;
@ -438,20 +510,30 @@ void AchievementsStatus::onRaceEnd(bool aborted)
m_variables[SWATTER_HIT_1RACE].counter = 0;
m_variables[ALL_HITS_1RACE].counter = 0;
if (m_variables[CONS_WON_RACES].counter > m_variables[CONS_WON_RACES_MAX].counter)
m_variables[CONS_WON_RACES_MAX].counter = m_variables[CONS_WON_RACES].counter;
if (m_variables[CONS_WON_RACES_HARD].counter > m_variables[CONS_WON_RACES_HARD_MAX].counter)
m_variables[CONS_WON_RACES_HARD_MAX].counter = m_variables[CONS_WON_RACES_HARD].counter;
// Prevent restart from being abused to get consecutive wins achievement
if (aborted)
{
m_variables[CONS_WON_RACES].counter = 0;
m_variables[CONS_WON_RACES_HARD].counter = 0;
}
updateAllAchievementsProgress();
} // onRaceEnd
// ----------------------------------------------------------------------------
void AchievementsStatus::onLapEnd()
{
updateAchievementsProgress(UP_ACHIEVEMENT_DATA, 0);
if (m_variables[SKIDDING_1LAP].counter > m_variables[SKIDDING_1LAP_MAX].counter)
m_variables[SKIDDING_1LAP_MAX].counter = m_variables[SKIDDING_1LAP].counter;
m_variables[SKIDDING_1LAP].counter = 0;
updateAchievementsProgress(UP_ACHIEVEMENT_DATA, (int)SKIDDING_1LAP);
} // onLapEnd
// ----------------------------------------------------------------------------
@ -469,26 +551,9 @@ void AchievementsStatus::trackEvent(std::string track_ident, AchievementsStatus:
break;
}
}
if (event==TR_STARTED)
m_track_stats[track_id].race_started++;
else if (event==TR_FINISHED)
m_track_stats[track_id].race_finished++;
else if (event==TR_WON)
m_track_stats[track_id].race_won++;
else if (event==TR_FINISHED_REVERSE)
m_track_stats[track_id].race_finished_reverse++;
else if (event==TR_FINISHED_ALONE)
m_track_stats[track_id].race_finished_alone++;
else if (event==TR_LESS_LAPS)
m_track_stats[track_id].less_laps++;
else if (event==TR_MORE_LAPS)
m_track_stats[track_id].more_laps++;
else if (event==TR_MIN_TWICE_LAPS)
m_track_stats[track_id].min_twice_laps++;
else if (event==TR_EGG_HUNT_STARTED)
m_track_stats[track_id].egg_hunt_started++;
else if (event==TR_EGG_HUNT_FINISHED)
m_track_stats[track_id].egg_hunt_finished++;
m_track_stats[track_id].track_data[(int)event]++;
updateAchievementsProgress(UP_TRACK_DATA, (int)event);
} // trackEvent
// ----------------------------------------------------------------------------

View File

@ -55,61 +55,71 @@ public :
// 1. Ignore races with not enough AIs for incrementation
// 2. Reset the counter in case of loss against any number of AIs
CONS_WON_RACES = 4,
CONS_WON_RACES_MAX = 5,
// Won races in (at least) hard requires at least 5 AI opponents
CONS_WON_RACES_HARD = 5,
CONS_WON_RACES_HARD = 6,
CONS_WON_RACES_HARD_MAX = 7,
// Count how many normal, TT & FTL races were started and finished by difficulty
EASY_STARTED = 6,
EASY_FINISHED = 7,
MEDIUM_STARTED = 8,
MEDIUM_FINISHED = 9,
HARD_STARTED = 10,
HARD_FINISHED = 11,
BEST_STARTED = 12,
BEST_FINISHED = 13,
EASY_STARTED = 8,
EASY_FINISHED = 9,
MEDIUM_STARTED = 10,
MEDIUM_FINISHED = 11,
HARD_STARTED = 12,
HARD_FINISHED = 13,
BEST_STARTED = 14,
BEST_FINISHED = 15,
// Count how many time a race/match was started and finished by game mode.
// Races with ghost replays technically belong to TT or egg hunt race mode,
// they increment both the with_ghost counter and the relevant mode counter.
NORMAL_STARTED = 14,
NORMAL_FINISHED = 15,
TT_STARTED = 16,
TT_FINISHED = 17,
FTL_STARTED = 18,
FTL_FINISHED = 19,
THREE_STRIKES_STARTED = 20,
THREE_STRIKES_FINISHED = 21,
SOCCER_STARTED = 22,
SOCCER_FINISHED = 23,
EGG_HUNT_STARTED = 24,
EGG_HUNT_FINISHED = 25,
WITH_GHOST_STARTED = 26,
WITH_GHOST_FINISHED = 27,
CTF_STARTED = 28,
CTF_FINISHED = 29,
FFA_STARTED = 30,
FFA_FINISHED = 31,
NORMAL_STARTED = 16,
NORMAL_FINISHED = 17,
TT_STARTED = 18,
TT_FINISHED = 19,
FTL_STARTED = 20,
FTL_FINISHED = 21,
THREE_STRIKES_STARTED = 22,
THREE_STRIKES_FINISHED = 23,
SOCCER_STARTED = 24,
SOCCER_FINISHED = 25,
EGG_HUNT_STARTED = 26,
EGG_HUNT_FINISHED = 27,
WITH_GHOST_STARTED = 28,
WITH_GHOST_FINISHED = 29,
CTF_STARTED = 30,
CTF_FINISHED = 31,
FFA_STARTED = 32,
FFA_FINISHED = 33,
// Count the number of powerups used by the player.
POWERUP_USED = 32,
POWERUP_USED_1RACE = 33,
POWERUP_USED = 34,
POWERUP_USED_1RACE = 35,
POWERUP_USED_1RACE_MAX = 36,
// Count how many times a bowling ball from the player hit a kart
BOWLING_HIT = 34,
BOWLING_HIT_1RACE = 35,
BOWLING_HIT = 37,
BOWLING_HIT_1RACE = 38,
BOWLING_HIT_1RACE_MAX = 39,
// Count how many times a swatter from the player hit a kart
SWATTER_HIT = 36,
SWATTER_HIT_1RACE = 37,
SWATTER_HIT = 40,
SWATTER_HIT_1RACE = 41,
SWATTER_HIT_1RACE_MAX = 42,
// Count how many times a swatter, bowling ball or cake from
// the player hit a kart (excluding the player's own kart)
ALL_HITS = 38,
ALL_HITS_1RACE = 39,
ALL_HITS = 43,
ALL_HITS_1RACE = 44,
ALL_HITS_1RACE_MAX = 45,
// Count the number of bananas hit
BANANA = 40,
BANANA_1RACE = 41,
BANANA = 46,
BANANA_1RACE = 47,
BANANA_1RACE_MAX = 48,
// Count how many times the player skidded
SKIDDING_1LAP = 42,
SKIDDING_1RACE = 43,
SKIDDING = 44,
SKIDDING = 49,
SKIDDING_1RACE = 50,
SKIDDING_1RACE_MAX = 51,
SKIDDING_1LAP = 52,
SKIDDING_1LAP_MAX = 53,
ACHIEVE_DATA_NUM = 45
ACHIEVE_DATA_NUM = 54
};
private:
@ -126,51 +136,55 @@ private:
int counter;
};
const int DATA_VERSION = 3;
const int DATA_VERSION = 4;
// The tracked values are defined at compile time
AchievementVariable m_variables[ACHIEVE_DATA_NUM];
// To keep track of track-specific data without hardcoding
// a list of tracks, we use a special structure.
struct TrackStats
{
std::string ident;
// counters for standard, TT & FTL races
int race_started;
int race_finished;
int race_won; // doesn't count race without any other AI/player
int race_finished_reverse;
int race_finished_alone; // races against replays are counted, too
// counters for standard & TT races, apply to finished races only,
// lap number compared to track default.
int less_laps;
int more_laps;
int min_twice_laps; // at least twice the track's default lap count
// counters for egg hunts
int egg_hunt_started;
int egg_hunt_finished;
};
// We store the enum name and matching goalTree type
// in this table for faster lookup.
std::string m_ach_enum_to_xml[ACHIEVE_DATA_NUM];
// Switching a few times from public to private
// helps here to keep related things next to each other
public:
enum TrackData {
// counters for standard, TT & FTL races
TR_STARTED = 0,
TR_FINISHED = 1,
// doesn't count race without any other AI/player
TR_WON = 2,
TR_FINISHED_REVERSE = 3,
TR_LESS_LAPS = 4,
TR_MORE_LAPS = 5,
TR_MIN_TWICE_LAPS = 6,
TR_FINISHED_ALONE = 7,
// races against replays are counted, too
TR_FINISHED_ALONE = 4,
// counters for standard & TT races, apply to finished races only,
// lap number compared to track default.
TR_LESS_LAPS = 5,
TR_MORE_LAPS = 6,
TR_MIN_TWICE_LAPS = 7, // at least twice the track's default lap count
// counters for egg hunts
TR_EGG_HUNT_STARTED = 8,
TR_EGG_HUNT_FINISHED = 9
TR_EGG_HUNT_FINISHED = 9,
TR_DATA_NUM = 10
};
private:
// To keep track of track-specific data without hardcoding
// a list of tracks, we use a special structure.
struct TrackStats
{
std::string ident;
int track_data[TR_DATA_NUM];
};
std::vector<TrackStats> m_track_stats;
// We store the enum name and matching goalTree type
// in this table for faster lookup.
// Each track data value matches 2 xml command
std::string m_tr_enum_to_xml[2*TR_DATA_NUM];
// TODO : keep track of battle/soccer arenas
// Keeps track of hits inflicted to other karts,
@ -197,6 +211,10 @@ private:
SyncAchievementsRequest() : Online::XMLRequest(true) {}
};
void setEnumToString();
void updateAchievementsProgress(UpdateType type, unsigned int enum_id);
void updateAllAchievementsProgress();
public :
AchievementsStatus();
~AchievementsStatus();
@ -205,7 +223,6 @@ public :
void save(UTFWriter &out);
void add(Achievement *achievement);
void sync(const std::vector<uint32_t> & achieved_ids);
void updateAchievementsProgress(UpdateType type, unsigned int enum_id);
void increaseDataVar(unsigned int achieve_data_id, int increase);
void resetDataVar(unsigned int achieve_data_id);
void onRaceEnd(bool aborted=false);
@ -214,7 +231,7 @@ public :
void resetKartHits(int num_karts);
void addKartHit(int kart_id);
// ------------------------------------------------------------------------
const std::map<uint32_t, Achievement *>& getAllAchievements()
std::map<uint32_t, Achievement *>& getAllAchievements()
{
return m_achievements;
}

View File

@ -141,30 +141,6 @@ public:
{
return PlayerManager::getCurrentPlayer()->getAchievementsStatus();
} // getCurrentAchievementsStatus
// ------------------------------------------------------------------------
/** A handy shortcut to increase points for an achievement key of the
* current player.
* \param achievement_id The achievement id.
* \param key The key of the current value to increase.
* \param increase How much to increase the current value.
* \param goal_key Optional: The goal key to compare the current value
* with. If not set, defaults to key.
*/
static void increaseAchievement(unsigned int achievement_id,
const std::string &key,
int increase = 1,
const std::string &goal_key="")
{
Achievement *a = getCurrentAchievementsStatus()
->getAchievement(achievement_id);
if (!a)
{
Log::fatal("PlayerManager", "Achievement '%d' not found.",
achievement_id);
}
a->increase(key, goal_key.empty() ? key : goal_key, increase);
} // increaseAchievement
// ------------------------------------------------------------------------
/** A handy shortcut to increase points for an achievement data of the

View File

@ -98,14 +98,14 @@ void BaseOnlineProfileAchievements::init()
// No need to wait for results, since they are local anyway
m_waiting_for_achievements = false;
m_achievements_list_widget->clear();
const std::map<uint32_t, Achievement *> & all_achievements =
std::map<uint32_t, Achievement *> & all_achievements =
PlayerManager::getCurrentPlayer()->getAchievementsStatus()
->getAllAchievements();
std::map<uint32_t, Achievement *>::const_iterator it;
for (it = all_achievements.begin(); it != all_achievements.end(); ++it)
{
std::vector<ListWidget::ListCell> row;
const Achievement *a = it->second;
Achievement *a = it->second;
if(a->getInfo()->isSecret() && !a->isAchieved())
continue;
ListWidget::ListCell title(translations->fribidize(a->getInfo()->getName()), -1, 2);