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