1 /*
2 * Copyright (C) 2000-2013 The Exult Team
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 */
18
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #include <cstdlib>
25 #include <cstring>
26 #include <unistd.h>
27 #include "menulist.h"
28 #include "Audio.h"
29 #include "Configuration.h"
30 #include "databuf.h"
31 #include "exult.h"
32 #include "exult_constants.h"
33 #include "exult_flx.h"
34 #include "files/U7fileman.h"
35 #include "files/U7file.h"
36 #include "files/utils.h"
37 #include "font.h"
38 #include "game.h"
39 #include "gamemgr/bggame.h"
40 #include "gamemgr/sigame.h"
41 #include "gamemgr/devgame.h"
42 #include "gamewin.h"
43 #include "keys.h"
44 #include "mouse.h"
45 #include "palette.h"
46 #include "shapeid.h"
47 #include "gamemgr/modmgr.h"
48 #include "shapes/miscinf.h"
49 #include "array_size.h"
50
51 using std::cout;
52 using std::endl;
53 using std::ifstream;
54 using std::strcpy;
55 using std::string;
56
57 bool Game::new_game_flag = false;
58 bool Game::editing_flag = false;
59 Game *game = nullptr;
60 Exult_Game Game::game_type = NONE;
61 Game_Language Game::language = ENGLISH;
62 bool Game::expansion = false;
63 bool Game::sibeta = false;
64
65 static char av_name[17] = "";
66 static int av_sex = -1;
67 static int av_skin = -1;
68
69 std::string Game::gametitle;
70 std::string Game::modtitle;
71
72 unsigned int Game::ticks = 0;
73
Game()74 Game::Game() {
75 try { // Okay to fail if development game.
76 menushapes.load(MAINSHP_FLX, PATCH_MAINSHP);
77 } catch (const exult_exception &e) {
78 if (!is_editing())
79 throw;
80 }
81 jive = false;
82 gwin = Game_window::get_instance();
83 win = gwin->get_win();
84 ibuf = win->get_ib8();
85 topx = (gwin->get_width() - 320) / 2;
86 topy = (gwin->get_height() - 200) / 2;
87 centerx = gwin->get_width() / 2;
88 centery = gwin->get_height() / 2;
89 }
90
~Game()91 Game::~Game() {
92 game_type = NONE;
93 language = ENGLISH;
94 delete xml;
95 while (!xmlstrings.empty()) {
96 char *str = xmlstrings.back();
97 xmlstrings.pop_back();
98 delete [] str;
99 }
100 U7FileManager::get_ptr()->reset();
101 Shapeinfo_lookup::reset();
102 }
103
create_game(BaseGameInfo * mygame)104 Game *Game::create_game(BaseGameInfo *mygame) {
105 mygame->setup_game_paths();
106 gametitle = mygame->get_cfgname();
107 modtitle = mygame->get_mod_title();
108 game_type = mygame->get_game_type();
109 language = mygame->get_game_language();
110 expansion = mygame->have_expansion();
111 sibeta = mygame->is_si_beta();
112 editing_flag = mygame->being_edited();
113
114 // Need to do this here. Maybe force on for EXULT_DEVEL_GAME too?
115 std::string str;
116 config->value("config/gameplay/bg_paperdolls", str, "yes");
117 sman->set_paperdoll_status(game_type == SERPENT_ISLE || str == "yes");
118 config->set("config/gameplay/bg_paperdolls", str, true);
119 char buf[256];
120 if (!mygame->get_mod_title().empty())
121 snprintf(buf, 256, " with the '%s' modification.",
122 mygame->get_mod_title().c_str());
123 else
124 buf[0] = 0;
125 switch (game_type) {
126 case BLACK_GATE:
127 cout << "Starting a BLACK GATE game" << buf << endl;
128 game = new BG_Game();
129 break;
130 case SERPENT_ISLE:
131 cout << "Starting a SERPENT ISLE game" << endl;
132 game = new SI_Game();
133 break;
134 case EXULT_DEVEL_GAME:
135 cout << "Starting '" << gametitle << "' game" << endl;
136 game = new DEV_Game();
137 break;
138 case EXULT_MENU_GAME:
139 cout << "Starting '" << gametitle << "' game" << endl;
140 game = new DEV_Game();
141 break;
142 default:
143 cout << "Unrecognized game type!" << endl;
144 game = nullptr;
145 }
146
147 cout << "Game path settings:" << std::endl;
148 cout << "Static : " << get_system_path("<STATIC>") << endl;
149 cout << "Gamedat : " << get_system_path("<GAMEDAT>") << endl;
150 cout << "Savegame: " << get_system_path("<SAVEGAME>") << endl;
151 if (is_system_path_defined("<PATCH>"))
152 cout << "Patch : " << get_system_path("<PATCH>") << endl;
153 else
154 cout << "Patch : none" << endl;
155 cout << endl;
156
157 return game;
158 }
159
160 const char *xml_root = "Game_config";
161
add_shape(const char * name,int shapenum)162 void Game::add_shape(const char *name, int shapenum) {
163 shapes[name] = shapenum;
164 }
165
get_shape(const char * name)166 int Game::get_shape(const char *name) {
167 if (xml) {
168 string key = string(xml_root) + "/shapes/" + name;
169 int ret;
170 xml->value(key, ret, 0);
171 return ret;
172 } else
173 return shapes[name];
174 }
175
add_resource(const char * name,const char * str,int num)176 void Game::add_resource(const char *name, const char *str, int num) {
177 resources[name].str = str;
178 resources[name].num = num;
179 }
180
get_resource(const char * name)181 const str_int_pair &Game::get_resource(const char *name) {
182 auto it = resources.find(name);
183 if (it != resources.end())
184 return it->second;
185 else if (xml) {
186 string key = string(xml_root) + "/resources/" + name;
187 string str;
188 xml->value(key, str, "");
189 char *res = newstrdup(str.c_str());
190 // This is used to prevent leaks.
191 key += "/num";
192 int num;
193 xml->value(key, num, 0);
194 // Add it for next time.
195 resources[name].str = res;
196 resources[name].num = num;
197 xmlstrings.push_back(res);
198 return resources[name];
199 } else {
200 char buf[250];
201 snprintf(buf, array_size(buf),
202 "Game::get_resource: Illegal resource requested: '%s'", name);
203 throw exult_exception(buf);
204 }
205 }
206
207 /*
208 * Write out game resources/shapes to "patch/exultgame.xml".
209 */
write_game_xml()210 void Game::write_game_xml() {
211 string name = get_system_path("<PATCH>/exultgame.xml");
212
213 U7mkdir("<PATCH>", 0755); // Create dir. if not already there.
214 if (U7exists(name))
215 U7remove(name.c_str());
216 string root = xml_root;
217 Configuration xml(name, root);
218 for (auto& resource : resources) {
219 string key = root;
220 key += "/resources/";
221 key += resource.first;
222 str_int_pair &val = resource.second;
223 if (val.str)
224 xml.set(key.c_str(), val.str, false);
225 if (val.num != 0) {
226 string valkey = key;
227 valkey += "/num";
228 xml.set(valkey.c_str(), val.num, false);
229 }
230 }
231 for (auto& shape : shapes) {
232 string key = root;
233 key += "/shapes/";
234 key += shape.first;
235 int num = shape.second;
236 xml.set(key.c_str(), num, false);
237 }
238 xml.write_back();
239 }
240
241 /*
242 * Read in game resources/shapes. First try 'name1', then
243 * "exultgame.xml" in the "patch" and "static" directories.
244 * Output: true if successful.
245 */
read_game_xml(const char * name1)246 bool Game::read_game_xml(const char *name1) {
247 const char *nm;
248
249 if (name1 && U7exists(name1))
250 nm = name1;
251 else if (!U7exists(nm = "<PATCH>/exultgame.xml")) {
252 if (!U7exists(nm = "<STATIC>/exultgame.xml"))
253 return false;
254 }
255 xml = new Configuration;
256 string namestr = get_system_path(nm);
257 xml->read_abs_config_file(namestr);
258 std::cout << "Reading game configuration from '" << namestr.c_str() <<
259 "'." << std::endl;
260 return true;
261 }
262
263
show_menu(bool skip)264 bool Game::show_menu(bool skip) {
265 int menuy = topy + 120;
266 // Brand-new game in development?
267 if (skip || (is_editing() && !U7exists(MAINSHP_FLX))) {
268 bool first = !U7exists(IDENTITY);
269 if (first)
270 set_avname("Newbie");
271 return gwin->init_gamedat(first);
272 }
273 IExultDataSource mouse_data(MAINSHP_FLX, PATCH_MAINSHP, 19);
274 menu_mouse = new Mouse(gwin, mouse_data);
275
276 top_menu();
277 MenuList *menu = nullptr;
278
279
280 int menuchoices[] = { 0x04, 0x05, 0x08, 0x06, 0x11, 0x12, 0x07 };
281 int num_choices = array_size(menuchoices);
282
283 Vga_file exult_flx(BUNDLE_CHECK(BUNDLE_EXULT_FLX, EXULT_FLX));
284 char npc_name[16];
285 snprintf(npc_name, 16, "Exult");
286 bool play = false;
287 bool fadeout = true;
288 bool exitmenu = false;
289
290 do {
291 if (!menu) {
292 menu = new MenuList();
293 int offset = 0;
294 for (int i = 0; i < num_choices; i++) {
295 if ((i != 4 && i != 5) || (i == 4 && U7exists("<SAVEGAME>/quotes.flg")) || (i == 5 && U7exists("<SAVEGAME>/endgame.flg"))) {
296 Shape_frame *f0 = menushapes.get_shape(menuchoices[i], 0);
297 Shape_frame *f1 = menushapes.get_shape(menuchoices[i], 1);
298 assert(f0 != nullptr && f1 != nullptr);
299 auto *entry = new MenuEntry(f1, f0, centerx, menuy + offset);
300 entry->set_id(i);
301 menu->add_entry(entry);
302 offset += f1->get_ybelow() + 3;
303 }
304 }
305 menu->set_selection(2);
306 }
307
308 bool created = false;
309 int choice = menu->handle_events(gwin, menu_mouse);
310 switch (choice) {
311 case -1: // Exit
312 #ifdef __IPHONEOS__
313 break;
314 #else
315 pal->fade_out(c_fade_out_time);
316 Audio::get_ptr()->stop_music();
317 delete menu_mouse;
318 delete menu;
319 throw quit_exception();
320 #endif
321 case 0: // Intro
322 if (game_type == EXULT_DEVEL_GAME)
323 break;
324 pal->fade_out(c_fade_out_time);
325 play_intro();
326 gwin->clear_screen(true);
327 top_menu();
328 break;
329 case 2: // Journey Onwards
330 created = gwin->init_gamedat(false);
331 if (!created) {
332 show_journey_failed();
333 gwin->clear_screen(true);
334 top_menu();
335 menu->set_selection(1);
336 break;
337 }
338 exitmenu = true;
339 fadeout = true;
340 play = true;
341 break;
342 case 1: // New Game
343 if (new_game(menushapes))
344 exitmenu = true;
345 else
346 break;
347 fadeout = false;
348 play = true;
349 break;
350 case 3: // Credits
351 pal->fade_out(c_fade_out_time);
352 show_credits();
353 delete menu;
354 menu = nullptr;
355 top_menu();
356 break;
357 case 4: // Quotes
358 pal->fade_out(c_fade_out_time);
359 show_quotes();
360 top_menu();
361 break;
362 case 5: // End Game
363 if (game_type == EXULT_DEVEL_GAME)
364 break;
365 pal->fade_out(c_fade_out_time);
366 end_game(true);
367 top_menu();
368 break;
369 case 6: // Return to Menu
370 play = false;
371 exitmenu = true;
372 fadeout = true;
373 break;
374 default:
375 break;
376 }
377 } while (!exitmenu);
378
379 if (fadeout) {
380 pal->fade_out(c_fade_out_time);
381 gwin->clear_screen(true);
382 }
383 delete menu;
384 Audio::get_ptr()->stop_music();
385 delete menu_mouse;
386 return play;
387 }
388
journey_failed_text()389 void Game::journey_failed_text() {
390 Font *font = fontManager.get_font("MENU_FONT");
391 font->center_text(ibuf, centerx, centery + 30, "You must start a new game first.");
392 font->center_text(ibuf, centerx, centery + 42, "Press ESC to return.");
393 pal->fade_in(50);
394 while (!wait_delay(10))
395 ;
396 pal->fade_out(50);
397 }
398
get_avname()399 const char *Game::get_avname() {
400 if (av_name[0])
401 return av_name;
402 else
403 return nullptr;
404 }
405
get_avsex()406 int Game::get_avsex() {
407 return av_sex;
408 }
get_avskin()409 int Game::get_avskin() {
410 return av_skin;
411 }
412
413 // Assume safe
set_avname(const char * name)414 void Game::set_avname(const char *name) {
415 strcpy(av_name, name);
416 }
417
set_avsex(int sex)418 void Game::set_avsex(int sex) {
419 av_sex = sex;
420 }
421
set_avskin(int skin)422 void Game::set_avskin(int skin) {
423 av_skin = skin;
424 }
425
clear_avname()426 void Game::clear_avname() {
427 av_name[0] = 0;
428 new_game_flag = false;
429 }
430
clear_avsex()431 void Game::clear_avsex() {
432 av_sex = -1;
433 }
434
clear_avskin()435 void Game::clear_avskin() {
436 av_skin = -1;
437 }
438
439 // wait ms milliseconds, while cycling colours startcol to startcol+ncol-1
440 // return 0 if time passed completly, 1 if user pressed any key or mouse button,
441 // and 2 if user pressed Return/Enter
wait_delay(int ms,int startcol,int ncol,int rotspd)442 int wait_delay(int ms, int startcol, int ncol, int rotspd) {
443 SDL_Event event;
444 static uint32 last_b3_click = 0;
445 unsigned long delay;
446 int loops;
447
448 int loopinterval = (ncol == 0) ? 50 : 10;
449 if (!ms) ms = 1;
450 if (ms <= 2 * loopinterval) {
451 delay = ms;
452 loops = 1;
453 } else {
454 delay = loopinterval;
455 loops = ms / static_cast<long>(delay);
456 }
457 Game_window *gwin = Game_window::get_instance();
458 int rot_speed = rotspd << (gwin->get_win()->fast_palette_rotate() ? 0 : 1);
459
460 static unsigned long last_rotate = 0;
461
462 for (int i = 0; i < loops; i++) {
463 unsigned long ticks1 = SDL_GetTicks();
464 // this may be a bit risky... How fast can events be generated?
465 while (SDL_PollEvent(&event)) {
466 switch (event.type) {
467 case SDL_KEYDOWN:
468 switch (event.key.keysym.sym) {
469 case SDLK_RSHIFT:
470 case SDLK_LSHIFT:
471 case SDLK_RCTRL:
472 case SDLK_LCTRL:
473 case SDLK_RALT:
474 case SDLK_LALT:
475 case SDLK_RGUI:
476 case SDLK_LGUI:
477 case SDLK_NUMLOCKCLEAR:
478 case SDLK_CAPSLOCK:
479 case SDLK_SCROLLLOCK:
480 break;
481 case SDLK_s:
482 if ((event.key.keysym.mod & KMOD_ALT) &&
483 (event.key.keysym.mod & KMOD_CTRL))
484 make_screenshot(true);
485 break;
486 case SDLK_ESCAPE:
487 return 1;
488 case SDLK_SPACE:
489 case SDLK_RETURN:
490 case SDLK_KP_ENTER:
491 return 2;
492 default:
493 break;
494 }
495 break;
496 case SDL_MOUSEBUTTONDOWN:
497 break;
498 case SDL_MOUSEBUTTONUP: {
499 if (event.button.button == 3 || event.button.button == 1) {
500 if (ticks1 - last_b3_click < 500)
501 return 1;
502 last_b3_click = ticks1;
503 }
504 break;
505 }
506 default:
507 break;
508 }
509 }
510 unsigned long ticks2 = SDL_GetTicks();
511 if (ticks2 - ticks1 > delay)
512 i += (ticks2 - ticks1) / delay - 1;
513 else
514 SDL_Delay(delay - (ticks2 - ticks1));
515 if (abs(ncol) > 1 && ticks2 > last_rotate + rot_speed) {
516 gwin->get_win()->rotate_colors(startcol, ncol, 1);
517 while (ticks2 > last_rotate + rot_speed)
518 last_rotate += rot_speed;
519 gwin->get_win()->show();
520 }
521 }
522
523 return 0;
524 }
525