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