1 // -*- Mode: C++; tab-width:2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 // vi:tw=80:et:ts=2:sts=2
3 //
4 // -----------------------------------------------------------------------
5 //
6 // This file is part of RLVM, a RealLive virtual machine clone.
7 //
8 // -----------------------------------------------------------------------
9 //
10 // Copyright (C) 2008 Elliot Glaysher
11 //
12 // This program is free software; you can redistribute it and/or modify
13 // it under the terms of the GNU General Public License as published by
14 // the Free Software Foundation; either version 3 of the License, or
15 // (at your option) any later version.
16 //
17 // This program is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 // GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 // -----------------------------------------------------------------------
26 
27 #include "platforms/gcn/gcn_platform.h"
28 
29 #include <boost/filesystem.hpp>
30 #include <functional>
31 #include <iomanip>
32 #include <queue>
33 #include <string>
34 #include <vector>
35 
36 #include "machine/long_operation.h"
37 #include "machine/rlmachine.h"
38 #include "machine/serialization.h"
39 #include "modules/module_sys_save.h"
40 #include "platforms/gcn/gcn_graphics.h"
41 #include "platforms/gcn/gcn_info_window.h"
42 #include "platforms/gcn/gcn_true_type_font.h"
43 #include "platforms/gcn/gcn_utils.h"
44 #include "systems/base/graphics_system.h"
45 #include "systems/base/rect.h"
46 #include "systems/base/system.h"
47 #include "systems/base/text_system.h"
48 #include "systems/sdl/sdl_event_system.h"
49 #include "utilities/exception.h"
50 #include "utilities/find_font_file.h"
51 #include "libreallive/gameexe.h"
52 
53 using std::bind;
54 using std::placeholders::_1;
55 namespace fs = boost::filesystem;
56 
57 const char* EVENT_CANCEL = "EVENT_CANCEL";
58 
59 // A mapping from the SYSCOM_ integer constants to a string suitable for an
60 // event name.
61 const char* SYSCOM_EVENTS[] = {
62     "SYSCOM_SAVE",                         "SYSCOM_LOAD",
63     "SYSCOM_MESSAGE_SPEED",                "SYSCOM_WINDOW_ATTRIBUTES",
64     "SYSCOM_VOLUME_SETTINGS",              "SYSCOM_DISPLAY_MODE",
65     "SYSCOM_MISCELLANEOUS_SETTINGS",       "NONE",
66     "SYSCOM_VOICE_SETTINGS",               "SYSCOM_FONT_SELECTION",
67     "SYSCOM_BGM_FADE",                     "SYSCOM_BGM_SETTINGS",
68     "SYSCOM_WINDOW_DECORATION_STYLE",      "SYSCOM_AUTO_MODE_SETTINGS",
69     "SYSCOM_RETURN_TO_PREVIOUS_SELECTION", "SYSCOM_USE_KOE",
70     "SYSCOM_DISPLAY_VERSION",              "SYSCOM_SHOW_WEATHER",
71     "SYSCOM_SHOW_OBJECT_1",                "SYSCOM_SHOW_OBJECT_2",
72     "SYSCOM_CLASSIFY_TEXT",  // ??????? Unknown function.
73     "SYSCOM_GENERIC_1",                    "SYSCOM_GENERIC_2",
74     "NONE",                                "SYSCOM_OPEN_MANUAL_PATH",
75     "SYSCOM_SET_SKIP_MODE",                "SYSCOM_AUTO_MODE",
76     "NONE",                                "SYSCOM_MENU_RETURN",
77     "SYSCOM_EXIT_GAME",                    "SYSCOM_HIDE_MENU",
78     "SYSCOM_SHOW_BACKGROUND",              NULL};
79 
80 const int MENU_END = -1;
81 const int MENU_SEPARATOR = -2;
82 const int MENU = -3;
83 
84 struct MenuSpec {
85   // Syscom id >= 0, or a MENU* thing.
86   int16_t syscom_id;
87 
88   // User interface string key, or NULL for syscom default.
89   const char* label;
90 
91   // Event to send back if the button is pressed, or NULL for
92   // SYSCOM_EVENTS[syscom_id]
93   const char* event_name;
94 };
95 
96 const char* MENU_PREFERENCES_EVENT = "MENU_PREFERENCES_EVENT";
97 const MenuSpec MENU_PREFERENCES_MENU[] = {
98     {SYSCOM_SCREEN_MODE, NULL, NULL},
99     {MENU_SEPARATOR, NULL, NULL},
100     {SYSCOM_VOLUME_SETTINGS, NULL, NULL},
101     {MENU_SEPARATOR, NULL, NULL},
102     {SYSCOM_WINDOW_ATTRIBUTES, NULL, NULL},
103     {SYSCOM_WINDOW_DECORATION_STYLE, NULL, NULL},
104     {MENU_SEPARATOR, NULL, NULL},
105     {SYSCOM_FONT_SELECTION, NULL, NULL},
106     {SYSCOM_MESSAGE_SPEED, NULL, NULL},
107     {SYSCOM_AUTO_MODE_SETTINGS, NULL, NULL},
108     {MENU_SEPARATOR, NULL, NULL},
109     {SYSCOM_SHOW_OBJECT_1, NULL, NULL},
110     {MENU_SEPARATOR, NULL, NULL},
111     {SYSCOM_MISCELLANEOUS_SETTINGS, NULL, NULL},
112     {MENU_END, NULL, NULL}};
113 
114 const char* MENU_RETURN_MENU_EVENT = "MENU_RETURN_MENU_EVENT";
115 const MenuSpec MENU_RETURN_MENU[] = {
116     {SYSCOM_HIDE_MENU, "028.000", EVENT_CANCEL},
117     {SYSCOM_MENU_RETURN, "028.001", NULL}, {MENU_END, NULL, NULL}};
118 
119 const char* EXIT_GAME_MENU_EVENT = "EXIT_GAME_MENU_EVENT";
120 const MenuSpec EXIT_GAME_MENU[] = {{SYSCOM_HIDE_MENU, "029.000", EVENT_CANCEL},
121                                    {SYSCOM_EXIT_GAME, "029.001", NULL},
122                                    {MENU_END, NULL, NULL}};
123 
124 // TODO(erg): Things like SYSCOM_MENU_RETURN need to be turned into menu
125 // pointers in their own right.
126 const MenuSpec SYCOM_MAIN_MENU[] = {
127     {SYSCOM_SET_SKIP_MODE, NULL, NULL},
128     {SYSCOM_AUTO_MODE, NULL, NULL},
129     {SYSCOM_SHOW_BACKGROUND, NULL, NULL},
130     {MENU_SEPARATOR, NULL, NULL},
131     {SYSCOM_SAVE, NULL, NULL},
132     {SYSCOM_LOAD, NULL, NULL},
133     {SYSCOM_RETURN_TO_PREVIOUS_SELECTION, NULL, NULL},
134     {MENU_SEPARATOR, NULL, NULL},
135     //  {MENU, "", MENU_PREFERENCES_EVENT},
136     //  {MENU_SEPARATOR, 0, 0},
137     {MENU, "028", MENU_RETURN_MENU_EVENT},
138     {MENU, "029", EXIT_GAME_MENU_EVENT},
139     {MENU_END, NULL, NULL}};
140 
141 // -----------------------------------------------------------------------
142 // GCNPlatformBlocker
143 // -----------------------------------------------------------------------
144 class GCNPlatformBlocker : public LongOperation,
145                            public Renderable,
146                            public RawSDLInputHandler {
147  public:
GCNPlatformBlocker(SDLEventSystem & system,GraphicsSystem & graphics,const std::shared_ptr<GCNPlatform> & platform)148   GCNPlatformBlocker(SDLEventSystem& system,
149                      GraphicsSystem& graphics,
150                      const std::shared_ptr<GCNPlatform>& platform)
151       : event_system_(system), graphics_system_(graphics), platform_(platform) {
152     event_system_.set_raw_sdl_input_handler(this);
153     graphics_system_.AddRenderable(this);
154     platform_->blocker_ = this;
155   }
156 
~GCNPlatformBlocker()157   ~GCNPlatformBlocker() {
158     graphics_system_.RemoveRenderable(this);
159     event_system_.set_raw_sdl_input_handler(NULL);
160     platform_->blocker_ = NULL;
161   }
162 
addTask(const std::function<void (void)> & task)163   void addTask(const std::function<void(void)>& task) {
164     delayed_tasks_.push(task);
165   }
166 
addMachineTask(const std::function<void (RLMachine &)> & task)167   void addMachineTask(const std::function<void(RLMachine&)>& task) {
168     delayed_rlmachine_tasks_.push(task);
169   }
170 
171   // Overridden from LongOperation:
operator ()(RLMachine & machine)172   virtual bool operator()(RLMachine& machine) override {
173     while (delayed_tasks_.size()) {
174       delayed_tasks_.front()();
175       delayed_tasks_.pop();
176     }
177 
178     while (delayed_rlmachine_tasks_.size()) {
179       delayed_rlmachine_tasks_.front()(machine);
180       delayed_rlmachine_tasks_.pop();
181     }
182 
183     machine.system().graphics().ForceRefresh();
184 
185     return platform_->window_stack_.size() == 0;
186   }
187 
188   // Overridden from Renderable:
Render(std::ostream * tree)189   virtual void Render(std::ostream* tree) override { platform_->render(); }
190 
191   // Overridden from RawSDLInputHandler:
pushInput(SDL_Event event)192   virtual void pushInput(SDL_Event event) override {
193     platform_->sdl_input_->pushInput(event);
194   }
195 
196  private:
197   SDLEventSystem& event_system_;
198   GraphicsSystem& graphics_system_;
199   std::shared_ptr<GCNPlatform> platform_;
200 
201   std::queue<std::function<void(void)>> delayed_tasks_;
202   std::queue<std::function<void(RLMachine&)>> delayed_rlmachine_tasks_;
203 };
204 
205 // -----------------------------------------------------------------------
206 // GCNPlatform
207 // -----------------------------------------------------------------------
208 
GCNPlatform(System & system,const Rect & screen_size)209 GCNPlatform::GCNPlatform(System& system, const Rect& screen_size)
210     : Platform(system.gameexe()), blocker_(NULL), screen_size_(screen_size) {
211   initializeGuichan(system, screen_size);
212 }
213 
214 // -----------------------------------------------------------------------
215 
~GCNPlatform()216 GCNPlatform::~GCNPlatform() {
217   toplevel_container_->removeMouseListener(this);
218   guichan_gui_->removeGlobalKeyListener(this);
219 
220   clearWindowStack();
221 }
222 
223 // -----------------------------------------------------------------------
224 
Run(RLMachine & machine)225 void GCNPlatform::Run(RLMachine& machine) { guichan_gui_->logic(); }
226 
227 // -----------------------------------------------------------------------
228 
render()229 void GCNPlatform::render() {
230   try {
231     guichan_gui_->draw();
232   }
233   catch (gcn::Exception& e) {
234     std::ostringstream oss;
235     oss << "Guichan Exception at " << e.getFunction() << ": " << e.getMessage();
236     throw rlvm::Exception(oss.str());
237   }
238 
239   // HACK: Something is either really wrong with Guichan and/or the intel
240   // drivers in Intrepid Ibex. Probably both. Something guichan is
241   // doing/drivers haven't implemented is causing a whole bunch of invalid
242   // enumerant errors.
243   while (glGetError() != GL_NO_ERROR) {}
244 }
245 
246 // -----------------------------------------------------------------------
247 
ShowNativeSyscomMenu(RLMachine & machine)248 void GCNPlatform::ShowNativeSyscomMenu(RLMachine& machine) {
249   pushBlocker(machine);
250   buildSyscomMenuFor("", SYCOM_MAIN_MENU, machine);
251 }
252 
253 // -----------------------------------------------------------------------
254 
InvokeSyscomStandardUI(RLMachine & machine,int syscom)255 void GCNPlatform::InvokeSyscomStandardUI(RLMachine& machine, int syscom) {
256   pushBlocker(machine);
257   if (syscom == SYSCOM_SAVE)
258     blocker_->addMachineTask(bind(&GCNPlatform::MenuSave, this, _1));
259   else if (syscom == SYSCOM_LOAD)
260     blocker_->addMachineTask(bind(&GCNPlatform::MenuLoad, this, _1));
261 }
262 
263 // -----------------------------------------------------------------------
264 
ShowSystemInfo(RLMachine & machine,const RlvmInfo & info)265 void GCNPlatform::ShowSystemInfo(RLMachine& machine, const RlvmInfo& info) {
266   pushBlocker(machine);
267   pushWindowOntoStack(new GCNInfoWindow(machine, info, this));
268 }
269 
270 // -----------------------------------------------------------------------
271 
windowCanceled(GCNWindow * window)272 void GCNPlatform::windowCanceled(GCNWindow* window) {
273   blocker_->addTask(bind(&GCNPlatform::popWindowFromStack, this));
274 }
275 
276 // -----------------------------------------------------------------------
277 
receiveGCNMenuEvent(GCNMenu * menu,const std::string & event)278 void GCNPlatform::receiveGCNMenuEvent(GCNMenu* menu, const std::string& event) {
279   // First, clear the window_stack_
280   blocker_->addTask(bind(&GCNPlatform::clearWindowStack, this));
281 
282   // Handle triggered syscom events
283   for (int i = 0; i <= SYSCOM_SHOW_BACKGROUND; ++i) {
284     if (event == SYSCOM_EVENTS[i]) {
285       blocker_->addMachineTask(bind(&GCNPlatform::InvokeSyscom, this, _1, i));
286       return;
287     }
288   }
289 
290   // Handle our own internal events
291   if (event == MENU_PREFERENCES_EVENT) {
292     blocker_->addMachineTask(bind(
293         &GCNPlatform::buildSyscomMenuFor, this, "", MENU_PREFERENCES_MENU, _1));
294   } else if (event == MENU_RETURN_MENU_EVENT) {
295     blocker_->addMachineTask(bind(&GCNPlatform::buildSyscomMenuFor,
296                                   this,
297                                   GetSyscomString("MENU_RETURN_MESS_STR"),
298                                   MENU_RETURN_MENU,
299                                   _1));
300   } else if (event == EXIT_GAME_MENU_EVENT) {
301     blocker_->addMachineTask(bind(&GCNPlatform::buildSyscomMenuFor,
302                                   this,
303                                   GetSyscomString("GAME_END_MESS_STR"),
304                                   EXIT_GAME_MENU,
305                                   _1));
306   }
307 }
308 
309 // -----------------------------------------------------------------------
310 
saveEvent(int slot)311 void GCNPlatform::saveEvent(int slot) {
312   blocker_->addTask(bind(&GCNPlatform::clearWindowStack, this));
313   blocker_->addMachineTask(bind(&GCNPlatform::DoSave, this, _1, slot));
314 }
315 
316 // -----------------------------------------------------------------------
317 
loadEvent(int slot)318 void GCNPlatform::loadEvent(int slot) {
319   blocker_->addTask(bind(&GCNPlatform::clearWindowStack, this));
320   blocker_->addMachineTask(bind(&GCNPlatform::DoLoad, this, _1, slot));
321 }
322 
323 // -----------------------------------------------------------------------
324 
mouseClicked(gcn::MouseEvent & mouseEvent)325 void GCNPlatform::mouseClicked(gcn::MouseEvent& mouseEvent) {
326   if (mouseEvent.getSource() == toplevel_container_.get() &&
327       mouseEvent.getButton() == gcn::MouseEvent::RIGHT) {
328     blocker_->addTask(bind(&GCNPlatform::clearWindowStack, this));
329   }
330 }
331 
332 // -----------------------------------------------------------------------
333 
keyReleased(gcn::KeyEvent & keyEvent)334 void GCNPlatform::keyReleased(gcn::KeyEvent& keyEvent) {
335   if (keyEvent.getKey() == gcn::Key::ESCAPE)
336     blocker_->addTask(bind(&GCNPlatform::clearWindowStack, this));
337 }
338 
339 // -----------------------------------------------------------------------
340 // Private
341 // -----------------------------------------------------------------------
342 
pushBlocker(RLMachine & machine)343 void GCNPlatform::pushBlocker(RLMachine& machine) {
344   if (blocker_ == NULL) {
345     // Block the world!
346     SDLEventSystem& event =
347         dynamic_cast<SDLEventSystem&>(machine.system().event());
348     GraphicsSystem& graphics = machine.system().graphics();
349     machine.PushLongOperation(
350         new GCNPlatformBlocker(event, graphics, shared_from_this()));
351   }
352 }
353 
354 // -----------------------------------------------------------------------
355 
initializeGuichan(System & system,const Rect & screen_size)356 void GCNPlatform::initializeGuichan(System& system, const Rect& screen_size) {
357   sdl_image_loader_.reset(new gcn::OpenGLSDLImageLoader());
358   gcn::Image::setImageLoader(sdl_image_loader_.get());
359 
360   sdl_input_.reset(new gcn::SDLInput());
361 
362   opengl_graphics_.reset(
363       new GCNGraphics(screen_size.width(), screen_size.height()));
364 
365   guichan_gui_.reset(new gcn::Gui);
366   guichan_gui_->setTabbingEnabled(false);  // Do I want this on?
367   guichan_gui_->setGraphics(opengl_graphics_.get());
368   guichan_gui_->setInput(sdl_input_.get());
369   guichan_gui_->addGlobalKeyListener(this);
370 
371   toplevel_container_.reset(new gcn::Container);
372   toplevel_container_->setBaseColor(gcn::Color(0x000000));
373   toplevel_container_->setOpaque(false);
374   toplevel_container_->setDimension(rectConvert(screen_size));
375   toplevel_container_->addMouseListener(this);
376   guichan_gui_->setTop(toplevel_container_.get());
377 
378   fs::path font_file = FindFontFile(system);
379   global_font_.reset(new GCNTrueTypeFont(font_file.string().c_str(), 12));
380   gcn::Widget::setGlobalFont(global_font_.get());
381 }
382 
383 // -----------------------------------------------------------------------
384 
buildSyscomMenuFor(const std::string & label,const MenuSpec menu_items[],RLMachine & machine)385 void GCNPlatform::buildSyscomMenuFor(const std::string& label,
386                                      const MenuSpec menu_items[],
387                                      RLMachine& machine) {
388   System& sys = machine.system();
389 
390   std::vector<GCNMenuButton> buttons;
391   for (int i = 0; menu_items[i].syscom_id != MENU_END; ++i) {
392     GCNMenuButton button_definition;
393 
394     if (menu_items[i].syscom_id == MENU_SEPARATOR) {
395       button_definition.separator = true;
396       buttons.push_back(button_definition);
397     } else if (menu_items[i].syscom_id == MENU) {
398       button_definition.label = GetSyscomString(menu_items[i].label);
399       button_definition.action = menu_items[i].event_name;
400       button_definition.enabled = true;
401       buttons.push_back(button_definition);
402     } else {
403       int id = menu_items[i].syscom_id;
404       int enabled = sys.IsSyscomEnabled(id);
405       if (enabled != SYSCOM_INVISIBLE) {
406         std::ostringstream labelss;
407         labelss << std::setw(3) << std::setfill('0') << id;
408 
409         if (menu_items[i].label == NULL)
410           button_definition.label = GetSyscomString(labelss.str());
411         else
412           button_definition.label = GetSyscomString(menu_items[i].label);
413 
414         if (menu_items[i].event_name == NULL)
415           button_definition.action = SYSCOM_EVENTS[id];
416         else
417           button_definition.action = menu_items[i].event_name;
418 
419         button_definition.enabled = enabled != SYSCOM_GREYED_OUT;
420         buttons.push_back(button_definition);
421       }
422     }
423   }
424 
425   pushWindowOntoStack(new GCNMenu(label, buttons, this));
426 }
427 
428 // -----------------------------------------------------------------------
429 
clearWindowStack()430 void GCNPlatform::clearWindowStack() {
431   while (window_stack_.size())
432     popWindowFromStack();
433 }
434 
435 // -----------------------------------------------------------------------
436 
popWindowFromStack()437 void GCNPlatform::popWindowFromStack() {
438   GCNWindow* to_pop = window_stack_.back();
439   window_stack_.pop_back();
440 
441   toplevel_container_->remove(to_pop);
442   delete to_pop;
443 }
444 
445 // -----------------------------------------------------------------------
446 
pushWindowOntoStack(GCNWindow * window)447 void GCNPlatform::pushWindowOntoStack(GCNWindow* window) {
448   window->centerInWindow(screen_size_.size());
449   window_stack_.push_back(window);
450   toplevel_container_->add(window);
451 }
452 
453 // -----------------------------------------------------------------------
454 // Event Handler Functions
455 // -----------------------------------------------------------------------
456 
MenuSave(RLMachine & machine)457 void GCNPlatform::MenuSave(RLMachine& machine) {
458   pushWindowOntoStack(
459       new GCNSaveLoadWindow(machine, GCNSaveLoadWindow::DO_SAVE, this));
460 }
461 
462 // -----------------------------------------------------------------------
463 
DoSave(RLMachine & machine,int slot)464 void GCNPlatform::DoSave(RLMachine& machine, int slot) {
465   Serialization::saveGlobalMemory(machine);
466   Serialization::saveGameForSlot(machine, slot);
467 }
468 
469 // -----------------------------------------------------------------------
470 
MenuLoad(RLMachine & machine)471 void GCNPlatform::MenuLoad(RLMachine& machine) {
472   pushWindowOntoStack(
473       new GCNSaveLoadWindow(machine, GCNSaveLoadWindow::DO_LOAD, this));
474 }
475 
476 // -----------------------------------------------------------------------
477 
DoLoad(RLMachine & machine,int slot)478 void GCNPlatform::DoLoad(RLMachine& machine, int slot) {
479   machine.ClearLongOperationsOffBackOfStack();
480   Sys_load()(machine, slot);
481 }
482 
483 // -----------------------------------------------------------------------
484 
InvokeSyscom(RLMachine & machine,int syscom)485 void GCNPlatform::InvokeSyscom(RLMachine& machine, int syscom) {
486   machine.system().InvokeSyscom(machine, syscom);
487 }
488