1 //  SuperTuxKart - a fun racing game with go-kart
2 //
3 //  Copyright (C) 2006-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 #include "states_screens/kart_selection.hpp"
20 
21 #include "audio/sfx_manager.hpp"
22 #include "challenges/unlock_manager.hpp"
23 #include "config/player_manager.hpp"
24 #include "config/user_config.hpp"
25 #include "graphics/irr_driver.hpp"
26 #include "graphics/render_info.hpp"
27 #include "guiengine/message_queue.hpp"
28 #include "guiengine/widgets/bubble_widget.hpp"
29 #include "guiengine/widgets/kart_stats_widget.hpp"
30 #include "guiengine/widgets/model_view_widget.hpp"
31 #include "guiengine/widgets/player_name_spinner.hpp"
32 #include "input/input_device.hpp"
33 #include "input/input_manager.hpp"
34 #include "input/device_manager.hpp"
35 #include "items/item_manager.hpp"
36 #include "karts/abstract_characteristic.hpp"
37 #include "karts/kart_model.hpp"
38 #include "karts/kart_properties.hpp"
39 #include "karts/kart_properties_manager.hpp"
40 #include "modes/overworld.hpp"
41 #include "network/network_config.hpp"
42 #include "states_screens/race_setup_screen.hpp"
43 #include "utils/log.hpp"
44 #include "utils/random_generator.hpp"
45 #include "utils/string_utils.hpp"
46 #include "utils/translation.hpp"
47 
48 #include <IGUIEnvironment.h>
49 #include <IGUIButton.h>
50 
51 using namespace GUIEngine;
52 using irr::core::stringw;
53 
54 
55 static const char RANDOM_KART_ID[] = "randomkart";
56 static const char ID_DONT_USE[] = "x";
57 // Use '/' as special character to avoid that someone creates
58 // a kart called 'locked'
59 static const char ID_LOCKED[] = "locked/";
60 
61 KartSelectionScreen* KartSelectionScreen::m_instance_ptr = NULL;
62 
63 int g_root_id;
64 
65 /** Currently, navigation for multiple players at the same time is implemented
66     in a somewhat clunky way. An invisible "dispatcher" widget is added above
67     kart icons. When a player moves up, he focuses the dispatcher, which in
68     turn moves the selection to the appropriate spinner. "tabbing roots" are
69     used to make navigation back down possible. (FIXME: maybe find a cleaner
70     way?) */
71 
72 // ------------------------------------------------------------------------
FocusDispatcher(KartSelectionScreen * parent)73 FocusDispatcher::FocusDispatcher(KartSelectionScreen* parent) : Widget(WTYPE_BUTTON)
74 {
75     m_parent = parent;
76     m_supports_multiplayer = true;
77     m_is_initialised = false;
78 
79     Widget* kartsAreaWidget = parent->getWidget("playerskarts");
80     assert(kartsAreaWidget);
81 
82     m_x = 0;
83     m_y = kartsAreaWidget->m_y;
84     m_w = 1;
85     m_h = 1;
86 
87     m_reserved_id = Widget::getNewNoFocusID();
88 }   // FocusDispatcher
89 // ------------------------------------------------------------------------
setRootID(const int reservedID)90 void FocusDispatcher::setRootID(const int reservedID)
91 {
92     assert(reservedID != -1);
93 
94     m_reserved_id = reservedID;
95 
96     if (m_element != NULL)
97     {
98         m_element->setID(m_reserved_id);
99     }
100 
101     m_is_initialised = true;
102 }   // setRootID
103 
104 // ------------------------------------------------------------------------
add()105 void FocusDispatcher::add()
106 {
107     core::rect<s32> widget_size(m_x, m_y, m_x + m_w, m_y + m_h);
108 
109     m_element = GUIEngine::getGUIEnv()->addButton(widget_size, NULL,
110                 m_reserved_id,
111                 L"Dispatcher", L"");
112 
113     m_id = m_element->getID();
114     m_element->setTabStop(true);
115     m_element->setTabGroup(false);
116     m_element->setTabOrder(m_id);
117 }
118 
focused(const int player_id)119 EventPropagation FocusDispatcher::focused(const int player_id)
120 {
121     if (!m_is_initialised) return EVENT_LET;
122 
123     if(UserConfigParams::logGUI())
124         Log::info("[KartSelectionScreen]", "FocusDispatcher focused by player %u",
125                   player_id);
126 
127     // since this screen is multiplayer, redirect focus to the right widget
128     const int amount = m_parent->m_kart_widgets.size();
129     for (int n=0; n<amount; n++)
130     {
131         if (m_parent->m_kart_widgets[n].getPlayerID() == player_id)
132         {
133             // If player is done, don't do anything with focus
134             if (m_parent->m_kart_widgets[n].isReady())
135                 return GUIEngine::EVENT_BLOCK;
136 
137             //std::cout << "--> Redirecting focus for player " << player_id
138             //          << " from FocusDispatcher "  <<
139             //             " (ID " << m_element->getID() <<
140             //             ") to spinner " << n << " (ID " <<
141             //             m_parent->m_kart_widgets[n].m_player_ident_spinner
142             //             ->getIrrlichtElement()->getID() <<
143             //             ")" << std::endl;
144 
145             m_parent->m_kart_widgets[n].m_player_ident_spinner
146             ->setFocusForPlayer(player_id);
147 
148 
149             return GUIEngine::EVENT_BLOCK;
150         }
151     }
152 
153     //Log::fatal("KartSelectionScreen", "The focus dispatcher can't"
154     //    "find the widget for player %d!", player_id);
155     return GUIEngine::EVENT_LET;
156 }   // focused
157 
158 #if 0
159 #pragma mark -
160 #pragma mark KartHoverListener
161 #endif
162 // ============================================================================
163 
KartHoverListener(KartSelectionScreen * parent)164 KartHoverListener::KartHoverListener(KartSelectionScreen* parent)
165 {
166     m_magic_number = 0xCAFEC001;
167     m_parent = parent;
168 }   // KartHoverListener
169 
170 // ------------------------------------------------------------------------
~KartHoverListener()171 KartHoverListener::~KartHoverListener()
172 {
173     assert(m_magic_number == 0xCAFEC001);
174     m_magic_number = 0xDEADBEEF;
175 }   // ~KartHoverListener
176 
177 // ------------------------------------------------------------------------
onSelectionChanged(DynamicRibbonWidget * theWidget,const std::string & selectionID,const irr::core::stringw & selectionText,const int player_id)178 void KartHoverListener::onSelectionChanged(DynamicRibbonWidget* theWidget,
179         const std::string& selectionID,
180         const irr::core::stringw& selectionText,
181         const int player_id)
182 {
183     assert(m_magic_number == 0xCAFEC001);
184 
185     // Check if this player has a kart
186     if (m_parent->m_kart_widgets.size() <= unsigned(player_id))
187     {
188         GUIEngine::focusNothingForPlayer(player_id);
189         return;
190     }
191 
192     // Don't allow changing the selection after confirming it
193     if (m_parent->m_kart_widgets[player_id].isReady())
194     {
195         // discard events sent when putting back to the right kart
196         if (selectionID ==
197                 m_parent->m_kart_widgets[player_id].m_kart_internal_name) return;
198 
199         DynamicRibbonWidget* w =
200             m_parent->getWidget<DynamicRibbonWidget>("karts");
201         assert(w != NULL);
202 
203         w->setSelection(m_parent->m_kart_widgets[player_id]
204                         .m_kart_internal_name, player_id, true);
205         return;
206     }
207 
208     if (m_parent->m_kart_widgets[player_id].getKartInternalName() == selectionID)
209         return; // already selected
210 
211     m_parent->updateKartWidgetModel(player_id, selectionID, selectionText,
212         m_parent->m_kart_widgets[player_id].getAssociatedPlayer()->getProfile()
213         ->getDefaultKartColor());
214     m_parent->m_kart_widgets[player_id].setKartInternalName(selectionID);
215     m_parent->updateKartStats(player_id, selectionID);
216     m_parent->validateKartChoices();
217 }   // onSelectionChanged
218 
219 #if 0
220 #pragma mark -
221 #pragma mark KartSelectionScreen
222 #endif
223 
224 // ============================================================================
225 
226 /** Small utility function that returns whether the two given players chose
227  *  the same kart. The advantage of this function is that it can handle
228  *  "random kart" selection. */
sameKart(const PlayerKartWidget & player1,const PlayerKartWidget & player2)229 bool sameKart(const PlayerKartWidget& player1, const PlayerKartWidget& player2)
230 {
231     return player1.getKartInternalName() == player2.getKartInternalName() &&
232            player1.getKartInternalName() != RANDOM_KART_ID;
233 }
234 
235 // ============================================================================
236 
KartSelectionScreen(const char * filename)237 KartSelectionScreen::KartSelectionScreen(const char* filename) : Screen(filename)
238 {
239     m_dispatcher           = NULL;
240     m_removed_widget       = NULL;
241     m_multiplayer_message  = NULL;
242     m_from_overworld       = false;
243     m_go_to_overworld_next = false;
244 }   // KartSelectionScreen
245 
246 // ============================================================================
247 
getRunningInstance()248 KartSelectionScreen* KartSelectionScreen::getRunningInstance()
249 {
250     return m_instance_ptr;
251 }
252 
253 // ----------------------------------------------------------------------------
254 
loadedFromFile()255 void KartSelectionScreen::loadedFromFile()
256 {
257     m_dispatcher          = new FocusDispatcher(this);
258     m_first_widget        = m_dispatcher;
259     m_game_master_confirmed    = false;
260     m_multiplayer_message = NULL;
261     // Dynamically add tabs
262     RibbonWidget* tabs = getWidget<RibbonWidget>("kartgroups");
263     assert( tabs != NULL );
264 
265     m_last_widget = tabs;
266 }   // loadedFromFile
267 
268 // ----------------------------------------------------------------------------
269 
beforeAddingWidget()270 void KartSelectionScreen::beforeAddingWidget()
271 {
272     if (useContinueButton())
273     {
274         getWidget("kartlist")->m_properties[GUIEngine::PROP_WIDTH] = "85%";
275         getWidget("continue")->setVisible(true);
276     }
277     else
278     {
279         getWidget("kartlist")->m_properties[GUIEngine::PROP_WIDTH] = "100%";
280         getWidget("continue")->setVisible(false);
281     }
282     // Remove dispatcher from m_widgets before calculateLayout otherwise a
283     // dummy button is shown in kart screen
284     bool removed_dispatcher = false;
285     if (m_widgets.contains(m_dispatcher))
286     {
287         m_widgets.remove(m_dispatcher);
288         removed_dispatcher = true;
289     }
290     calculateLayout();
291     if (removed_dispatcher)
292         m_widgets.push_back(m_dispatcher);
293 
294     // Dynamically add tabs
295     RibbonWidget* tabs = getWidget<RibbonWidget>("kartgroups");
296     assert( tabs != NULL );
297 
298     m_last_widget = tabs;
299     tabs->clearAllChildren();
300 
301     const std::vector<std::string>& groups =
302         kart_properties_manager->getAllGroups();
303     const int group_amount = (int)groups.size();
304 
305     // add all group first
306     if (group_amount > 1)
307     {
308         //I18N: name of the tab that will show tracks from all groups
309         tabs->addTextChild( _("All") , ALL_KART_GROUPS_ID);
310     }
311 
312     // Make group names being picked up by gettext
313 #define FOR_GETTEXT_ONLY(x)
314     //I18N: kart group name
315     FOR_GETTEXT_ONLY( _("standard") )
316     //I18N: kart group name
317     FOR_GETTEXT_ONLY( _("Add-Ons") )
318 
319 
320     // add others after
321     for (int n=0; n<group_amount; n++)
322     {
323         // try to translate group names
324         tabs->addTextChild( _(groups[n].c_str()) , groups[n]);
325     }   // for n<group_amount
326 
327 
328     DynamicRibbonWidget* w = getWidget<DynamicRibbonWidget>("karts");
329     assert( w != NULL );
330 
331     w->setItemCountHint( kart_properties_manager->getNumberOfKarts() );
332 }   // beforeAddingWidget
333 
334 // ----------------------------------------------------------------------------
335 
init()336 void KartSelectionScreen::init()
337 {
338     m_instance_ptr = this;
339     Screen::init();
340     m_must_delete_on_back = false;
341 
342     RibbonWidget* tabs = getWidget<RibbonWidget>("kartgroups");
343     assert( tabs != NULL );
344     tabs->select(UserConfigParams::m_last_used_kart_group,
345                  PLAYER_ID_GAME_MASTER);
346 
347     Widget* placeholder = getWidget("playerskarts");
348     assert(placeholder != NULL);
349 
350     m_dispatcher->setRootID(placeholder->m_reserved_id);
351 
352     g_root_id = placeholder->m_reserved_id;
353     if (!m_widgets.contains(m_dispatcher))
354     {
355         m_widgets.push_back(m_dispatcher);
356 
357         // this is only needed if the dispatcher wasn't already in
358         // the list of widgets. If it already was, it was added along
359         // other widgets.
360         m_dispatcher->add();
361     }
362 
363     m_game_master_confirmed = false;
364 
365     tabs->setActive(true);
366 
367     m_kart_widgets.clearAndDeleteAll();
368 
369     DynamicRibbonWidget* w = getWidget<DynamicRibbonWidget>("karts");
370     assert( w != NULL );
371     // Only allow keyboard and gamepad to choose kart without continue button in
372     // multitouch GUI, so mouse (touch) clicking can be used as previewing karts
373     w->setEventCallbackActive(Input::IT_MOUSEBUTTON, !useContinueButton());
374 
375     KartHoverListener* karthoverListener = new KartHoverListener(this);
376     w->registerHoverListener(karthoverListener);
377 
378 
379     // Build kart list (it is built everytime, to account for .g. locking)
380     setKartsFromCurrentGroup();
381 
382     /*
383 
384      TODO: Ultimately, it'd be nice to *not* clear m_kart_widgets so that
385      when players return to the kart selection screen, it will appear as
386      it did when they left (at least when returning from the track menu).
387      Rebuilding the screen is a little tricky.
388 
389      */
390 
391     /*
392     if (m_kart_widgets.size() > 0)
393     {
394         // trying to rebuild the screen
395         for (int n = 0; n < m_kart_widgets.size(); n++)
396         {
397             PlayerKartWidget *pkw;
398             pkw = m_kart_widgets.get(n);
399             manualAddWidget(pkw);
400             pkw->add();
401         }
402 
403     }
404     else */
405     // For now this is what will happen
406     input_manager->getDeviceManager()->setAssignMode(DETECT_NEW);
407     // This flag will cause that a 'fire' event will be mapped to 'select' (if
408     // 'fire' is not assigned to a GUI event). This is done to support the old
409     // way of player joining by pressing 'fire' instead of 'select'.
410     input_manager->getDeviceManager()->mapFireToSelect(true);
411 
412     if (!NetworkConfig::get()->isNetworking())
413     {
414         StateManager::get()->resetActivePlayers();
415         if (!m_multiplayer)
416         {
417             joinPlayer(input_manager->getDeviceManager()->getLatestUsedDevice(),
418                 NULL/*player profile*/);
419             w->updateItemDisplay();
420 
421             // Player 0 select default kart
422             if (!w->setSelection(UserConfigParams::m_default_kart, 0, true))
423             {
424                 // if kart from config not found, select the first instead
425                 w->setSelection(0, 0, true);
426             }
427         }
428         else
429         {
430             // Add multiplayer message
431             addMultiplayerMessage();
432 #ifdef MOBILE_STK
433             MessageQueue::addStatic(MessageQueue::MT_GENERIC,
434                 _("Connect a keyboard or gamepad to play splitscreen multiplayer"));
435 #endif
436         }
437     }
438 }   // init
439 
440 // ----------------------------------------------------------------------------
441 
tearDown()442 void KartSelectionScreen::tearDown()
443 {
444 #ifdef MOBILE_STK
445     if (m_multiplayer)
446         MessageQueue::discardStatic();
447 #endif
448     // Reset the 'map fire to select' option of the device manager
449     input_manager->getDeviceManager()->mapFireToSelect(false);
450 
451     // if a removed widget is currently shrinking down, remove it upon leaving
452     // the screen
453     if (m_removed_widget != NULL)
454     {
455         manualRemoveWidget(m_removed_widget);
456         delete m_removed_widget;
457         m_removed_widget = NULL;
458     }
459 
460     removeMultiplayerMessage();
461 
462     Screen::tearDown();
463     m_kart_widgets.clearAndDeleteAll();
464 
465     if (m_must_delete_on_back)
466         GUIEngine::removeScreen(this);
467 
468 }   // tearDown
469 
470 // ----------------------------------------------------------------------------
471 
unloaded()472 void KartSelectionScreen::unloaded()
473 {
474     // This pointer is no longer valid (has been deleted along other widgets)
475     m_dispatcher = NULL;
476 }
477 
478 // ----------------------------------------------------------------------------
479 // Return true if event was handled successfully
joinPlayer(InputDevice * device,PlayerProfile * p)480 bool KartSelectionScreen::joinPlayer(InputDevice* device, PlayerProfile* p)
481 {
482 #ifdef MOBILE_STK
483     if (m_multiplayer)
484         MessageQueue::discardStatic();
485 #endif
486     bool first_player = m_kart_widgets.size() == 0;
487 
488     if (UserConfigParams::logGUI())
489         Log::info("KartSelectionScreen",  "joinPlayer() invoked");
490     if (!m_multiplayer && !first_player) return false;
491 
492     assert (m_dispatcher != NULL);
493 
494     DynamicRibbonWidget* w = getWidget<DynamicRibbonWidget>("karts");
495     if (w == NULL)
496     {
497         Log::error("KartSelectionScreen", "joinPlayer(): Called outside of "
498                   "kart selection screen.");
499         return false;
500     }
501     else if (device == NULL)
502     {
503         if (!NetworkConfig::get()->isNetworkAIInstance())
504         {
505             Log::error("KartSelectionScreen", "joinPlayer(): Received null "
506                     "device pointer");
507         }
508         return false;
509     }
510 
511     if (StateManager::get()->activePlayerCount() >= MAX_PLAYER_COUNT)
512     {
513         Log::error("KartSelectionScreen", "Maximum number of players "
514                   "reached");
515         SFXManager::get()->quickSound( "anvil" );
516         return false;
517     }
518 
519     // ---- Create new active player
520     PlayerProfile* profile_to_use = p == NULL ?
521         PlayerManager::getCurrentPlayer() : p;
522 
523     // Make sure enough guest character exists. At this stage this player has
524     // not been added, so the number of guests requested for the first player
525     // is 0 --> forcing at least one real player.
526     if (p == NULL)
527     {
528         PlayerManager::get()->createGuestPlayers(
529             StateManager::get()->activePlayerCount());
530     }
531     if (!first_player && p == NULL)
532     {
533         // Give each player a different start profile
534         const int num_active_players = StateManager::get()->activePlayerCount();
535         profile_to_use = PlayerManager::get()->getPlayer(num_active_players);
536 
537         removeMultiplayerMessage();
538     }
539 
540     const int new_player_id =
541         StateManager::get()->createActivePlayer(profile_to_use, device);
542     StateManager::ActivePlayer* aplayer =
543         StateManager::get()->getActivePlayer(new_player_id);
544 
545     RibbonWidget* tabs = getWidget<RibbonWidget>("kartgroups");
546     assert(tabs != NULL);
547 
548     std::string selected_kart_group =
549         tabs->getSelectionIDString(PLAYER_ID_GAME_MASTER);
550 
551     // ---- Get available area for karts
552     // make a copy of the area, ands move it to be outside the screen
553     Widget* kartsAreaWidget = getWidget("playerskarts");
554     // start at the rightmost of the screen
555     const int shift = irr_driver->getFrameSize().Width;
556     core::recti kartsArea(kartsAreaWidget->m_x + shift,
557                           kartsAreaWidget->m_y,
558                           kartsAreaWidget->m_x + shift + kartsAreaWidget->m_w,
559                           kartsAreaWidget->m_y + kartsAreaWidget->m_h);
560 
561     // ---- Create player/kart widget
562     PlayerKartWidget* newPlayerWidget =
563         new PlayerKartWidget(this, aplayer, kartsArea, m_kart_widgets.size(),
564                              selected_kart_group);
565 
566     manualAddWidget(newPlayerWidget);
567     m_kart_widgets.push_back(newPlayerWidget);
568 
569     newPlayerWidget->add();
570     // From network kart selection, the player name is already defined
571     if (p != NULL)
572     {
573         newPlayerWidget->getPlayerNameSpinner()->setActive(false);
574         newPlayerWidget->getPlayerNameSpinner()->setCustomText(p->getName());
575     }
576 
577     // ---- Divide screen space among all karts
578     const int amount = m_kart_widgets.size();
579     Widget* fullarea = getWidget("playerskarts");
580 
581     // in this special case, leave room for a message on the right
582     if (m_multiplayer && first_player)
583     {
584         if (p == NULL)
585             addMultiplayerMessage();
586         const int splitWidth = fullarea->m_w / 2;
587         m_kart_widgets[0].move( fullarea->m_x, fullarea->m_y, splitWidth,
588                                 fullarea->m_h );
589     }
590     else
591     {
592         const int splitWidth = fullarea->m_w / amount;
593 
594         for (int n=0; n<amount; n++)
595         {
596             m_kart_widgets[n].move( fullarea->m_x + splitWidth * n,
597                                     fullarea->m_y, splitWidth, fullarea->m_h);
598         }
599     }
600 
601     // select something (anything) in the ribbon; by default, only the
602     // game master has something selected. Thus, when a new player joins,
603     // we need to select something for them
604     w->setSelection(new_player_id, new_player_id, true);
605 
606     //newPlayerWidget->m_player_ident_spinner
607     //               ->setFocusForPlayer(new_player_id);
608 
609     if (!m_multiplayer)
610     {
611         input_manager->getDeviceManager()->setSinglePlayer(StateManager::get()
612                                                          ->getActivePlayer(0));
613     }
614 
615     return true;
616 }   // joinPlayer
617 
618 // -----------------------------------------------------------------------------
619 
playerQuit(StateManager::ActivePlayer * player)620 bool KartSelectionScreen::playerQuit(StateManager::ActivePlayer* player)
621 {
622     int player_id = -1;
623 
624     DynamicRibbonWidget* w = getWidget<DynamicRibbonWidget>("karts");
625     if (w == NULL)
626     {
627         Log::error("KartSelectionScreen", "playerQuit() called "
628                   "outside of kart selection screen, "
629                   "or the XML file for this screen was changed without "
630                   "adapting the code accordingly");
631         return false;
632     }
633 
634     // If last player quits, return to main menu
635     if (m_kart_widgets.size() <= 1)
636     {
637         StateManager::get()->escapePressed();
638         return true;
639     }
640 
641     std::map<PlayerKartWidget*, std::string> selections;
642 
643     // Find the player ID associated to this player
644     for (unsigned int n=0; n<m_kart_widgets.size(); n++)
645     {
646         if (m_kart_widgets[n].getAssociatedPlayer() == player)
647         {
648             // Check that this player has not already confirmed,
649             // then they can't back out
650             if (m_kart_widgets[n].isReady())
651             {
652                 SFXManager::get()->quickSound( "anvil" );
653                 return true;
654             }
655 
656             player_id = n;
657         }
658         else
659         {
660             selections[m_kart_widgets.get(n)] =
661                 m_kart_widgets[n].getKartInternalName();
662         }
663     }
664     if (player_id == -1)
665     {
666         Log::warn("KartSelectionScreen", "playerQuit cannot find "
667                   "passed player");
668         return false;
669     }
670     if(UserConfigParams::logGUI())
671         Log::info("KartSelectionScreen", "playerQuit(%d)", player_id);
672 
673     // Just a cheap way to check if there is any discrepancy
674     // between m_kart_widgets and the active player array
675     assert( m_kart_widgets.size() == StateManager::get()->activePlayerCount());
676 
677     // unset selection of this player
678     GUIEngine::focusNothingForPlayer(player_id);
679 
680     // delete a previous removed widget that didn't have time to fully shrink
681     // yet.
682     // TODO: handle multiple shrinking widgets gracefully?
683     if (m_removed_widget != NULL)
684     {
685         manualRemoveWidget(m_removed_widget);
686         delete m_removed_widget;
687         m_removed_widget = NULL;
688     }
689 
690     // keep the removed kart a while, for the 'disappear' animation
691     // to take place
692     m_removed_widget = m_kart_widgets.remove(player_id);
693 
694     // Tell the StateManager to remove this player
695     StateManager::get()->removeActivePlayer(player_id);
696 
697     addMultiplayerMessage();
698 
699     // Karts count changed, maybe order too, so renumber them.
700     renumberKarts();
701 
702     // Tell the removed widget to perform the shrinking animation (which will
703     // be updated in onUpdate, and will stop when the widget has disappeared)
704     Widget* fullarea = getWidget("playerskarts");
705     m_removed_widget->move(m_removed_widget->m_x + m_removed_widget->m_w/2,
706                            fullarea->m_y + fullarea->m_h, 0, 0);
707 
708     // update selections
709 
710     const unsigned int amount = m_kart_widgets.size();
711     for (unsigned int n=0; n<amount; n++)
712     {
713         const std::string& selectedKart = selections[m_kart_widgets.get(n)];
714         if (selectedKart.size() > 0)
715         {
716             //std::cout << m_kart_widgets[n].getAssociatedPlayer()
717             //              ->getProfile()->getName() << " selected "
718             //          << selectedKart.c_str() << "\n";
719             const bool success = w->setSelection(selectedKart, n, true);
720             if (!success)
721             {
722                 Log::warn("KartSelectionScreen", "Failed to select kart %s"
723                           " for player %u, what's going on??", selectedKart.c_str(),n);
724             }
725         }
726     }
727 
728 
729     // check if all players are ready
730     bool allPlayersReady = true;
731     for (unsigned int n=0; n<amount; n++)
732     {
733         if (!m_kart_widgets[n].isReady())
734         {
735             allPlayersReady = false;
736             break;
737         }
738     }
739     if (allPlayersReady && (!m_multiplayer || amount > 1)) allPlayersDone();
740 
741     return true;
742 }   // playerQuit
743 
744 // ----------------------------------------------------------------------------
745 
onUpdate(float delta)746 void KartSelectionScreen::onUpdate(float delta)
747 {
748     // Dispatch the onUpdate event to each kart, so they can perform their
749     // animation if any
750     const int amount = m_kart_widgets.size();
751     for (int n=0; n<amount; n++)
752     {
753         m_kart_widgets[n].onUpdate(delta);
754     }
755 
756     // When a kart widget is removed, it's a kept a while, for the disappear
757     // animation to take place
758     if (m_removed_widget != NULL)
759     {
760         m_removed_widget->onUpdate(delta);
761 
762         if (m_removed_widget->m_w == 0 || m_removed_widget->m_h == 0)
763         {
764             // destruct when too small (for "disappear" effects)
765             manualRemoveWidget(m_removed_widget);
766             delete m_removed_widget;
767             m_removed_widget = NULL;
768         }
769     }
770 }   // onUpdate
771 
772 // ----------------------------------------------------------------------------
773 
playerConfirm(const int player_id)774 void KartSelectionScreen::playerConfirm(const int player_id)
775 {
776     DynamicRibbonWidget* w = getWidget<DynamicRibbonWidget>("karts");
777     assert(w != NULL);
778     const std::string selection = w->getSelectionIDString(player_id);
779     if (StringUtils::startsWith(selection, ID_LOCKED) && !m_multiplayer)
780     {
781         unlock_manager->playLockSound();
782         return;
783     }
784 
785     if (m_kart_widgets[player_id].getKartInternalName().size() == 0 ||
786         m_kart_widgets[player_id].getKartInternalName() == RibbonWidget::NO_ITEM_ID)
787     {
788         SFXManager::get()->quickSound( "anvil" );
789         return;
790     }
791 
792     const int amount = m_kart_widgets.size();
793 
794     // Check if we have enough karts for everybody. If there are more players
795     // than karts then just allow duplicates
796     const int available_kart_count = (int) w->getItems().size();
797     const bool will_need_duplicates = (amount > available_kart_count);
798 
799     // make sure no other player selected the same identity or kart
800     for (int n=0; n<amount; n++)
801     {
802         if (n == player_id) continue; // don't check a kart against itself
803 
804         const bool player_ready   = m_kart_widgets[n].isReady();
805         const bool ident_conflict =
806             !m_kart_widgets[n].getAssociatedPlayer()->getProfile()
807             ->isGuestAccount() &&
808             m_kart_widgets[n].getAssociatedPlayer()->getProfile() ==
809             m_kart_widgets[player_id].getAssociatedPlayer()->getProfile();
810         const bool kart_conflict  = sameKart(m_kart_widgets[n],
811                                              m_kart_widgets[player_id]);
812 
813         if (player_ready && (ident_conflict || kart_conflict) &&
814                 !will_need_duplicates)
815         {
816             if (UserConfigParams::logGUI())
817                 Log::warn("KartSelectionScreen", "You can't select this identity "
818                        "or kart, someone already took it!!");
819 
820             SFXManager::get()->quickSound( "anvil" );
821             return;
822         }
823 
824         // If two PlayerKart entries are associated to the same ActivePlayer,
825         // something went wrong
826         assert(m_kart_widgets[n].getAssociatedPlayer() !=
827                m_kart_widgets[player_id].getAssociatedPlayer());
828     }
829 
830     // Mark this player as ready to start
831     m_kart_widgets[player_id].markAsReady();
832 
833     if (player_id == PLAYER_ID_GAME_MASTER)
834     {
835         m_game_master_confirmed = true;
836         RibbonWidget* tabs = getWidget<RibbonWidget>("kartgroups");
837         assert( tabs != NULL );
838         tabs->setActive(false);
839     }
840 
841     // validate choices to notify player of duplicates
842     const bool names_ok = validateIdentChoices();
843     const bool karts_ok = validateKartChoices();
844 
845     if (!names_ok || !karts_ok) return;
846 
847     // check if all players are ready
848     bool allPlayersReady = true;
849     for (int n=0; n<amount; n++)
850     {
851         if (!m_kart_widgets[n].isReady())
852         {
853             allPlayersReady = false;
854             break;
855         }
856     }
857 
858     if (allPlayersReady && (!m_multiplayer || amount > 1)) allPlayersDone();
859 }   // playerConfirm
860 
861 // ----------------------------------------------------------------------------
862 
updateKartStats(uint8_t widget_id,const std::string & selection)863 void KartSelectionScreen::updateKartStats(uint8_t widget_id,
864                                           const std::string& selection)
865 {
866     KartStatsWidget* w = m_kart_widgets[widget_id].m_kart_stats;
867     assert(w != NULL);
868 
869     const KartProperties *kp =
870                     kart_properties_manager->getKart(selection);
871     // Adjust for online addon karts
872     if (kp && kp->isAddon() && NetworkConfig::get()->isNetworking() &&
873         NetworkConfig::get()->useTuxHitboxAddon())
874         kp = kart_properties_manager->getKart("tux");
875     if (kp != NULL)
876     {
877         w->setValues(kp, m_kart_widgets[widget_id].getHandicap());
878         w->update(0);
879     }
880     else
881     {
882         w->hideAll();
883         w->update(0);
884     }
885 }
886 
887 // ----------------------------------------------------------------------------
updateKartWidgetModel(int widget_id,const std::string & selection,const irr::core::stringw & selectionText,float kart_color)888 void KartSelectionScreen::updateKartWidgetModel(int widget_id,
889                 const std::string& selection,
890                 const irr::core::stringw& selectionText, float kart_color)
891 {
892     // Update the displayed model
893     ModelViewWidget* w3 = m_kart_widgets[widget_id].m_model_view;
894     assert( w3 != NULL );
895 
896     // set the color of the name label
897     m_kart_widgets[widget_id].m_kart_name->setColor(GUIEngine::getSkin()->getColor("text::neutral"));
898 
899     if (selection == RANDOM_KART_ID)
900     {
901         // Random kart
902         scene::IMesh* model =
903             ItemManager::getItemModel(Item::ITEM_BONUS_BOX);
904 
905         w3->clearModels();
906         core::matrix4 model_location;
907         model_location.setTranslation(core::vector3df(0.0f, -12.0f, 0.0f));
908         model_location.setScale(core::vector3df(35.0f, 35.0f, 35.0f));
909         w3->addModel(model, model_location);
910         w3->update(0);
911         m_kart_widgets[widget_id].m_kart_name
912             ->setText( _("Random Kart"), false );
913     }
914     // selection contains the name of the kart, so check only for substr
915     else if (StringUtils::startsWith(selection, ID_LOCKED) && !m_multiplayer)
916     {
917         w3->clearModels();
918         core::matrix4 model_location;
919         model_location.setScale(core::vector3df(15.0f, 15.0f, 15.0f));
920         file_manager->pushTextureSearchPath
921             (file_manager->getAsset(FileManager::MODEL,""), "models");
922         w3->addModel(irr_driver->getAnimatedMesh(
923             file_manager->getAsset(FileManager::MODEL, "chest.spm"))
924             ->getMesh(20), model_location);
925         file_manager->popTextureSearchPath();
926         w3->update(0);
927 
928         if (m_multiplayer)
929         {
930             m_kart_widgets[widget_id].m_kart_name
931             ->setText(_("Locked"), false );
932         }
933         else
934         {
935             m_kart_widgets[widget_id].m_kart_name
936             ->setText(_("Locked : solve active challenges to gain access to more!"), false );
937         }
938     }
939     else
940     {
941         const KartProperties *kp =
942             kart_properties_manager->getKart(selection);
943         if (kp != NULL)
944         {
945             const KartModel &kart_model = kp->getMasterKartModel();
946 
947             float scale = 35.0f;
948             if (kart_model.getLength() > 1.45f)
949             {
950                 // if kart is too long, size it down a bit so that it fits
951                 scale = 30.0f;
952             }
953 
954             core::matrix4 model_location;
955             model_location.setScale(core::vector3df(scale, scale, scale));
956             w3->clearModels();
957             const bool has_win_anime =
958                 UserConfigParams::m_animated_characters &&
959                 (((kart_model.getFrame(KartModel::AF_WIN_LOOP_START) > -1 ||
960                 kart_model.getFrame(KartModel::AF_WIN_START) > -1) &&
961                 kart_model.getFrame(KartModel::AF_WIN_END) > -1) ||
962                 (kart_model.getFrame(KartModel::AF_SELECTION_START) > -1 &&
963                 kart_model.getFrame(KartModel::AF_SELECTION_END) > -1));
964             w3->addModel( kart_model.getModel(), model_location,
965                 has_win_anime ?
966                 kart_model.getFrame(KartModel::AF_SELECTION_START) > -1 ?
967                 kart_model.getFrame(KartModel::AF_SELECTION_START) :
968                 kart_model.getFrame(KartModel::AF_WIN_LOOP_START) > -1 ?
969                 kart_model.getFrame(KartModel::AF_WIN_LOOP_START) :
970                 kart_model.getFrame(KartModel::AF_WIN_START) :
971                 kart_model.getBaseFrame(),
972                 has_win_anime ?
973                 kart_model.getFrame(KartModel::AF_SELECTION_END) > -1 ?
974                 kart_model.getFrame(KartModel::AF_SELECTION_END) :
975                 kart_model.getFrame(KartModel::AF_WIN_END) :
976                 kart_model.getBaseFrame(),
977                 kart_model.getAnimationSpeed());
978 
979             w3->getModelViewRenderInfo()->setHue(kart_color);
980             model_location.setScale(core::vector3df(1.0f, 1.0f, 1.0f));
981             for (unsigned i = 0; i < 4; i++)
982             {
983                 model_location.setTranslation(kart_model
984                     .getWheelGraphicsPosition(i).toIrrVector());
985                 w3->addModel(kart_model.getWheelModel(i), model_location);
986             }
987 
988             for (unsigned i = 0;
989                  i < kart_model.getSpeedWeightedObjectsCount(); i++)
990             {
991                 const SpeedWeightedObject& obj =
992                     kart_model.getSpeedWeightedObject(i);
993                 core::matrix4 swol = obj.m_location;
994                 if (!obj.m_bone_name.empty())
995                 {
996                     core::matrix4 inv =
997                         kart_model.getInverseBoneMatrix(obj.m_bone_name);
998                     swol = inv * obj.m_location;
999                 }
1000                 w3->addModel(obj.m_model, swol, -1, -1, 0.0f, obj.m_bone_name);
1001             }
1002             //w3->update(0);
1003 
1004             m_kart_widgets[widget_id].m_kart_name
1005                 ->setText( selectionText.c_str(), false );
1006         }
1007         else
1008             Log::warn("KartSelectionScreen", "could not "
1009                       "find a kart named '%s'",
1010                       selection.c_str());
1011     }
1012 }
1013 
1014 // ----------------------------------------------------------------------------
1015 /**
1016  * Adds a message to the screen which indicates that players must press fire to join.
1017  */
addMultiplayerMessage()1018 void KartSelectionScreen::addMultiplayerMessage()
1019 {
1020     Widget* fullarea = getWidget("playerskarts");
1021     const int splitWidth = fullarea->m_w / 2;
1022     int message_x = 0;
1023     if (m_kart_widgets.size() == 1)
1024         message_x = (int) (fullarea->m_x + splitWidth + splitWidth * 0.2f);
1025     else
1026         message_x = (int) (fullarea->m_x + splitWidth / 2 + splitWidth * 0.2f);
1027 
1028     if (m_kart_widgets.size() < 2 && m_multiplayer_message == NULL)
1029     {
1030         m_multiplayer_message = new BubbleWidget();
1031         m_multiplayer_message->m_properties[PROP_TEXT_ALIGN] = "center";
1032         m_multiplayer_message->setText(_("Everyone:\n"
1033             "Press the 'Select' button to join the game"));
1034         m_multiplayer_message->m_x = message_x;
1035         m_multiplayer_message->m_y = (int) (fullarea->m_y + fullarea->m_h * 0.3f);
1036         m_multiplayer_message->m_w = (int) (splitWidth * 0.6f);
1037         m_multiplayer_message->m_h = (int) (fullarea->m_h * 0.6f);
1038         m_multiplayer_message->setFocusable(false);
1039         m_multiplayer_message->add();
1040         manualAddWidget(m_multiplayer_message);
1041     }
1042     else if(m_multiplayer_message != NULL)
1043     {
1044         m_multiplayer_message->move(message_x, (int) (fullarea->m_y + fullarea->m_h * 0.3f),
1045                                     (int) (splitWidth * 0.6f), (int) (fullarea->m_h * 0.6f));
1046     }
1047 }   // addMultiplayerMessage
1048 
1049 // ----------------------------------------------------------------------------
1050 /**
1051  * Remove the multiplayer message.
1052  */
removeMultiplayerMessage()1053 void KartSelectionScreen::removeMultiplayerMessage()
1054 {
1055     if (m_multiplayer_message != NULL)
1056     {
1057         manualRemoveWidget(m_multiplayer_message);
1058         m_multiplayer_message->getIrrlichtElement()->remove();
1059         m_multiplayer_message->elementRemoved();
1060         delete m_multiplayer_message;
1061         m_multiplayer_message = NULL;
1062     }
1063 }   // removeMultiplayerMessage
1064 
1065 // ----------------------------------------------------------------------------
1066 /**
1067  * Callback handling events from the kart selection menu
1068  */
eventCallback(Widget * widget,const std::string & name,const int player_id)1069 void KartSelectionScreen::eventCallback(Widget* widget,
1070                                         const std::string& name,
1071                                         const int player_id)
1072 {
1073     // don't allow changing group after someone confirmed
1074     if (name == "kartgroups" && !m_game_master_confirmed)
1075     {
1076         RibbonWidget* tabs = getWidget<RibbonWidget>("kartgroups");
1077         assert(tabs != NULL);
1078         DynamicRibbonWidget* w = getWidget<DynamicRibbonWidget>("karts");
1079         assert(w != NULL);
1080 
1081         setKartsFromCurrentGroup();
1082 
1083         const std::string &selected_kart_group =
1084             tabs->getSelectionIDString(PLAYER_ID_GAME_MASTER);
1085 
1086         UserConfigParams::m_last_used_kart_group = selected_kart_group;
1087 
1088         RandomGenerator random;
1089 
1090         const int num_players = m_kart_widgets.size();
1091         for (int n=0; n<num_players; n++)
1092         {
1093             // The game master is the one that can change the groups, leave
1094             // his focus on the tabs for others, remove focus from kart that
1095             // might no more exist in this tab.
1096             if (n != PLAYER_ID_GAME_MASTER)
1097                 GUIEngine::focusNothingForPlayer(n);
1098 
1099             if (!m_kart_widgets[n].isReady())
1100             {
1101                 // try to preserve the same kart for each player (except for
1102                 // game master, since it's the one  that can change the
1103                 // groups, so focus for this player must remain on the tabs)
1104                 const std::string& selected_kart =
1105                     m_kart_widgets[n].getKartInternalName();
1106                 if (!w->setSelection( selected_kart, n,
1107                                       n != PLAYER_ID_GAME_MASTER))
1108                 {
1109                     // if we get here, it means one player "lost" his kart in
1110                     // the tab switch
1111                     if (UserConfigParams::logGUI())
1112                         Log::info("KartSelectionScreen", "Player %u"
1113                                   " lost their selection when switching tabs!!!",n);
1114 
1115                     // Select a random kart in this case
1116                     const int count = (int) w->getItems().size();
1117                     if (count > 0)
1118                     {
1119                         // FIXME: two players may be given the same kart by
1120                         // the use of random
1121                         const int random_id = random.get( count );
1122 
1123                         // select kart for players > 0 (player 0 is the one
1124                         // that can change the groups, so focus for player 0
1125                         // must remain on the tabs)
1126                         const bool success =
1127                             w->setSelection( random_id, n,
1128                                              n != PLAYER_ID_GAME_MASTER );
1129                         if (!success)
1130                             Log::warn("KartSelectionScreen",
1131                                       "setting kart of player %u failed");
1132                     }
1133                     else
1134                     {
1135                         Log::warn("KartSelectionScreen",  " 0 items "
1136                                   "in the ribbon");
1137                     }
1138                 }
1139             }
1140         } // end for
1141     }
1142     else if (name == "karts")
1143     {
1144         if (m_kart_widgets.size() > unsigned(player_id))
1145             playerConfirm(player_id);
1146     }
1147     else if (name == "continue")
1148     {
1149         if (m_kart_widgets.size() > unsigned(player_id))
1150             playerConfirm(player_id);
1151     }
1152     else if (name == "back")
1153     {
1154         StateManager::get()->escapePressed();
1155     }
1156     else
1157     {
1158         // Transmit to all subwidgets, maybe *they* care about this event
1159         const int amount = m_kart_widgets.size();
1160         for (int n=0; n<amount; n++)
1161         {
1162             m_kart_widgets[n].transmitEvent(widget, name, player_id);
1163         }
1164 
1165         // those events may mean that a player selection changed, so
1166         // validate again
1167         validateIdentChoices();
1168         validateKartChoices();
1169     }
1170 }   // eventCallback
1171 
1172 // ----------------------------------------------------------------------------
1173 
setMultiplayer(bool multiplayer)1174 void KartSelectionScreen::setMultiplayer(bool multiplayer)
1175 {
1176     m_multiplayer = multiplayer;
1177 }   // setMultiplayer
1178 
1179 // ----------------------------------------------------------------------------
1180 
onEscapePressed()1181 bool KartSelectionScreen::onEscapePressed()
1182 {
1183     m_go_to_overworld_next = false; // valid once
1184     m_must_delete_on_back = true; // delete the screen
1185     if (m_from_overworld)
1186     {
1187         m_from_overworld = false; // valid once
1188         OverWorld::enterOverWorld();
1189         return false;
1190     }
1191     else
1192     {
1193         return true;
1194     }
1195 }
1196 
1197 // ----------------------------------------------------------------------------
1198 
1199 #if 0
1200 #pragma mark -
1201 #pragma mark KartSelectionScreen (private)
1202 #endif
1203 
allPlayersDone()1204 void KartSelectionScreen::allPlayersDone()
1205 {
1206     input_manager->setMasterPlayerOnly(true);
1207 
1208     RibbonWidget* tabs = getWidget<RibbonWidget>("kartgroups");
1209     assert(tabs != NULL);
1210 
1211     std::string selected_kart_group =
1212         tabs->getSelectionIDString(PLAYER_ID_GAME_MASTER);
1213 
1214     UserConfigParams::m_last_used_kart_group = selected_kart_group;
1215 
1216     DynamicRibbonWidget* w = getWidget<DynamicRibbonWidget>("karts");
1217     assert( w != NULL );
1218 
1219     const PtrVector< StateManager::ActivePlayer, HOLD >& players =
1220         StateManager::get()->getActivePlayers();
1221 
1222     // ---- Print selection (for debugging purposes)
1223     if(UserConfigParams::logGUI())
1224     {
1225         Log::info("KartSelectionScreen", "players : %d",players.size());
1226 
1227         for (unsigned int n=0; n<players.size(); n++)
1228         {
1229             Log::info("KartSelectionScreen", "     Player %u is %s on %s",n,
1230                     core::stringc(
1231                           players[n].getConstProfile()->getName().c_str()).c_str(),
1232                     players[n].getDevice()->getName().c_str());
1233         }
1234     }
1235 
1236     for (unsigned int n=0; n<players.size(); n++)
1237     {
1238         StateManager::get()->getActivePlayer(n)->getProfile()
1239             ->incrementUseFrequency();
1240     }
1241     // ---- Give player info to race manager
1242     RaceManager::get()->setNumPlayers(players.size());
1243 
1244     // ---- Manage 'random kart' selection(s)
1245     RandomGenerator random;
1246 
1247     std::vector<ItemDescription> items = w->getItems();
1248 
1249     // remove the 'random' item itself
1250     const int item_count = (int) items.size();
1251     for (int n=0; n<item_count; n++)
1252     {
1253         if (items[n].m_code_name == RANDOM_KART_ID)
1254         {
1255             items[n].m_code_name = ID_DONT_USE;
1256             break;
1257         }
1258     }
1259 
1260     // pick random karts
1261     const int kart_count = m_kart_widgets.size();
1262     for (int n = 0; n < kart_count; n++)
1263     {
1264         std::string selected_kart = m_kart_widgets[n].m_kart_internal_name;
1265 
1266         if (selected_kart == RANDOM_KART_ID)
1267         {
1268             // don't select an already selected kart
1269             // to prevent infinite loop in case they are all locked
1270             int count = 0;
1271             bool done = false;
1272             do
1273             {
1274                 int random_id = random.get(item_count);
1275                 // valid kart if it can bt used, and is either not locked,
1276                 // or it's a multiplayer race.
1277                 if (items[random_id].m_code_name != ID_DONT_USE &&
1278                     (!StringUtils::startsWith(items[random_id].m_code_name, ID_LOCKED)
1279                     || m_multiplayer)                                                 )
1280                 {
1281                     selected_kart = items[random_id].m_code_name;
1282                     done = true;
1283                 }
1284                 items[random_id].m_code_name = ID_DONT_USE;
1285                 count++;
1286                 if (count > 100) return;
1287             }
1288             while (!done);
1289         }
1290         else
1291         {
1292             // mark the item as taken
1293             for (int i=0; i<item_count; i++)
1294             {
1295                 if (items[i].m_code_name ==
1296                         m_kart_widgets[n].m_kart_internal_name)
1297                 {
1298                     items[i].m_code_name = ID_DONT_USE;
1299                     break;
1300                 }
1301             }
1302         }
1303 
1304         if (n == PLAYER_ID_GAME_MASTER)
1305         {
1306             UserConfigParams::m_default_kart = selected_kart;
1307         }
1308 
1309         RaceManager::get()->setPlayerKart(n, selected_kart);
1310 
1311         // Set handicap if needed
1312         if (m_multiplayer && UserConfigParams::m_per_player_difficulty)
1313             RaceManager::get()->setPlayerHandicap(n, m_kart_widgets[n].getHandicap());
1314     }
1315 
1316     // ---- Switch to assign mode
1317     input_manager->getDeviceManager()->setAssignMode(ASSIGN);
1318 
1319     StateManager::ActivePlayer *ap = m_multiplayer
1320                                    ? NULL
1321                                    : StateManager::get()->getActivePlayer(0);
1322     input_manager->getDeviceManager()->setSinglePlayer(ap);
1323 
1324     // ---- Go to next screen or return to overworld
1325     if (m_from_overworld || m_go_to_overworld_next)
1326     {
1327         m_from_overworld = false; // valid once
1328         m_go_to_overworld_next = false;
1329         OverWorld::enterOverWorld();
1330     }
1331     else
1332     {
1333         RaceSetupScreen::getInstance()->push();
1334     }
1335 }   // allPlayersDone
1336 
1337 // ----------------------------------------------------------------------------
1338 
validateIdentChoices()1339 bool KartSelectionScreen::validateIdentChoices()
1340 {
1341     bool ok = true;
1342 
1343     const int amount = m_kart_widgets.size();
1344 
1345     // reset all marks, we'll re-add them next if errors are still there
1346     for (int n=0; n<amount; n++)
1347     {
1348         // first check if the player name widget is still there, it won't
1349         // be for those that confirmed
1350         if (m_kart_widgets[n].m_player_ident_spinner != NULL)
1351         {
1352             m_kart_widgets[n].m_player_ident_spinner->markAsCorrect();
1353 
1354             // verify internal consistency in debug mode
1355             if (m_multiplayer)
1356             {
1357                 int spinner_value = m_kart_widgets[n].m_player_ident_spinner->getValue();
1358                 if (UserConfigParams::m_per_player_difficulty)
1359                     spinner_value /= 2;
1360                 assert(m_kart_widgets[n].getAssociatedPlayer()->getProfile() ==
1361                     PlayerManager::get()->getPlayer(spinner_value));
1362             }
1363         }
1364     }
1365 
1366     // perform actual checking
1367     for (int n=0; n<amount; n++)
1368     {
1369         // skip players that took a guest account, they can be many on the
1370         // same identity in this case
1371         if (m_kart_widgets[n].getAssociatedPlayer()->getProfile()
1372                 ->isGuestAccount())
1373         {
1374             continue;
1375         }
1376 
1377         // check if another kart took the same identity as the current one
1378         for (int m=n+1; m<amount; m++)
1379         {
1380 
1381             // check if 2 players took the same name
1382             if (m_kart_widgets[n].getAssociatedPlayer()->getProfile() ==
1383                     m_kart_widgets[m].getAssociatedPlayer()->getProfile())
1384             {
1385                 // two players took the same name. check if one is ready
1386                 if (!m_kart_widgets[n].isReady() &&
1387                         m_kart_widgets[m].isReady())
1388                 {
1389                     // player m is ready, so player n should not choose
1390                     // this name
1391                     m_kart_widgets[n].m_player_ident_spinner
1392                     ->markAsIncorrect();
1393                 }
1394                 else if (m_kart_widgets[n].isReady() &&
1395                          !m_kart_widgets[m].isReady())
1396                 {
1397                     // player n is ready, so player m should not
1398                     // choose this name
1399                     m_kart_widgets[m].m_player_ident_spinner
1400                     ->markAsIncorrect();
1401                 }
1402                 else if (m_kart_widgets[n].isReady() &&
1403                          m_kart_widgets[m].isReady())
1404                 {
1405                     // it should be impossible for two players to confirm
1406                     // they're ready with the same name
1407                     assert(false);
1408                 }
1409 
1410                 ok = false;
1411             }
1412         } // end for
1413     }
1414 
1415     return ok;
1416 }   // validateIdentChoices
1417 
1418 // -----------------------------------------------------------------------------
1419 
validateKartChoices()1420 bool KartSelectionScreen::validateKartChoices()
1421 {
1422     bool ok = true;
1423 
1424     const unsigned int amount = m_kart_widgets.size();
1425 
1426     // reset all marks, we'll re-add them next if errors are still there
1427     for (unsigned int n=0; n<amount; n++)
1428     {
1429         m_kart_widgets[n].m_model_view->unsetBadge(BAD_BADGE);
1430     }
1431 
1432     // Check if we have enough karts for everybody. If there are more
1433     // players than karts then just allow duplicates
1434     DynamicRibbonWidget* w = getWidget<DynamicRibbonWidget>("karts");
1435     assert( w != NULL );
1436     const unsigned int availableKartCount = (unsigned int)w->getItems().size();
1437     if (amount > availableKartCount) return true;
1438 
1439     // Check everyone for duplicates
1440     for (unsigned int n=0; n<amount; n++)
1441     {
1442         for (unsigned int m=n+1; m<amount; m++)
1443         {
1444             // check if 2 players took the same name
1445             if (sameKart(m_kart_widgets[n], m_kart_widgets[m]))
1446             {
1447                 if (UserConfigParams::logGUI())
1448                 {
1449                     Log::warn("KartSelectionScreen", "Kart conflict!!");
1450                     Log::warn("KartSelectionScreen", "    Player %u chose %s",n,
1451                               m_kart_widgets[n].getKartInternalName().c_str());
1452                     Log::warn("KartSelectionScreen", "    Player %u chose %s",m,
1453                               m_kart_widgets[m].getKartInternalName().c_str());
1454                 }
1455 
1456                 // two players took the same kart. check if one is ready
1457                 if (!m_kart_widgets[n].isReady() &&
1458                         m_kart_widgets[m].isReady())
1459                 {
1460                     if (UserConfigParams::logGUI())
1461                        Log::info("KartSelectionScreen", "    --> Setting red badge on player %u", n);
1462 
1463                     // player m is ready, so player n should not choose
1464                     // this name
1465                     m_kart_widgets[n].m_model_view->setBadge(BAD_BADGE);
1466                 }
1467                 else if (m_kart_widgets[n].isReady() &&
1468                          !m_kart_widgets[m].isReady())
1469                 {
1470                     if (UserConfigParams::logGUI())
1471                         Log::info("KartSelectionScreen", "    --> Setting red badge on player %u",m);
1472 
1473                     // player n is ready, so player m should not
1474                     // choose this name
1475                     m_kart_widgets[m].m_model_view->setBadge(BAD_BADGE);
1476                 }
1477                 else if (m_kart_widgets[n].isReady() &&
1478                          m_kart_widgets[m].isReady())
1479                 {
1480                     // it should be impossible for two players to confirm
1481                     // they're ready with the same kart
1482                     assert(false);
1483                 }
1484 
1485                 // we know it's not ok (but don't stop right now, all bad
1486                 // ones need red badges)
1487                 ok = false;
1488             }
1489         } // end for
1490     }
1491 
1492     return ok;
1493 
1494 }   // validateKartChoices
1495 
1496 // ----------------------------------------------------------------------------
1497 
renumberKarts()1498 void KartSelectionScreen::renumberKarts()
1499 {
1500     DynamicRibbonWidget* w = getWidget<DynamicRibbonWidget>("karts");
1501     assert( w != NULL );
1502     Widget* fullarea = getWidget("playerskarts");
1503     int splitWidth = fullarea->m_w / m_kart_widgets.size();
1504     if (m_kart_widgets.size() == 1)
1505         splitWidth /= 2;
1506 
1507     for (unsigned int n=0; n < m_kart_widgets.size(); n++)
1508     {
1509         m_kart_widgets[n].setPlayerID(n);
1510         m_kart_widgets[n].move( fullarea->m_x + splitWidth*n, fullarea->m_y,
1511                                 splitWidth, fullarea->m_h );
1512     }
1513 
1514     w->updateItemDisplay();
1515 }   // renumberKarts
1516 
1517 // ----------------------------------------------------------------------------
getUsableKarts(const std::string & selected_kart_group)1518 PtrVector<const KartProperties, REF> KartSelectionScreen::getUsableKarts(
1519     const std::string& selected_kart_group)
1520 {
1521     PtrVector<const KartProperties, REF> karts;
1522     for(unsigned int i=0; i<kart_properties_manager->getNumberOfKarts(); i++)
1523     {
1524         const KartProperties* prop = kart_properties_manager->getKartById(i);
1525         // Ignore karts that are not in the selected group
1526         if((selected_kart_group != ALL_KART_GROUPS_ID &&
1527             !prop->isInGroup(selected_kart_group)) || isIgnored(prop->getIdent()))
1528             continue;
1529         karts.push_back(prop);
1530     }
1531     karts.insertionSort();
1532     return karts;
1533 }   // getUsableKarts
1534 
1535 // ----------------------------------------------------------------------------
1536 
setKartsFromCurrentGroup()1537 void KartSelectionScreen::setKartsFromCurrentGroup()
1538 {
1539     RibbonWidget* tabs = getWidget<RibbonWidget>("kartgroups");
1540     assert(tabs != NULL);
1541 
1542     std::string selected_kart_group =
1543         tabs->getSelectionIDString(PLAYER_ID_GAME_MASTER);
1544 
1545     UserConfigParams::m_last_used_kart_group = selected_kart_group;
1546 
1547     // This can happen if addons are removed so that also the previously
1548     // selected kart group is removed. In this case, select the
1549     // 'standard' group
1550     if (selected_kart_group != ALL_KART_GROUPS_ID &&
1551         !kart_properties_manager->getKartsInGroup(selected_kart_group).size())
1552     {
1553         selected_kart_group = DEFAULT_GROUP_NAME;
1554     }
1555 
1556     DynamicRibbonWidget* w = getWidget<DynamicRibbonWidget>("karts");
1557     w->clearItems();
1558 
1559     int usable_kart_count = 0;
1560     PtrVector<const KartProperties, REF> karts = getUsableKarts(selected_kart_group);
1561 
1562     if (karts.empty())
1563     {
1564         // In network this will happen if no addons kart on server
1565         PtrVector<const KartProperties, REF> new_karts =
1566             getUsableKarts(DEFAULT_GROUP_NAME);
1567         std::swap(karts.m_contents_vector, new_karts.m_contents_vector);
1568         tabs->select(DEFAULT_GROUP_NAME, PLAYER_ID_GAME_MASTER);
1569     }
1570 
1571     for(unsigned int i=0; i<karts.size(); i++)
1572     {
1573         const KartProperties* prop = karts.get(i);
1574         if (PlayerManager::getCurrentPlayer()->isLocked(prop->getIdent()) &&
1575             !m_multiplayer && !NetworkConfig::get()->isNetworking())
1576         {
1577             w->addItem(_("Locked : solve active challenges to gain access to more!"),
1578                        ID_LOCKED + prop->getIdent(),
1579                        prop->getAbsoluteIconFile(), LOCKED_BADGE,
1580                        IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE);
1581         }
1582         else
1583         {
1584             w->addItem(prop->getName(),
1585                        prop->getIdent(),
1586                        prop->getAbsoluteIconFile(), 0,
1587                        IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE);
1588             usable_kart_count++;
1589         }
1590     }
1591 
1592     // add random
1593     if (usable_kart_count > 1)
1594     {
1595         w->addItem(_("Random Kart"), RANDOM_KART_ID, "/gui/icons/random_kart.png");
1596     }
1597 
1598     w->updateItemDisplay();
1599 }
1600 
1601 // ----------------------------------------------------------------------------
useContinueButton() const1602 bool KartSelectionScreen::useContinueButton() const
1603 {
1604 #ifdef MOBILE_STK
1605     if (m_multiplayer)
1606         return false;
1607     bool multitouch_enabled = (UserConfigParams::m_multitouch_active == 1 &&
1608         irr_driver->getDevice()->supportsTouchDevice()) ||
1609         UserConfigParams::m_multitouch_active > 1;
1610     return multitouch_enabled;
1611 #else
1612     return false;
1613 #endif
1614 }   // useContinueButton
1615 
1616 #if 0
1617 #pragma mark -
1618 #endif
1619 
1620