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