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