1 //
2 //  SuperTuxKart - a fun racing game with go-kart
3 //  Copyright (C) 2010-2015 SuperTuxKart-Team
4 //
5 //  This program is free software; you can redistribute it and/or
6 //  modify it under the terms of the GNU General Public License
7 //  as published by the Free Software Foundation; either version 3
8 //  of the License, or (at your option) any later version.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 
20 #include "challenges/story_mode_status.hpp"
21 
22 #include "challenges/challenge_status.hpp"
23 #include "challenges/challenge_data.hpp"
24 #include "challenges/story_mode_timer.hpp"
25 #include "challenges/unlock_manager.hpp"
26 #include "config/player_manager.hpp"
27 #include "config/user_config.hpp"
28 #include "io/utf_writer.hpp"
29 #include "io/xml_node.hpp"
30 
31 //-----------------------------------------------------------------------------
StoryModeStatus(const XMLNode * node)32 StoryModeStatus::StoryModeStatus(const XMLNode *node)
33 {
34     m_points                  = 0;
35     m_points_before           = 0;
36     m_next_unlock_points      = 0;
37     m_first_time              = true;
38     m_easy_challenges         = 0;
39     m_medium_challenges       = 0;
40     m_hard_challenges         = 0;
41     m_best_challenges         = 0;
42     m_current_challenge       = NULL;
43     m_story_mode_finished     = false;
44     m_valid_speedrun_finished = false;
45     m_story_mode_milliseconds = 0;
46     m_speedrun_milliseconds   = 0;
47 
48     // If there is saved data, load it
49     if(node)
50     {
51         node->get("first-time", &m_first_time);
52 
53         // If the timer sub-nodes are missing, don't load junk data
54         // Disable the timers if story mode has already been started.
55         if(!node->get("finished", &m_story_mode_finished))
56             m_story_mode_finished     = !m_first_time;
57         if(!node->get("speedrun-finished", &m_valid_speedrun_finished))
58             m_valid_speedrun_finished = false;
59         // Disable showing story mode timer if starting stk with old
60         // players.xml
61         if(!node->get("story-ms", &m_story_mode_milliseconds))
62         {
63             UserConfigParams::m_display_story_mode_timer = false;
64             m_story_mode_milliseconds = -1;
65         }
66         if(!node->get("speedrun-ms", &m_speedrun_milliseconds))
67         {
68             UserConfigParams::m_display_story_mode_timer = false;
69             m_speedrun_milliseconds   = -1;
70         }
71     }   // if node
72 }   // StoryModeStatus
73 
74 //-----------------------------------------------------------------------------
~StoryModeStatus()75 StoryModeStatus::~StoryModeStatus()
76 {
77     std::map<std::string, ChallengeStatus*>::iterator it;
78     for (it = m_challenges_state.begin();it != m_challenges_state.end();it++)
79     {
80         delete it->second;
81     }
82 } // ~StoryModeStatus
83 
84 //-----------------------------------------------------------------------------
85 /** Adds a ChallengeStatus with the specified id to the set of all statuses
86  *  of this object.
87  *  \param cs The challenge status.
88  */
addStatus(ChallengeStatus * cs)89 void StoryModeStatus::addStatus(ChallengeStatus *cs)
90 {
91     m_challenges_state[cs->getData()->getChallengeId()] = cs;
92 }   // addStatus
93 
94 //-----------------------------------------------------------------------------
isLocked(const std::string & feature)95 bool StoryModeStatus::isLocked(const std::string& feature)
96 {
97     if (UserConfigParams::m_unlock_everything > 0)
98         return false;
99 
100     return m_locked_features.find(feature)!=m_locked_features.end();
101 }  // featureIsLocked
102 
103 //-----------------------------------------------------------------------------
computeActive(bool first_call)104 void StoryModeStatus::computeActive(bool first_call)
105 {
106     int old_points = m_points;
107     m_points = 0;
108     m_next_unlock_points = 0;
109     m_easy_challenges = 0;
110     m_medium_challenges = 0;
111     m_hard_challenges = 0;
112     m_best_challenges = 0;
113 
114     m_locked_features.clear(); // start afresh
115 
116     std::map<std::string, ChallengeStatus*>::const_iterator i;
117     for(i = m_challenges_state.begin();
118         i != m_challenges_state.end();  i++)
119     {
120         // Lock features from unsolved challenges
121         if(!(i->second)->isSolvedAtAnyDifficulty())
122         {
123             lockFeature(i->second);
124         }
125         // Count points from solved challenges
126         else if(!i->second->isUnlockList())
127         {
128             int gp_factor = i->second->isGrandPrix() ? GP_FACTOR : 1;
129 
130             if (i->second->isSolved(RaceManager::DIFFICULTY_BEST))
131             {
132                 m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_BEST]*gp_factor;
133                 m_best_challenges++;
134             }
135             else if (i->second->isSolved(RaceManager::DIFFICULTY_HARD))
136             {
137                 m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_HARD]*gp_factor;
138                 m_hard_challenges++;
139             }
140             else if (i->second->isSolved(RaceManager::DIFFICULTY_MEDIUM))
141             {
142                 m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_MEDIUM]*gp_factor;
143                 m_medium_challenges++;
144             }
145             else if (i->second->isSolved(RaceManager::DIFFICULTY_EASY))
146             {
147                 m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_EASY]*gp_factor;
148                 m_easy_challenges++;
149             }
150         }
151 
152         switch(i->second->highestSolved())
153         {
154             // Uses switch fallthrough
155             case RaceManager::DIFFICULTY_NONE:
156                 i->second->setActive(RaceManager::DIFFICULTY_EASY);
157             case RaceManager::DIFFICULTY_EASY:
158                 i->second->setActive(RaceManager::DIFFICULTY_MEDIUM);
159             case RaceManager::DIFFICULTY_MEDIUM:
160                 i->second->setActive(RaceManager::DIFFICULTY_HARD);
161             case RaceManager::DIFFICULTY_HARD:
162                 i->second->setActive(RaceManager::DIFFICULTY_BEST);
163             default:
164                 break;
165         }
166     }   // for i
167 
168     // now we have the number of points.
169 
170     // Update the previous number of points
171     // On game launch, set it to the number of points the player has
172     if (old_points != m_points)
173         m_points_before = (first_call) ? m_points : old_points;
174 
175     unlockFeatureByList();
176 
177     //Actually lock the tracks
178     for (i = m_challenges_state.begin(); i != m_challenges_state.end();  i++)
179     {
180         if (m_points < i->second->getData()->getNumTrophies())
181         {
182             if (i->second->getData()->isSingleRace())
183                 m_locked_features[i->second->getData()->getTrackId()] = true;
184             else if (i->second->getData()->isGrandPrix())
185                 m_locked_features[i->second->getData()->getGPId()] = true;
186             else
187             {
188                 // FIXME when more challenge types are implemented.
189                 assert(false);
190             }
191         }
192     }
193 }   // computeActive
194 
195 //-----------------------------------------------------------------------------
196 
unlockFeatureByList()197 void StoryModeStatus::unlockFeatureByList()
198 {
199     // test if we have unlocked a feature requiring a certain number of points
200     std::map<std::string, ChallengeStatus*>::const_iterator i;
201     for(i = m_challenges_state.begin();
202         i != m_challenges_state.end();  i++)
203     {
204         if (i->second->isUnlockList())
205         {
206             if (i->second->isSolvedAtAnyDifficulty())
207                 continue;
208 
209             bool newly_solved = unlock_manager->unlockByPoints(m_points,i->second);
210             newly_solved = newly_solved || unlock_manager->unlockSpecial(i->second, getNumReqMetInLowerDiff());
211 
212             // Add to list of recently unlocked features
213             if(newly_solved)
214                 m_unlocked_features.push_back(i->second->getData());
215 
216             //Retrieve the smallest number of points for the next unlockable
217             if (i->second->getData()->getNumTrophies() > m_points && (m_next_unlock_points == 0
218                 || i->second->getData()->getNumTrophies() < m_next_unlock_points) )
219                 m_next_unlock_points = i->second->getData()->getNumTrophies();
220         }
221     }
222 } //unlockFeatureByList
223 
224 
225 //-----------------------------------------------------------------------------
226 
lockFeature(ChallengeStatus * challenge_status)227 void StoryModeStatus::lockFeature(ChallengeStatus *challenge_status)
228 {
229     const std::vector<ChallengeData::UnlockableFeature>& features =
230         challenge_status->getData()->getFeatures();
231 
232     const unsigned int amount = (unsigned int)features.size();
233     for (unsigned int n=0; n<amount; n++)
234     {
235         m_locked_features[features[n].m_name] = true;
236     }
237 }   // lockFeature
238 
239 //-----------------------------------------------------------------------------
240 /** Unlocks a feature.
241  *  ComputeActive resets the locked feature list, so no special code
242  *  is required in order to update m_locked_features.
243  *  \param c  The challenge that was fulfilled.
244  *  \param d Difficulty at which the challenge was solved.
245  *  \param do_save If true update the challenge file on disk.
246  */
unlockFeature(ChallengeStatus * c,RaceManager::Difficulty d,bool do_save)247 void StoryModeStatus::unlockFeature(ChallengeStatus* c, RaceManager::Difficulty d,
248                              bool do_save)
249 {
250     // Add to list of recently unlocked features
251     // if the challenge is newly completed at the current difficulty
252     if (!c->isSolved(d))
253         m_unlocked_features.push_back(c->getData());
254 
255     c->setSolved(d);  // reset isActive flag
256 
257     // Save the new unlock information
258     if (do_save) PlayerManager::get()->save();
259 }   // unlockFeature
260 
261 //-----------------------------------------------------------------------------
262 /** Set the current challenge (or NULL if no challenge is done).
263  *  \param challenge Pointer to the challenge (or NULL)
264  */
setCurrentChallenge(const std::string & challenge_id)265 void StoryModeStatus::setCurrentChallenge(const std::string &challenge_id)
266 {
267     m_current_challenge = challenge_id=="" ? NULL
268                                            : getChallengeStatus(challenge_id);
269 }   // setCurrentChallenge
270 
271 //-----------------------------------------------------------------------------
272 /** This is called when a race is finished. See if there is an active
273  *  challenge that was fulfilled.
274  */
raceFinished()275 void StoryModeStatus::raceFinished()
276 {
277     if(m_current_challenge                                            &&
278         RaceManager::get()->getDifficulty() != RaceManager::DIFFICULTY_BEST &&
279         m_current_challenge->getData()->isChallengeFulfilled(true /*best*/))
280     {
281         ChallengeStatus* c = const_cast<ChallengeStatus*>(m_current_challenge);
282         c->setMaxReqInLowerDiff();
283     }
284 
285     if(m_current_challenge                                           &&
286         m_current_challenge->isActive(RaceManager::get()->getDifficulty()) &&
287         m_current_challenge->getData()->isChallengeFulfilled()           )
288     {
289         // cast const away so that the challenge can be set to fulfilled.
290         // The 'clean' implementation would involve searching the challenge
291         // in m_challenges_state, which is a bit of an overkill
292         unlockFeature(const_cast<ChallengeStatus*>(m_current_challenge),
293                       RaceManager::get()->getDifficulty());
294     }   // if isActive && challenge solved
295 
296     //This updates the number of points.
297     //It then calls unlockFeatureByList which checks the specially unlocked features (by points, etc)
298     computeActive();
299 }   // raceFinished
300 
301 //-----------------------------------------------------------------------------
302 /** This is called when a GP is finished. See if there is an active
303  *  challenge that was fulfilled.
304  */
grandPrixFinished()305 void StoryModeStatus::grandPrixFinished()
306 {
307     if(m_current_challenge                                           &&
308         m_current_challenge->isActive(RaceManager::get()->getDifficulty()) )
309     {
310         ChallengeData::GPLevel unlock_level = m_current_challenge->getData()->isGPFulfilled();
311 
312         RaceManager::Difficulty difficulty = RaceManager::DIFFICULTY_EASY;
313 
314         switch (unlock_level)
315         {
316         case ChallengeData::GP_NONE:
317             RaceManager::get()->setCoinTarget(0);
318             return; //No cup unlocked
319         case ChallengeData::GP_EASY:
320             difficulty = RaceManager::DIFFICULTY_EASY;
321             break;
322         case ChallengeData::GP_MEDIUM:
323             difficulty = RaceManager::DIFFICULTY_MEDIUM;
324             break;
325         case ChallengeData::GP_HARD:
326             difficulty = RaceManager::DIFFICULTY_HARD;
327             break;
328         case ChallengeData::GP_BEST:
329             difficulty = RaceManager::DIFFICULTY_BEST;
330             break;
331         }
332 
333         RaceManager::get()->setDifficulty(difficulty);
334         unlockFeature(const_cast<ChallengeStatus*>(m_current_challenge), difficulty);
335     }   // if isActive && challenge solved
336 
337     RaceManager::get()->setCoinTarget(0);
338 }   // grandPrixFinished
339 
340 //-----------------------------------------------------------------------------
341 /** Writes the data of this StoryModeStatus to the specified stream.
342  *  \param out UTF stream to write to.
343  */
save(UTFWriter & out,bool current_player)344 void StoryModeStatus::save(UTFWriter &out, bool current_player)
345 {
346     if (story_mode_timer->playerLoaded() && current_player)
347     {
348         // Make sure the timer pauses time is correct
349         if (story_mode_timer->isStoryModePaused())
350         {
351             story_mode_timer->unpauseTimer(/*loading*/ false);
352             story_mode_timer->updateTimer();
353             story_mode_timer->pauseTimer(/*loading*/ false);
354         }
355 
356         if(m_first_time)
357         {
358             m_speedrun_milliseconds = 0;
359             m_story_mode_milliseconds = 0;
360         }
361         else
362         {
363             m_speedrun_milliseconds = story_mode_timer->getSpeedrunTime();
364             m_story_mode_milliseconds = story_mode_timer->getStoryModeTime();
365         }
366     }
367 
368     out << "      <story-mode first-time=\"" << m_first_time  << "\"";
369     out << " finished=\"" << m_story_mode_finished  << "\"";
370     out << " speedrun-finished=\"" << m_valid_speedrun_finished  << "\"\n";
371     out << "                  story-ms=\"" << m_story_mode_milliseconds  << "\"";
372     out << " speedrun-ms=\"" << m_speedrun_milliseconds  << "\">\n";
373 
374     std::map<std::string, ChallengeStatus*>::const_iterator i;
375     for(i = m_challenges_state.begin();
376         i != m_challenges_state.end();  i++)
377     {
378         if (i->second != NULL)
379             i->second->save(out);
380     }
381     out << "      </story-mode>\n";
382 }  // save
383 
getNumReqMetInLowerDiff() const384 int StoryModeStatus::getNumReqMetInLowerDiff() const
385 {
386     int counter = 0;
387     for ( const auto &c : m_challenges_state)
388     {
389         counter += (c.second->areMaxReqMetInLowerDiff()) ? 1 : 0;
390     }
391     return counter;
392 }  // getNumReqMetInLowerDiff
393 
394 /* EOF */
395