1 // SuperTuxKart - a fun racing game with go-kart
2 // Copyright (C) 2009-2015 Marianne Gagnon
3 // (C) 2014-2015 Joerg Henrichs, konstin
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 #include "states_screens/gp_info_screen.hpp"
20
21 #include "audio/sfx_manager.hpp"
22 #include "challenges/unlock_manager.hpp"
23 #include "config/player_manager.hpp"
24 #include "config/saved_grand_prix.hpp"
25 #include "graphics/stk_tex_manager.hpp"
26 #include "guiengine/engine.hpp"
27 #include "guiengine/screen.hpp"
28 #include "guiengine/widgets/icon_button_widget.hpp"
29 #include "guiengine/widgets/label_widget.hpp"
30 #include "guiengine/widgets/list_widget.hpp"
31 #include "guiengine/widgets/ribbon_widget.hpp"
32 #include "guiengine/widgets/spinner_widget.hpp"
33 #include "io/file_manager.hpp"
34 #include "race/grand_prix_manager.hpp"
35 #include "race/grand_prix_data.hpp"
36 #include "race/race_manager.hpp"
37 #include "states_screens/state_manager.hpp"
38 #include "tracks/track.hpp"
39 #include "tracks/track_manager.hpp"
40 #include "utils/string_utils.hpp"
41 #include "utils/translation.hpp"
42
43 #include <IGUIEnvironment.h>
44 #include <IGUIStaticText.h>
45
46 #include <algorithm>
47
48 using irr::gui::IGUIStaticText;
49 using namespace GUIEngine;
50
51 /** Constructor, initialised some variables which might be used before
52 * loadedFromFile is called.
53 */
GPInfoScreen()54 GPInfoScreen::GPInfoScreen() : Screen("gp_info.stkgui")
55 {
56 m_curr_time = 0.0f;
57 // Necessary to test if loadedFroMFile() was executed (in setGP)
58 m_reverse_spinner = NULL;
59 m_max_num_tracks = 0;
60 } // GPInfoScreen
61
62 // ----------------------------------------------------------------------------
63 /** Called when the stkgui file is read. It stores the pointer to various
64 * widgets and adds the right names for reverse mode.
65 */
loadedFromFile()66 void GPInfoScreen::loadedFromFile()
67 {
68 // The group spinner is filled in init every time the screen is shown
69 // (since the groups can change if addons are added/deleted).
70 m_group_spinner = getWidget<SpinnerWidget>("group-spinner");
71 m_reverse_spinner = getWidget<SpinnerWidget>("reverse-spinner");
72 m_reverse_spinner->addLabel(_("Default"));
73 m_reverse_spinner->addLabel(_("None"));
74 m_reverse_spinner->addLabel(_("All"));
75 m_reverse_spinner->addLabel(_("Random"));
76 m_reverse_spinner->setValue(0);
77
78 m_num_tracks_spinner = getWidget<SpinnerWidget>("track-spinner");
79 // Only init the number of tracks here, this way the previously selected
80 // number of tracks will be the default.
81 m_num_tracks_spinner->setValue(1);
82
83 m_ai_kart_spinner = getWidget<SpinnerWidget>("ai-spinner");
84
85 GUIEngine::IconButtonWidget* screenshot = getWidget<IconButtonWidget>("screenshot");
86 screenshot->setFocusable(false);
87 screenshot->m_tab_stop = false;
88 } // loadedFromFile
89
90 // ----------------------------------------------------------------------------
91 /** Sets the GP to be displayed. If the identifier is 'random', no gp info
92 * will be loaded.
93 */
setGP(const std::string & gp_ident)94 void GPInfoScreen::setGP(const std::string &gp_ident)
95 {
96 if(gp_ident!=GrandPrixData::getRandomGPID())
97 m_gp = *grand_prix_manager->getGrandPrix(gp_ident);
98 else
99 {
100 // Doesn't matter what kind of GP we create, it just gets the
101 // right id ("random").
102 m_gp.createRandomGP(1, "standard",
103 m_reverse_spinner ? getReverse()
104 : GrandPrixData::GP_NO_REVERSE);
105 }
106 } // setGP
107
108 // ----------------------------------------------------------------------------
109 /** Converts the currently selected reverse status into a value of type
110 * GPReverseType .
111 */
getReverse() const112 GrandPrixData::GPReverseType GPInfoScreen::getReverse() const
113 {
114 switch (m_reverse_spinner->getValue())
115 {
116 case 0: return GrandPrixData::GP_DEFAULT_REVERSE; break;
117 case 1: return GrandPrixData::GP_NO_REVERSE; break;
118 case 2: return GrandPrixData::GP_ALL_REVERSE; break;
119 case 3: return GrandPrixData::GP_RANDOM_REVERSE; break;
120 default: assert(false);
121 } // switch
122 // Avoid compiler warning
123 return GrandPrixData::GP_DEFAULT_REVERSE;
124 } // getReverse
125 // ----------------------------------------------------------------------------
beforeAddingWidget()126 void GPInfoScreen::beforeAddingWidget()
127 {
128 bool random = m_gp.isRandomGP();
129 if (!random)
130 {
131 // Check if there is a saved GP:
132 SavedGrandPrix* saved_gp = SavedGrandPrix::getSavedGP(
133 StateManager::get()->getActivePlayerProfile(0)->getUniqueID(),
134 m_gp.getId(),
135 RaceManager::get()->getMinorMode(),
136 RaceManager::get()->getNumLocalPlayers());
137
138 int tracks = (int)m_gp.getTrackNames().size();
139 bool continue_visible = saved_gp && saved_gp->getNextTrack() > 0 &&
140 saved_gp->getNextTrack() < tracks;
141
142 RibbonWidget* ribbonButtons = getWidget<RibbonWidget>("buttons");
143 int id_continue_button = ribbonButtons->findItemNamed("continue");
144 ribbonButtons->setItemVisible(id_continue_button, continue_visible);
145 ribbonButtons->setLabel(id_continue_button, _("Continue saved GP"));
146 getWidget<IconButtonWidget>("continue")->setImage("gui/icons/green_check.png");
147 }
148 else
149 {
150 RibbonWidget* ribbonButtons = getWidget<RibbonWidget>("buttons");
151 int id_continue_button = ribbonButtons->findItemNamed("continue");
152 ribbonButtons->setItemVisible(id_continue_button, true);
153 ribbonButtons->setLabel(id_continue_button, _("Reload"));
154 getWidget<IconButtonWidget>("continue")->setImage("gui/icons/restart.png");
155 }
156 }
157
158 // ----------------------------------------------------------------------------
159 /** Called before the screen is shown. It adds the screenshot icon, and
160 * initialises all widgets depending on GP mode (random or not), if a saved
161 * GP is available etc.
162 */
init()163 void GPInfoScreen::init()
164 {
165 Screen::init();
166 m_curr_time = 0.0f;
167
168 bool random = m_gp.isRandomGP();
169
170 getWidget<LabelWidget >("track-text" )->setVisible(random);
171 m_num_tracks_spinner->setVisible(random);
172 getWidget<LabelWidget >("group-text" )->setVisible(random);
173 m_group_spinner->setVisible(random);
174
175
176 if(random)
177 {
178 RibbonWidget *rb = getWidget<RibbonWidget>("buttons");
179 rb->setLabel(1,_(L"Reload") );
180 std::string restart = file_manager->getAsset(FileManager::GUI_ICON, "restart.png");
181
182 // We have to recreate the group spinner, but a new group might have
183 // been added or deleted since the last time this screen was shown.
184 const std::vector<std::string>& groups = track_manager->getAllTrackGroups();
185 m_group_names.clear();
186 m_group_names.push_back("all");
187 for (unsigned int i = 0; i < groups.size(); i++)
188 m_group_names.push_back(groups[i]);
189 m_group_spinner->clearLabels();
190 int index_standard=0;
191 for (unsigned int i = 0; i < m_group_names.size(); i++)
192 {
193 m_group_spinner->addLabel(_(m_group_names[i].c_str()));
194 if (m_group_names[i] == "standard")
195 index_standard = i + 1;
196 }
197 // Try to keep a previously selected group value
198 if(m_group_spinner->getValue() >= (int)groups.size())
199 {
200 m_group_spinner->setValue(index_standard);
201 m_group_name = "standard";
202 }
203 else
204 m_group_name = stringc(m_group_names[m_group_spinner->getValue()].c_str()).c_str();
205
206 m_max_num_tracks = getMaxNumTracks(m_group_name);
207
208 m_num_tracks_spinner->setMax(m_max_num_tracks);
209 if(m_num_tracks_spinner->getValue() > m_max_num_tracks ||
210 m_num_tracks_spinner->getValue() < 1)
211 {
212 m_num_tracks_spinner->setValue(m_max_num_tracks);
213 }
214
215 // Now create the random GP:
216 m_gp.createRandomGP(m_num_tracks_spinner->getValue(),
217 m_group_name, getReverse(), true);
218
219 getWidget<LabelWidget>("name")->setText(m_gp.getName(), false);
220 }
221 else
222 {
223 getWidget<LabelWidget>("name")->setText(m_gp.getName(), false);
224 m_gp.checkConsistency();
225 }
226
227 // Number of AIs
228 // -------------
229 const bool has_AI = RaceManager::get()->hasAI();
230 m_ai_kart_spinner->setVisible(has_AI);
231 getWidget<LabelWidget>("ai-text")->setVisible(has_AI);
232
233 if (has_AI)
234 {
235 const int local_players = RaceManager::get()->getNumLocalPlayers();
236 int min_ai = 0;
237 int num_ai = int(UserConfigParams::m_num_karts_per_gamemode
238 [RaceManager::MAJOR_MODE_GRAND_PRIX]) - local_players;
239
240 // A ftl reace needs at least three karts to make any sense
241 if (RaceManager::get()->getMinorMode()==RaceManager::MINOR_MODE_FOLLOW_LEADER)
242 {
243 min_ai = std::max(0, 3 - local_players);
244 }
245
246 num_ai = std::max(min_ai, num_ai);
247
248 m_ai_kart_spinner->setActive(true);
249 m_ai_kart_spinner->setValue(num_ai);
250 m_ai_kart_spinner->setMax(stk_config->m_max_karts - local_players);
251 m_ai_kart_spinner->setMin(min_ai);
252 } // has_AI
253
254 addTracks();
255 addScreenshot();
256
257 RibbonWidget* bt_start = getWidget<GUIEngine::RibbonWidget>("buttons");
258 bt_start->setFocusForPlayer(PLAYER_ID_GAME_MASTER);
259 } // init
260
261 // ----------------------------------------------------------------------------
262 /** Updates the list of tracks shown.
263 */
addTracks()264 void GPInfoScreen::addTracks()
265 {
266 const std::vector<std::string> tracks = m_gp.getTrackNames();
267
268 ListWidget *list = getWidget<ListWidget>("tracks");
269 list->clear();
270 for (unsigned int i = 0; i < (unsigned int)tracks.size(); i++)
271 {
272 const Track *track = track_manager->getTrack(tracks[i]);
273 std::string s = StringUtils::toString(i);
274 list->addItem(s, track->getName());
275 }
276 } // addTracks
277
278 // ----------------------------------------------------------------------------
279 /** Creates a screenshot widget in the placeholder of the GUI.
280 */
addScreenshot()281 void GPInfoScreen::addScreenshot()
282 {
283 GUIEngine::IconButtonWidget* screenshot = getWidget<IconButtonWidget>("screenshot");
284
285 // Temporary icon, will replace it just after
286 // (but it will be shown if the given icon is not found)
287 screenshot->m_properties[PROP_ICON] = "gui/icons/main_help.png";
288
289 const Track *track = track_manager->getTrack(m_gp.getTrackId(0));
290 video::ITexture* image = STKTexManager::getInstance()
291 ->getTexture(track->getScreenshotFile(),
292 "While loading screenshot for track '%s':", track->getFilename());
293 if (image != NULL)
294 screenshot->setImage(image);
295 } // addScreenShot
296
297 // ----------------------------------------------------------------------------
298 /** Handle user input.
299 */
eventCallback(Widget *,const std::string & name,const int player_id)300 void GPInfoScreen::eventCallback(Widget *, const std::string &name,
301 const int player_id)
302 {
303 if(name=="buttons")
304 {
305 const std::string &button = getWidget<RibbonWidget>("buttons")
306 ->getSelectionIDString(PLAYER_ID_GAME_MASTER);
307
308 // The continue button becomes a 'reload' button in random GP:
309 if(button=="continue" && m_gp.isRandomGP())
310 {
311 // Create a new GP:
312 m_gp.createRandomGP(m_num_tracks_spinner->getValue(),
313 m_group_name, getReverse(),
314 /*new tracks*/ true );
315 addTracks();
316 }
317 else if (button == "start")
318 {
319 // Normal GP: start GP
320 const int local_players = RaceManager::get()->getNumLocalPlayers();
321 const bool has_AI = RaceManager::get()->hasAI();
322 const int num_ai = has_AI ? m_ai_kart_spinner->getValue() : 0;
323
324 RaceManager::get()->setNumKarts(local_players + num_ai);
325 UserConfigParams::m_num_karts_per_gamemode[RaceManager::MAJOR_MODE_GRAND_PRIX] = local_players + num_ai;
326
327 m_gp.changeReverse(getReverse());
328 RaceManager::get()->startGP(m_gp, false, false);
329 }
330 else if (button == "continue")
331 {
332 // Normal GP: continue a saved GP
333 m_gp.changeReverse(getReverse());
334 RaceManager::get()->startGP(m_gp, false, true);
335 }
336 } // name=="buttons"
337 else if (name=="group-spinner")
338 {
339 m_group_name = stringc(m_group_names[m_group_spinner->getValue()].c_str()).c_str();
340
341 m_max_num_tracks = getMaxNumTracks(m_group_name);
342
343 m_num_tracks_spinner->setMax(m_max_num_tracks);
344 if (m_num_tracks_spinner->getValue() > m_max_num_tracks)
345 m_num_tracks_spinner->setValue(m_max_num_tracks);
346 // Create a new (i.e. with new tracks) random gp, since the old
347 // tracks might not all belong to the newly selected group.
348
349 m_gp.createRandomGP(m_num_tracks_spinner->getValue(), m_group_name,
350 getReverse(), /*new_tracks*/true);
351 addTracks();
352 }
353 else if (name=="track-spinner")
354 {
355 m_gp.changeTrackNumber(m_num_tracks_spinner->getValue(), m_group_name);
356 addTracks();
357 }
358 else if (name=="ai-spinner")
359 {
360 const int num_ai = m_ai_kart_spinner->getValue();
361 RaceManager::get()->setNumKarts( RaceManager::get()->getNumLocalPlayers() + num_ai );
362 UserConfigParams::m_num_karts_per_gamemode[RaceManager::MAJOR_MODE_GRAND_PRIX] = RaceManager::get()->getNumLocalPlayers() + num_ai;
363 }
364 else if(name=="back")
365 {
366 StateManager::get()->escapePressed();
367 }
368
369 } // eventCallback
370
371 // ----------------------------------------------------------------------------
372 /** Called every update. Used to cycle the screenshots.
373 * \param dt Time step size.
374 */
onUpdate(float dt)375 void GPInfoScreen::onUpdate(float dt)
376 {
377 if (dt == 0)
378 return; // if nothing changed, return right now
379
380 m_curr_time += dt;
381 int frame_after = (int)(m_curr_time / 1.5f);
382
383 const std::vector<std::string> tracks = m_gp.getTrackNames();
384 if (frame_after >= (int)tracks.size())
385 {
386 frame_after = 0;
387 m_curr_time = 0;
388 }
389
390 Track* track = track_manager->getTrack(tracks[frame_after]);
391 std::string file = track->getScreenshotFile();
392 GUIEngine::IconButtonWidget* screenshot = getWidget<IconButtonWidget>("screenshot");
393 screenshot->setImage(file, IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE);
394 screenshot->m_properties[PROP_ICON] = file;
395 } // onUpdate
396
397 /** Get number of available tracks for random GPs
398 */
getMaxNumTracks(std::string group)399 int GPInfoScreen::getMaxNumTracks(std::string group)
400 {
401 int max_num_tracks = 0;
402
403 if (group == "all")
404 {
405 for (unsigned int i = 0; i < track_manager->getNumberOfTracks(); i++)
406 {
407 std::string id = track_manager->getTrack(i)->getIdent();
408
409 if (!PlayerManager::getCurrentPlayer()->isLocked(id) &&
410 track_manager->getTrack(i)->isRaceTrack())
411 {
412 max_num_tracks++;
413 }
414 }
415 }
416 else
417 {
418 std::vector<int> tracks = track_manager->getTracksInGroup(group);
419
420 for (unsigned int i = 0; i < tracks.size(); i++)
421 {
422 std::string id = track_manager->getTrack(tracks[i])->getIdent();
423
424 if (!PlayerManager::getCurrentPlayer()->isLocked(id) &&
425 track_manager->getTrack(tracks[i])->isRaceTrack())
426 {
427 max_num_tracks++;
428 }
429 }
430 }
431
432 return max_num_tracks;
433 }
434