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 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 #include "bggame.h"
24
25 #include "Audio.h"
26 #include "AudioMixer.h"
27 #include "Configuration.h"
28 #include "array_size.h"
29 #include "data/bg/introsfx_mt32_flx.h"
30 #include "data/exult_bg_flx.h"
31 #include "databuf.h"
32 #include "exult.h"
33 #include "exult_constants.h"
34 #include "files/U7file.h"
35 #include "files/utils.h"
36 #include "flic/playfli.h"
37 #include "fnames.h"
38 #include "font.h"
39 #include "gamewin.h"
40 #include "gump_utils.h"
41 #include "imagewin/ArbScaler.h"
42 #include "imagewin/imagewin.h"
43 #include "items.h"
44 #include "mappatch.h"
45 #include "miscinf.h"
46 #include "modmgr.h"
47 #include "palette.h"
48 #include "shapeid.h"
49 #include "sigame.h"
50 #include "touchui.h"
51 #include "txtscroll.h"
52
53 #include <SDL.h>
54 #include <SDL_events.h>
55
56 #include <cctype>
57 #include <cstring>
58 #include <string>
59 #include <typeinfo>
60 #include <utility>
61 #include <vector>
62 #include <memory>
63
64 using std::abs;
65 using std::make_unique;
66 using std::rand;
67 using std::strchr;
68 using std::strlen;
69 using std::unique_ptr;
70
71 enum {
72 ultima_text_shp = 0x0D,
73 butterfly_shp = 0x0E,
74 lord_british_shp = 0x11,
75 trees_shp = 0x12,
76
77 guardian_mouth_shp = 0x1E,
78 guardian_forehead_shp = 0x1F,
79 guardian_eyes_shp = 0x20
80 };
81
82 enum {
83 bird_song_midi = 0,
84 home_song_midi = 1,
85 guardian_midi = 2,
86 menu_midi = 3,
87 credits_midi = 4,
88 quotes_midi = 5
89 };
90
BG_Game()91 BG_Game::BG_Game()
92 : shapes(ENDSHAPE_FLX, -1, PATCH_ENDSHAPE) {
93 if (!read_game_xml()) {
94 add_shape("gumps/check", 2);
95 add_shape("gumps/fileio", 3);
96 add_shape("gumps/fntext", 4);
97 add_shape("gumps/loadbtn", 5);
98 add_shape("gumps/savebtn", 6);
99 add_shape("gumps/halo", 7);
100 add_shape("gumps/disk", 24);
101 add_shape("gumps/heart", 25);
102 add_shape("gumps/statatts", 28);
103 add_shape("gumps/musicbtn", 29);
104 add_shape("gumps/speechbtn", 30);
105 add_shape("gumps/soundbtn", 31);
106 add_shape("gumps/spellbook", 43);
107 add_shape("gumps/statsdisplay", 47);
108 add_shape("gumps/combat", 46);
109 add_shape("gumps/quitbtn", 56);
110 add_shape("gumps/yesnobox", 69);
111 add_shape("gumps/yesbtn", 70);
112 add_shape("gumps/nobtn", 71);
113 add_shape("gumps/book", 32);
114 add_shape("gumps/scroll", 55);
115 add_shape("gumps/combatmode", 12);
116 add_shape("gumps/slider", 14);
117 add_shape("gumps/slider_diamond", 15);
118 add_shape("gumps/slider_right", 16);
119 add_shape("gumps/slider_left", 17);
120
121 add_shape("gumps/box", 0);
122 add_shape("gumps/crate", 1);
123 add_shape("gumps/barrel", 8);
124 add_shape("gumps/bag", 9);
125 add_shape("gumps/backpack", 10);
126 add_shape("gumps/basket", 11);
127 add_shape("gumps/chest", 22);
128 add_shape("gumps/shipshold", 26);
129 add_shape("gumps/drawer", 27);
130 add_shape("gumps/woodsign", 49);
131 add_shape("gumps/tombstone", 50);
132 add_shape("gumps/goldsign", 51);
133 add_shape("gumps/body", 53);
134
135 add_shape("gumps/scroll_spells", 0xfff);
136 add_shape("gumps/spell_scroll", 0xfff);
137 add_shape("gumps/jawbone", 0xfff);
138 add_shape("gumps/tooth", 0xfff);
139
140 add_shape("sprites/map", 22);
141 add_shape("sprites/cheatmap", EXULT_BG_FLX_BGMAP_SHP);
142
143 const char *exultflx = BUNDLE_CHECK(BUNDLE_EXULT_FLX, EXULT_FLX);
144 const char *gameflx = BUNDLE_CHECK(BUNDLE_EXULT_BG_FLX, EXULT_BG_FLX);
145
146 add_resource("files/shapes/count", nullptr, 9);
147 add_resource("files/shapes/0", SHAPES_VGA, 0);
148 add_resource("files/shapes/1", FACES_VGA, 0);
149 add_resource("files/shapes/2", GUMPS_VGA, 0);
150 add_resource("files/shapes/3", SPRITES_VGA, 0);
151 add_resource("files/shapes/4", MAINSHP_FLX, 0);
152 add_resource("files/shapes/5", ENDSHAPE_FLX, 0);
153 add_resource("files/shapes/6", FONTS_VGA, 0);
154 add_resource("files/shapes/7", exultflx, 0);
155 add_resource("files/shapes/8", gameflx, 0);
156
157 add_resource("files/gameflx", gameflx, 0);
158
159 add_resource("files/paperdolvga", gameflx, EXULT_BG_FLX_BG_PAPERDOL_VGA);
160 add_resource("files/mrfacesvga", gameflx, EXULT_BG_FLX_BG_MR_FACES_VGA);
161 add_resource("config/defaultkeys", gameflx, EXULT_BG_FLX_DEFAULTKEYS_TXT);
162 add_resource("config/bodies", gameflx, EXULT_BG_FLX_BODIES_TXT);
163 add_resource("config/paperdol_info", gameflx, EXULT_BG_FLX_PAPERDOL_INFO_TXT);
164 add_resource("config/shape_info", gameflx, EXULT_BG_FLX_SHAPE_INFO_TXT);
165 add_resource("config/shape_files", gameflx, EXULT_BG_FLX_SHAPE_FILES_TXT);
166 add_resource("config/avatar_data", gameflx, EXULT_BG_FLX_AVATAR_DATA_TXT);
167 add_resource("config/autonotes", gameflx, EXULT_BG_FLX_AUTONOTES_TXT);
168 add_resource("files/intro_hand", gameflx, EXULT_BG_FLX_INTRO_HAND_SHP);
169
170 add_resource("palettes/count", nullptr, 18);
171 add_resource("palettes/0", PALETTES_FLX, 0);
172 add_resource("palettes/1", PALETTES_FLX, 1);
173 add_resource("palettes/2", PALETTES_FLX, 2);
174 add_resource("palettes/3", PALETTES_FLX, 3);
175 add_resource("palettes/4", PALETTES_FLX, 4);
176 add_resource("palettes/5", PALETTES_FLX, 5);
177 add_resource("palettes/6", PALETTES_FLX, 6);
178 add_resource("palettes/7", PALETTES_FLX, 7);
179 add_resource("palettes/8", PALETTES_FLX, 8);
180 add_resource("palettes/9", PALETTES_FLX, 10);
181 add_resource("palettes/10", PALETTES_FLX, 11);
182 add_resource("palettes/11", PALETTES_FLX, 12);
183 add_resource("palettes/12", INTROPAL_DAT, 0);
184 add_resource("palettes/13", INTROPAL_DAT, 1);
185 add_resource("palettes/14", INTROPAL_DAT, 2);
186 add_resource("palettes/15", INTROPAL_DAT, 3);
187 add_resource("palettes/16", INTROPAL_DAT, 4);
188 add_resource("palettes/17", INTROPAL_DAT, 5);
189
190 add_resource("palettes/patch/0", PATCH_PALETTES, 0);
191 add_resource("palettes/patch/1", PATCH_PALETTES, 1);
192 add_resource("palettes/patch/2", PATCH_PALETTES, 2);
193 add_resource("palettes/patch/3", PATCH_PALETTES, 3);
194 add_resource("palettes/patch/4", PATCH_PALETTES, 4);
195 add_resource("palettes/patch/5", PATCH_PALETTES, 5);
196 add_resource("palettes/patch/6", PATCH_PALETTES, 6);
197 add_resource("palettes/patch/7", PATCH_PALETTES, 7);
198 add_resource("palettes/patch/8", PATCH_PALETTES, 8);
199 add_resource("palettes/patch/9", PATCH_PALETTES, 10);
200 add_resource("palettes/patch/10", PATCH_PALETTES, 11);
201 add_resource("palettes/patch/11", PATCH_PALETTES, 12);
202 add_resource("palettes/patch/12", PATCH_INTROPAL, 0);
203 add_resource("palettes/patch/13", PATCH_INTROPAL, 1);
204 add_resource("palettes/patch/14", PATCH_INTROPAL, 2);
205 add_resource("palettes/patch/15", PATCH_INTROPAL, 3);
206 add_resource("palettes/patch/16", PATCH_INTROPAL, 4);
207 add_resource("palettes/patch/17", PATCH_INTROPAL, 5);
208
209 add_resource("xforms/count", nullptr, 20);
210 add_resource("xforms/0", XFORMTBL, 0);
211 add_resource("xforms/1", XFORMTBL, 1);
212 add_resource("xforms/2", XFORMTBL, 2);
213 add_resource("xforms/3", XFORMTBL, 3);
214 add_resource("xforms/4", XFORMTBL, 4);
215 add_resource("xforms/5", XFORMTBL, 5);
216 add_resource("xforms/6", XFORMTBL, 6);
217 add_resource("xforms/7", XFORMTBL, 7);
218 add_resource("xforms/8", XFORMTBL, 8);
219 add_resource("xforms/9", XFORMTBL, 9);
220 add_resource("xforms/10", XFORMTBL, 10);
221 add_resource("xforms/11", XFORMTBL, 11);
222 add_resource("xforms/12", XFORMTBL, 12);
223 add_resource("xforms/13", XFORMTBL, 13);
224 add_resource("xforms/14", XFORMTBL, 14);
225 add_resource("xforms/15", XFORMTBL, 15);
226 add_resource("xforms/16", XFORMTBL, 16);
227 add_resource("xforms/17", XFORMTBL, 17);
228 add_resource("xforms/18", XFORMTBL, 18);
229 add_resource("xforms/19", XFORMTBL, 19);
230 }
231 fontManager.add_font("MENU_FONT", MAINSHP_FLX, PATCH_MAINSHP, 9, 1);
232 fontManager.add_font("END2_FONT", ENDGAME, PATCH_ENDGAME, 4, -1);
233 fontManager.add_font("END3_FONT", ENDGAME, PATCH_ENDGAME, 5, -2);
234 fontManager.add_font("NORMAL_FONT", FONTS_VGA, PATCH_FONTS, 0, -1);
235 fontManager.add_font("SMALL_BLACK_FONT", FONTS_VGA, PATCH_FONTS, 2, 0);
236 fontManager.add_font("TINY_BLACK_FONT", FONTS_VGA, PATCH_FONTS, 4, 0);
237 fontManager.add_font("GUARDIAN_FONT", MAINSHP_FLX, PATCH_MAINSHP, 3, -2);
238 auto& mp = gwin->get_map_patches();
239 // Sawdust in Iolo's hut is at lift 2, should be 0
240 // FIXME - the original had some way to deal with this
241 mp.add(std::make_unique<Map_patch_modify>(Object_spec(
242 Tile_coord(481, 599, 2), 224, 7, 0),
243 Object_spec(
244 Tile_coord(480, 598, 0), 224, 7, 0)));
245 mp.add(std::make_unique<Map_patch_modify>(Object_spec(
246 Tile_coord(482, 601, 2), 224, 7, 0),
247 Object_spec(
248 Tile_coord(481, 600, 0), 224, 7, 0)));
249 mp.add(std::make_unique<Map_patch_modify>(Object_spec(
250 Tile_coord(482, 600, 2), 224, 7, 0),
251 Object_spec(
252 Tile_coord(481, 599, 0), 224, 7, 0)));
253 mp.add(std::make_unique<Map_patch_modify>(Object_spec(
254 Tile_coord(481, 602, 2), 224, 7, 0),
255 Object_spec(
256 Tile_coord(480, 601, 0), 224, 7, 0)));
257 mp.add(std::make_unique<Map_patch_modify>(Object_spec(
258 Tile_coord(479, 599, 2), 224, 7, 0),
259 Object_spec(
260 Tile_coord(478, 598, 0), 224, 7, 0)));
261 mp.add(std::make_unique<Map_patch_modify>(Object_spec(
262 Tile_coord(477, 597, 2), 224, 7, 0),
263 Object_spec(
264 Tile_coord(476, 596, 0), 224, 7, 0)));
265 mp.add(std::make_unique<Map_patch_modify>(Object_spec(
266 Tile_coord(476, 597, 2), 224, 7, 0),
267 Object_spec(
268 Tile_coord(475, 596, 0), 224, 7, 0)));
269 mp.add(std::make_unique<Map_patch_modify>(Object_spec(
270 Tile_coord(472, 595, 2), 224, 7, 0),
271 Object_spec(
272 Tile_coord(471, 594, 0), 224, 7, 0)));
273 mp.add(std::make_unique<Map_patch_modify>(Object_spec(
274 Tile_coord(473, 598, 2), 224, 7, 0),
275 Object_spec(
276 Tile_coord(472, 597, 0), 224, 7, 0)));
277 mp.add(std::make_unique<Map_patch_modify>(Object_spec(
278 Tile_coord(472, 600, 2), 224, 7, 0),
279 Object_spec(
280 Tile_coord(471, 599, 0), 224, 7, 0)));
281 mp.add(std::make_unique<Map_patch_modify>(Object_spec(
282 Tile_coord(470, 597, 2), 224, 7, 0),
283 Object_spec(
284 Tile_coord(469, 596, 0), 224, 7, 0)));
285 mp.add(std::make_unique<Map_patch_modify>(Object_spec(
286 Tile_coord(469, 597, 2), 224, 7, 0),
287 Object_spec(
288 Tile_coord(468, 596, 0), 224, 7, 0)));
289 mp.add(std::make_unique<Map_patch_modify>(Object_spec(
290 Tile_coord(467, 599, 2), 224, 7, 0),
291 Object_spec(
292 Tile_coord(466, 598, 0), 224, 7, 0)));
293 mp.add(std::make_unique<Map_patch_modify>(Object_spec(
294 Tile_coord(468, 600, 2), 224, 7, 0),
295 Object_spec(
296 Tile_coord(467, 599, 0), 224, 7, 0)));
297 mp.add(std::make_unique<Map_patch_modify>(Object_spec(
298 Tile_coord(467, 601, 2), 224, 7, 0),
299 Object_spec(
300 Tile_coord(466, 600, 0), 224, 7, 0)));
301 // pure BG shape 874 only has an empty frame. For FoV's Test of Truth
302 // it was changed for creating a dungeon chasm while obviously forgetting
303 // that frame #0 was used in two occassions:
304 // hole in Despise's rebel base to invisible stairway
305 mp.add(std::make_unique<Map_patch_remove>(Object_spec(
306 Tile_coord(681, 1192, 5), 874, 0, 0)));
307 mp.add(std::make_unique<Map_patch_remove>(Object_spec(
308 Tile_coord(682, 1192, 5), 874, 0, 0)));
309 mp.add(std::make_unique<Map_patch_remove>(Object_spec(
310 Tile_coord(681, 1195, 5), 874, 0, 0)));
311 mp.add(std::make_unique<Map_patch_remove>(Object_spec(
312 Tile_coord(682, 1195, 5), 874, 0, 0)));
313 // stairways hole in Shame's 2nd floor
314 mp.add(std::make_unique<Map_patch_remove>(Object_spec(
315 Tile_coord(807, 951, 5), 874, 0, 0)));
316 mp.add(std::make_unique<Map_patch_remove>(Object_spec(
317 Tile_coord(811, 951, 5), 874, 0, 0)));
318 mp.add(std::make_unique<Map_patch_remove>(Object_spec(
319 Tile_coord(807, 955, 5), 874, 0, 0)));
320 mp.add(std::make_unique<Map_patch_remove>(Object_spec(
321 Tile_coord(811, 955, 5), 874, 0, 0)));
322 }
323
324 class UserSkipException : public UserBreakException {
325 };
326
327
328 #define WAITDELAY(x) switch(wait_delay(x)) { \
329 case 1: throw UserBreakException(); break; \
330 case 2: throw UserSkipException(); break; \
331 }
332
333 #define WAITDELAYCYCLE1(x) switch (wait_delay((x), 16, 78, 50)) { \
334 case 1: throw UserBreakException(); break; \
335 case 2: throw UserSkipException(); break; \
336 }
337
338 #define WAITDELAYCYCLE2(x) switch (wait_delay((x), 250, 5)) { \
339 case 1: throw UserBreakException(); break; \
340 case 2: throw UserSkipException(); break; \
341 }
342
343 #define WAITDELAYCYCLE3(x) switch (wait_delay((x), 240, 15)) { \
344 case 1: throw UserBreakException(); break; \
345 case 2: throw UserSkipException(); break; \
346 }
347
348 #define WAITDELAYCYCLE4(x) switch (wait_delay((x), 16, 78, 15)) { \
349 case 1: throw UserBreakException(); break; \
350 case 2: throw UserSkipException(); break; \
351 }
352
353 #define WAITDELAYCYCLE5(x) switch (wait_delay((x), 16, -78, 50)) { \
354 case 1: throw UserBreakException(); break; \
355 case 2: throw UserSkipException(); break; \
356 }
357
358 #define WAITDELAYCYCLE6(x) switch (wait_delay((x), 16, -78, 15)) { \
359 case 1: throw UserBreakException(); break; \
360 case 2: throw UserSkipException(); break; \
361 }
362
play_intro()363 void BG_Game::play_intro() {
364 Audio *audio = Audio::get_ptr();
365 audio->stop_music();
366 MyMidiPlayer *midi = audio->get_midi();
367 if (midi) midi->set_timbre_lib(MyMidiPlayer::TIMBRE_LIB_INTRO);
368
369 gwin->clear_screen(true);
370
371 // TODO: check/fix other resolutions
372
373 try {
374 /********************************************************************
375 Lord British Presents
376 ********************************************************************/
377
378 scene_lord_british();
379
380 /********************************************************************
381 Ultima VII logo w/Trees
382 ********************************************************************/
383
384 scene_butterfly();
385
386 /********************************************************************
387 Enter guardian
388 ********************************************************************/
389
390 scene_guardian();
391
392 /********************************************************************
393 PC screen
394 ********************************************************************/
395
396 scene_desk();
397
398 /********************************************************************
399 The Moongate
400 ********************************************************************/
401
402 scene_moongate();
403 } catch (const UserBreakException &/*x*/) {
404 }
405
406 // Fade out the palette...
407 pal->fade_out(c_fade_out_time);
408
409 // ... and clean the screen.
410 gwin->clear_screen(true);
411
412 // Stop all audio output
413 audio->cancel_streams();
414 }
415
get_sfx_subflex()416 File_spec BG_Game::get_sfx_subflex() {
417 if (Audio::have_sblaster_sfx(BLACK_GATE)) {
418 return File_spec{game->get_resource("files/gameflx").str, EXULT_BG_FLX_INTROSFX_SB_FLX};
419 }
420 // TODO: MIDI SFX
421 // if (audio->have_midi_sfx()) {
422 // return File_spec{game->get_resource("files/gameflx").str, EXULT_BG_FLX_INTROSFX_MTIDI_FLX};
423 // }
424 return File_spec{game->get_resource("files/gameflx").str, EXULT_BG_FLX_INTROSFX_MT32_FLX};
425 }
426
scene_lord_british()427 void BG_Game::scene_lord_british() {
428 /*
429 * Enhancements to lip-syncing contributed by
430 * Eric Wasylishen, Jun. 19, 2006.
431 */
432
433 try {
434 WAITDELAY(1500); // - give a little space between exult title music
435 // and LB presents screen
436
437
438 // Lord British presents... (sh. 0x11)
439 pal->load(INTROPAL_DAT, PATCH_INTROPAL, 3);
440 sman->paint_shape(topx, topy, shapes.get_shape(lord_british_shp, 0));
441
442 pal->fade_in(c_fade_in_time);
443 if (1 == wait_delay(2000))
444 throw UserBreakException();
445 pal->fade_out(c_fade_out_time);
446 } catch (const UserSkipException &/*x*/) {
447 }
448 gwin->clear_screen(true);
449 }
450
scene_butterfly()451 void BG_Game::scene_butterfly() {
452 constexpr static const int frame_duration = 23; // - used to be 16.. too fast.
453 constexpr static const int frame_count = 3;
454
455 constexpr static int butterfly_x[] = {
456 6, 18, 30, 41, 52, 62, 70, 78, 86, 95,
457 104, 113, 122, 132, 139, 146, 151, 155, 157, 158,
458 157, 155, 151, 146, 139, 132, 124, 116, 108, 102,
459 96, 93, 93, 93, 95, 99, 109, 111, 118, 125,
460 132, 140, 148, 157, 164, 171, 178, 184, 190, 196,
461 203, 211, 219, 228, 237, 246, 254, 259, 262, 264,
462 265, 265, 263, 260, 256, 251, 245, 239, 232, 226,
463 219, 212, 208, 206, 206, 209, 212, 216, 220, 224,
464 227, 234, 231, 232, 233, 233, 233, 233, 234, 236,
465 239, 243, 247, 250, 258, 265
466 };
467
468 constexpr static int butterfly_y[] = {
469 155, 153, 151, 150, 149, 148, 148, 148, 148, 149,
470 150, 150, 150, 149, 147, 142, 137, 131, 125, 118,
471 110, 103, 98, 94, 92, 91, 91, 91, 92, 95,
472 99, 104, 110, 117, 123, 127, 131, 134, 135, 135,
473 135, 135, 135, 134, 132, 129, 127, 123, 119, 115,
474 112, 109, 104, 102, 101, 102, 109, 109, 114, 119,
475 125, 131, 138, 144, 149, 152, 156, 158, 159, 159,
476 158, 155, 150, 144, 137, 130, 124, 118, 112, 105,
477 99, 93, 86, 80, 73, 66, 59, 53, 47, 42,
478 38, 35, 32, 29, 26, 25
479 };
480
481 constexpr static const int butterfly_num_coords = array_size(butterfly_x);
482
483 constexpr static const int butterfly_end_frames[] = {
484 3, 4, 3, 4, 3, 2, 1, 0
485 };
486 constexpr static const int butterfly_end_delay[] = {
487 167, 416, 250, 416, 416, 416, 416, 333
488 };
489
490 int frame;
491
492 auto DrawButterfly = [&](int x, int y, int frame, int delay, Image_buffer *backup, Shape_frame *butterfly) {
493 // Draw butterfly
494 win->get(backup, topx + x - butterfly->get_xleft(),
495 topy + y - butterfly->get_yabove());
496 sman->paint_shape(topx + x, topy + y, shapes.get_shape(butterfly_shp, frame));
497 win->show();
498 WAITDELAY(delay);
499 win->put(backup, topx + x - butterfly->get_xleft(),
500 topy + y - butterfly->get_yabove());
501 };
502
503 int dir = 0;
504 auto ButterflyFlap = [&]() {
505 if ((rand() % 5)<4) {
506 if (frame == 3) {
507 dir = -1;
508 } else if (frame == 0) {
509 dir = +1;
510 }
511 frame += dir;
512 }
513 };
514
515 try {
516 pal->load(INTROPAL_DAT, PATCH_INTROPAL, 4);
517
518 // Load the butterfly shape
519 Shape_frame *butterfly(shapes.get_shape(butterfly_shp, 0));
520 unique_ptr<Image_buffer> backup(win->create_buffer(butterfly->get_width(), butterfly->get_height()));
521
522 // Start playing the birdsongs while still faded out
523 Audio::get_ptr()->start_music(bird_song_midi, false, INTROMUS);
524
525 // trees with "Ultima VII" on top of 'em
526 sman->paint_shape(topx, topy, shapes.get_shape(trees_shp, 0));
527 sman->paint_shape(topx + 160, topy + 50, shapes.get_shape(ultima_text_shp, 0));
528
529
530 // Keep it dark for some more time, playing the music
531 WAITDELAY(4500); // - was WAITDELAY(3500);
532
533 // Finally fade in
534 pal->fade_in(c_fade_in_time);
535
536 WAITDELAY(4000);
537
538 win->show();
539
540 WAITDELAY(7100);
541
542 //
543 // Move the butterfly along its path
544 //
545 frame = 0;
546 Sint32 delay = frame_duration;
547 for (int i = 0; i < butterfly_num_coords - 1; ++i) {
548 for (int j = 0; j < frame_count; ++j) {
549
550 Sint32 ticks = SDL_GetTicks();
551 int x = butterfly_x[i] + j * (butterfly_x[i + 1] - butterfly_x[i]) / frame_count;
552 int y = butterfly_y[i] + j * (butterfly_y[i + 1] - butterfly_y[i]) / frame_count;
553 DrawButterfly(x, y, frame, delay, backup.get(), butterfly);
554
555 // Flap the wings; but not always, so that the butterfly "glides" from time to time
556 ButterflyFlap();
557
558 // Calculate the difference between the time we wanted to spent and the time
559 // we actually spent; then adjust 'delay' accordingly
560 ticks = SDL_GetTicks() - ticks;
561 delay = (delay + (2 * frame_duration - ticks)) / 2;
562
563 // ... but maybe we also have to skip frames..
564 if (delay < 0) {
565 // Calculate how many frames we should skip
566 int frames_to_skip = (-delay) / frame_duration + 1;
567 int new_index = i * frame_count + j + frames_to_skip;
568 i = new_index / frame_count;
569 j = new_index % frame_count;
570
571 // Did we skip over the end?
572 if (i >= butterfly_num_coords - 1)
573 break;
574
575 while (frames_to_skip--)
576 ButterflyFlap();
577
578 delay = 0;
579 }
580 }
581 }
582
583 // Finally, let it flutter a bit on the end spot
584 for (int i = 0; i < 8; i++) {
585 DrawButterfly(butterfly_x[butterfly_num_coords - 1],
586 butterfly_y[butterfly_num_coords - 1],
587 butterfly_end_frames[i],
588 butterfly_end_delay[i],
589 backup.get(), butterfly);
590 }
591
592 WAITDELAY(1000);
593
594 // Wait till the music finished playing
595 while (Audio::get_ptr()->is_track_playing(bird_song_midi))
596 WAITDELAY(20);
597 } catch (const UserSkipException &/*x*/) {
598 }
599 }
600
601 #define FLASH_SHAPE1(x,y,shape,frame,delay) do { \
602 sman->paint_shape((x),(y),shapes.get_shape((shape),(frame))); \
603 win->show(); \
604 WAITDELAYCYCLE1((delay)); \
605 win->put(backup.get(),(x)-s->get_xleft(),(y)-s->get_yabove()); \
606 } while(0)
607
608 #define FLASH_SHAPE2(x,y,shape,frame,delay) do { \
609 sman->paint_shape((x),(y),shapes.get_shape((shape),(frame))); \
610 win->show(); \
611 WAITDELAYCYCLE4((delay)); \
612 win->put(backup.get(),(x)-s->get_xleft(),(y)-s->get_yabove()); \
613 } while(0)
614
615
616 class LipSynchReader {
617 std::unique_ptr<IBufferDataView> data;
618
619 public:
LipSynchReader()620 LipSynchReader()
621 : data(std::make_unique<IExultDataSource>(MAINSHP_FLX, PATCH_MAINSHP, 0x0F)) {}
LipSynchReader(char const * pp,int len)622 LipSynchReader(char const *pp, int len)
623 : data(std::make_unique<IBufferDataView>(pp, len)) {}
have_events() const624 bool have_events() const {
625 return data->getAvail() > 0;
626 }
get_event(int & time,int & code)627 void get_event(int &time, int &code) {
628 if (have_events()) {
629 int curr_time = data->read2();
630 time = curr_time * 1000.0 / 60.0;
631 code = data->read1();
632 } else {
633 time = 0;
634 code = 0;
635 }
636 }
translate_code(int code,int & mouth,int & eyes,int & lasteyes)637 void translate_code(int code, int &mouth, int &eyes, int &lasteyes) {
638 // Lookup table for translating lipsynch data. The table is indexed by the
639 // current frame-1, then by read lipsynch data-1.
640 constexpr static const int eye_frame_LUT[6][9] = {
641 // 1, 2, 3, 4, 5, 6, 7, 8, 9 // Last eye frame (or the one before, if it was 10)
642 {1, 2, 3, 1, 2, 3, 1, 2, 3}, // 1: Change eyebrows to angry
643 {4, 5, 6, 4, 5, 6, 4, 5, 6}, // 2: Change eyebrows to neutral
644 {7, 8, 9, 7, 8, 9, 7, 8, 9}, // 3: Change eyebrows to raised
645 {1, 1, 1, 4, 4, 4, 7, 7, 7}, // 4: Change eyes to fully open
646 {2, 2, 2, 5, 5, 5, 8, 8, 8}, // 5: Change eyes to half-open
647 {3, 3, 3, 6, 6, 6, 9, 9, 9}, // 6: Change eyes to fully closed
648 };
649 // Set based on code read
650 if (code >= 8 && code < 23) {
651 // This changes mouth frame
652 mouth = code - 8;
653 } else if (code == 7) {
654 // This sets eyes to looking down
655 // Don't update last eye frame
656 eyes = 10;
657 } else if (code > 0 && code < 7) {
658 // This changes eyes and eyebrows
659 eyes = eye_frame_LUT[code-1][lasteyes-1];
660 lasteyes = eyes;
661 }
662 }
663 };
664
665 // Simple RAII wrapper for SDL_FreeSurface
666 class SDL_SurfaceOwner {
667 SDL_Surface* surf;
668 public:
SDL_SurfaceOwner(Image_buffer * src,SDL_Surface * draw)669 SDL_SurfaceOwner(Image_buffer *src, SDL_Surface *draw)
670 : surf(SDL_CreateRGBSurfaceFrom(src->get_bits(),
671 src->get_height(), src->get_width(),
672 draw->format->BitsPerPixel,
673 src->get_line_width(),
674 draw->format->Rmask,
675 draw->format->Gmask,
676 draw->format->Bmask,
677 draw->format->Amask)) {}
~SDL_SurfaceOwner()678 ~SDL_SurfaceOwner() noexcept {
679 SDL_FreeSurface(surf);
680 }
get()681 SDL_Surface* get() noexcept {
682 return surf;
683 }
684 };
685
686 // Simple RAII wrapper for managing window clip
687 class WinClip {
688 Image_window8 *window;
689 public:
WinClip(Image_window8 * win,int cx,int cy,int wid,int hgt)690 WinClip(Image_window8 *win, int cx, int cy, int wid, int hgt)
691 : window(win) {
692 window->set_clip(cx, cy, wid, hgt);
693 }
~WinClip()694 ~WinClip() noexcept {
695 window->clear_clip();
696 }
697 };
698
699 // Simple RAII wrapper for stopping speech
700 struct SpeechManager {
SpeechManagerSpeechManager701 SpeechManager(const char *fname, const char *fpatch, bool wait) {
702 if (Audio::get_ptr()->is_audio_enabled() &&
703 Audio::get_ptr()->is_speech_enabled()) {
704 Audio::get_ptr()->playfile(fname, fpatch, wait);
705 }
706 }
~SpeechManagerSpeechManager707 ~SpeechManager() noexcept {
708 Audio::get_ptr()->stop_speech();
709 }
710 };
711
scene_guardian()712 void BG_Game::scene_guardian() {
713 constexpr static const int Eyes_Dist = 12;
714 constexpr static const int Forehead_Dist = 49;
715
716 constexpr static const int text_times[] = {
717 250, 1700, 5250, 8200, 10550, 12900, 16000, 19950, 22700, 27200, 32400,
718 36400, 39650, 42400
719 };
720
721 constexpr static const int text_num_frames = array_size(text_times);
722
723 constexpr static const char surfacing_data[] = {
724 0x03, 0x00, 0x02, // Eyebrows -> neutral
725 0x03, 0x00, 0x05, // Eyes -> half-open
726 0x15, 0x00, 0x04, // Eyes -> fully open
727 0x33, 0x00, 0x07, // Eyes -> Look down
728 0x54, 0x00, 0x04, // Eyes -> fully open
729 0x77, 0x00, 0x00
730 };
731
732 try {
733 Uint32 ticks;
734 const File_spec sfxfile = get_sfx_subflex();
735 {
736 // create buffer containing a blue 'plasma' screen
737 unique_ptr<Image_buffer> plasma(win->create_buffer(win->get_full_width(),
738 win->get_full_height()));
739 gwin->plasma(win->get_full_width(), win->get_full_height(), win->get_start_x(), win->get_start_y(), 16, 16 + 76);
740 win->get(plasma.get(), win->get_start_x(), win->get_start_y());
741
742 pal->load(INTROPAL_DAT, PATCH_INTROPAL, 2);
743 pal->set_color(1, 0, 0, 0); //UGLY hack... set font background to black
744 pal->apply();
745
746 //play static SFX
747 Audio::get_ptr()->play_sound_effect(sfxfile, INTROSFX_MT32_FLX_INTRO_MT_STATIC1_WAV);
748
749 //
750 // Show some "static" alternating with the blue plasma
751 //
752 // TODO: Is this the right kind of static? Dosbox shows a mostly black
753 // with an ocassional white pixel static - but is this what it
754 // was really like?
755
756 ticks = SDL_GetTicks();
757 while (true) {
758 win->get_ibuf()->fill_static(0, 7, 15);
759 win->show();
760 WAITDELAYCYCLE1(2);
761 if (SDL_GetTicks() > ticks + 400)//400)
762 break;
763 }
764
765 Audio::get_ptr()->play_sound_effect(sfxfile, INTROSFX_MT32_FLX_INTRO_MT_STATIC2_WAV);
766
767 win->put(plasma.get(), win->get_start_x(), win->get_start_y());
768 win->show();
769 WAITDELAYCYCLE1(200);
770
771 ticks = SDL_GetTicks();
772 while (true) {
773 win->get_ibuf()->fill_static(0, 7, 15);
774 win->show();
775 WAITDELAYCYCLE1(2);
776 if (SDL_GetTicks() > ticks + 200)
777 break;
778 }
779
780 Audio::get_ptr()->play_sound_effect(sfxfile, INTROSFX_MT32_FLX_INTRO_MT_STATIC3_WAV);
781
782 win->put(plasma.get(), win->get_start_x(), win->get_start_y());
783 win->show();
784 WAITDELAYCYCLE1(200);
785
786 ticks = SDL_GetTicks();
787 while (true) {
788 win->get_ibuf()->fill_static(0, 7, 15);
789 win->show();
790 WAITDELAYCYCLE1(2);
791 if (SDL_GetTicks() > ticks + 100)
792 break;
793 }
794
795 win->put(plasma.get(), win->get_start_x(), win->get_start_y());
796 win->show();
797 }
798
799 //
800 // Start background music
801 //
802 Audio::get_ptr()->start_music(guardian_midi, false, INTROMUS);
803
804 WAITDELAYCYCLE1(3800);
805
806 //
807 // First 'popup' (sh. 0x21)
808 //
809
810 Audio::get_ptr()->play_sound_effect(sfxfile, INTROSFX_MT32_FLX_INTRO_MT_GUARDIAN1_WAV);
811
812 {
813 Shape_frame *s = shapes.get_shape(0x21, 0);
814 unique_ptr<Image_buffer> backup(win->create_buffer(s->get_width(), s->get_height()));
815 win->get(backup.get(), centerx - 53 - s->get_xleft(), centery - 68 - s->get_yabove());
816 for (int i = 8; i >= -8; i--)
817 FLASH_SHAPE2(centerx - 53, centery - 68, 0x21, 1 + abs(i), 80);
818 }
819 WAITDELAYCYCLE1(2000);
820
821 //
822 // Second 'popup' (sh. 0x22)
823 //
824 Audio::get_ptr()->play_sound_effect(sfxfile, INTROSFX_MT32_FLX_INTRO_MT_GUARDIAN2_WAV);
825
826 {
827 Shape_frame *s = shapes.get_shape(0x22, 0);
828 unique_ptr<Image_buffer> backup(win->create_buffer(s->get_width(), s->get_height()));
829 win->get(backup.get(), centerx - s->get_xleft(), centery - 45 - s->get_yabove());
830 for (int i = 9; i >= -9; i--)
831 FLASH_SHAPE2(centerx, centery - 45, 0x22, 9 - abs(i), 80);
832 }
833 WAITDELAYCYCLE1(2000);
834
835 //
836 // Successful 'popup' (sh. 0x23)
837 //
838 Audio::get_ptr()->play_sound_effect(sfxfile, INTROSFX_MT32_FLX_INTRO_MT_GUARDIAN3_WAV);
839
840 {
841 Shape_frame *s = shapes.get_shape(0x23, 0);
842 unique_ptr<Image_buffer> backup(win->create_buffer(s->get_width(), s->get_height()));
843 unique_ptr<Image_buffer> cbackup(win->create_buffer(s->get_width(), s->get_height()));
844
845 win->get(cbackup.get(), centerx - s->get_xleft(), centery - s->get_yabove());
846 sman->paint_shape(centerx, centery, s); // frame 0 is static background
847 win->get(backup.get(), centerx - s->get_xleft(), centery - s->get_yabove());
848 for (int i = 1; i < 16; i++)
849 FLASH_SHAPE2(centerx, centery, 0x23, i, 70);
850
851 sman->paint_shape(centerx, centery, shapes.get_shape(0x23, 15));
852 win->show();
853
854 WAITDELAYCYCLE1(500); // - show his face for half a second
855 // before he opens his eyes.
856
857 win->put(cbackup.get(), centerx - s->get_xleft(), centery - s->get_yabove());
858 }
859 //
860 // Actual appearance
861 //
862
863 // mouth
864 {
865 Shape_frame *s = shapes.get_shape(guardian_mouth_shp, 0);
866 unique_ptr<Image_buffer> backup(win->create_buffer(s->get_width(), s->get_height()));
867 unique_ptr<Image_buffer> cbackup(win->create_buffer(s->get_width(), s->get_height()));
868 win->get(cbackup.get(), centerx - s->get_xleft(), centery - s->get_yabove());
869 sman->paint_shape(centerx, centery, s); // frame 0 is background
870 win->get(backup.get(), centerx - s->get_xleft(), centery - s->get_yabove());
871 // eyes
872 Shape_frame *s2 = shapes.get_shape(guardian_eyes_shp, 0);
873 unique_ptr<Image_buffer> backup2(win->create_buffer(s2->get_width(), s2->get_height()));
874 unique_ptr<Image_buffer> cbackup2(win->create_buffer(s2->get_width(), s2->get_height()));
875 win->get(cbackup2.get(), centerx - s2->get_xleft(),
876 centery - Eyes_Dist - s2->get_yabove());
877 sman->paint_shape(centerx, centery - Eyes_Dist, s2); // frame 0 is background
878 win->get(backup2.get(), centerx - s2->get_xleft(),
879 centery - Eyes_Dist - s2->get_yabove());
880 // forehead
881 Shape_frame *s3 = shapes.get_shape(guardian_forehead_shp, 0);
882 unique_ptr<Image_buffer> cbackup3(win->create_buffer(s3->get_width(), s3->get_height()));
883 win->get(cbackup3.get(), centerx - s3->get_xleft(),
884 centery - Forehead_Dist - s3->get_yabove());
885 sman->paint_shape(centerx, centery - Forehead_Dist, s3); // forehead isn't animated
886
887 // prepare Guardian speech
888 {
889 Font *font = fontManager.get_font("GUARDIAN_FONT");
890 U7multiobject textobj(MAINSHP_FLX, PATCH_MAINSHP, 0x0D);
891 size_t txt_len;
892 auto txt = textobj.retrieve(txt_len);
893 char *txt_ptr;
894 char *txt_end;
895 char *next_txt;
896 next_txt = txt_ptr = reinterpret_cast<char*>(txt.get());
897
898 int txt_height = font->get_text_height();
899 int txt_ypos = gwin->get_height() - txt_height - 16;
900
901 // backup text area
902 unique_ptr<Image_buffer> backup3(win->create_buffer(win->get_full_width(), txt_height));
903 win->get(backup3.get(), win->get_start_x(), txt_ypos);
904
905 // Lipsynching
906 int eye_frame = 3;
907 int last_eye_frame = 3;
908 int mouth_frame = 1;
909 int text_index = -1;
910 int next_time;
911 int next_code;
912 LipSynchReader lipsync;
913 LipSynchReader surfacing(surfacing_data, sizeof(surfacing_data));
914
915 int time = 0;
916 unsigned long start = SDL_GetTicks();
917
918 bool speech = Audio::get_ptr()->is_audio_enabled() &&
919 Audio::get_ptr()->is_speech_enabled();
920 bool want_subs = !speech || Audio::get_ptr()->is_speech_with_subs();
921
922 auto AdvanceTextPointer = [&]() {
923 txt_ptr = next_txt;
924 txt_end = strchr(txt_ptr, '\r');
925 *txt_end = '\0';
926 next_txt = txt_end+2;
927 };
928 auto EraseAndDraw = [&](Image_buffer *backup, Shape_frame *fra, int shnum, int frnum, int dist) {
929 win->put(backup, centerx - fra->get_xleft(),
930 centery - dist - fra->get_yabove());
931 sman->paint_shape(centerx, centery - dist,
932 shapes.get_shape(shnum, frnum));
933 };
934 auto DrawSpeech = [&]() {
935 // Erase text
936 win->put(backup3.get(), win->get_start_x(), txt_ypos);
937 // Erase and redraw eyes
938 EraseAndDraw(backup2.get(), s2, guardian_eyes_shp, eye_frame, Eyes_Dist);
939 // Erase and redraw mouth
940 EraseAndDraw(backup.get(), s, guardian_mouth_shp, mouth_frame, 0);
941 if (text_index > 0 && text_index < text_num_frames) {
942 // Draw text
943 font->center_text(win->get_ib8(), centerx, txt_ypos, txt_ptr);
944 }
945 };
946
947 // First event needs to be read here
948 surfacing.get_event(next_time, next_code);
949 DrawSpeech();
950 // before speech
951 while (time < 2000) {
952 if (surfacing.have_events() && time >= next_time) {
953 surfacing.translate_code(next_code, mouth_frame, eye_frame, last_eye_frame);
954 surfacing.get_event(next_time, next_code);
955 DrawSpeech();
956 }
957
958 win->show();
959 WAITDELAYCYCLE5(15);
960 win->show();
961 time = (SDL_GetTicks() - start);
962 }
963
964
965 SpeechManager mngr(INTROSND, PATCH_INTROSND, false);
966 time = 0;
967 start = SDL_GetTicks();
968
969 // First event needs to be read here
970 lipsync.get_event(next_time, next_code);
971 if (want_subs) {
972 text_index = 0;
973 } else {
974 text_index = text_num_frames; // Disable subtitles
975 }
976 // start speech
977 while (time < 47537) {
978 bool need_redraw = false;
979 if (next_code && lipsync.have_events() && time >= next_time) {
980 lipsync.translate_code(next_code, mouth_frame, eye_frame, last_eye_frame);
981 lipsync.get_event(next_time, next_code);
982 need_redraw = true;
983 }
984
985 if (text_index < text_num_frames && time >= text_times[text_index]) {
986 text_index++;
987 AdvanceTextPointer();
988 need_redraw = true;
989 }
990 if (need_redraw) {
991 DrawSpeech();
992 }
993 win->show();
994 WAITDELAYCYCLE6(15);
995 win->show();
996 time = (SDL_GetTicks() - start);
997 }
998
999 win->show();
1000 WAITDELAYCYCLE6(1000);
1001 win->show();
1002
1003 win->put(backup3.get(), 0, txt_ypos);
1004 win->put(cbackup.get(), centerx - s->get_xleft(), centery - s->get_yabove());
1005 win->put(cbackup2.get(), centerx - s2->get_xleft(),
1006 centery - Eyes_Dist - s2->get_yabove());
1007 win->put(cbackup3.get(), centerx - s3->get_xleft(),
1008 centery - Forehead_Dist - s3->get_yabove());
1009 }
1010
1011 // G. disappears again (sp. 0x23 again)
1012 Audio::get_ptr()->play_sound_effect(sfxfile, INTROSFX_MT32_FLX_INTRO_MT_GUARDIAN4_WAV);
1013
1014 {
1015 Shape_frame *s = shapes.get_shape(0x23, 0);
1016 unique_ptr<Image_buffer> backup(win->create_buffer(s->get_width(), s->get_height()));
1017 unique_ptr<Image_buffer> cbackup(win->create_buffer(s->get_width(), s->get_height()));
1018 win->get(cbackup.get(), centerx - s->get_xleft(), centery - s->get_yabove());
1019 sman->paint_shape(centerx, centery, s); // frame 0 is background
1020 win->get(backup.get(), centerx - s->get_xleft(), centery - s->get_yabove());
1021 for (int i = 15; i > 0; i--)
1022 FLASH_SHAPE2(centerx, centery, 0x23, i, 70);
1023 win->put(cbackup.get(), centerx - s->get_xleft(), centery - s->get_yabove());
1024 }
1025
1026 win->show();
1027 }
1028 WAITDELAYCYCLE1(1200);
1029
1030 Audio::get_ptr()->stop_music();
1031
1032 //
1033 // More static
1034 //
1035 Audio::get_ptr()->play_sound_effect(sfxfile, INTROSFX_MT32_FLX_INTRO_MT_OUTSTATIC_WAV);
1036
1037 ticks = SDL_GetTicks();
1038 while (true) {
1039 win->get_ibuf()->fill_static(0, 7, 15);
1040 win->show();
1041 WAITDELAYCYCLE1(2);
1042 if (SDL_GetTicks() > ticks + 400)
1043 break;
1044 }
1045
1046 gwin->clear_screen(true);
1047
1048 //
1049 // White dot
1050 //
1051 Audio::get_ptr()->play_sound_effect(sfxfile, INTROSFX_MT32_FLX_INTRO_MT_OUTNOISE_WAV);
1052
1053 Shape_frame *s = shapes.get_shape(0x14, 0);
1054 unique_ptr<Image_buffer> backup(win->create_buffer(s->get_width() + 2, s->get_height() + 2));
1055 win->get(backup.get(), centerx - 1, centery - 1);
1056
1057 ticks = SDL_GetTicks();
1058 while (true) {
1059 int x = centerx + rand() % 3 - 1;
1060 int y = centery + rand() % 3 - 1;
1061 FLASH_SHAPE1(x, y, 0x14, 0, 0);
1062 WAITDELAYCYCLE1(2);
1063 if (SDL_GetTicks() - ticks > 800)
1064 break;
1065 }
1066 } catch (const UserSkipException &/*x*/) {
1067 }
1068 }
1069
1070 namespace {//anonymous
1071 class Hand_Handler {
1072 private:
1073 enum HandlerScriptOps {
1074 eNOP, // Does nothing except redraw static if needed
1075 eHAND_HIT, // Move hand to frame 3, and redraw static if needed
1076 eHAND_RECOIL, // Decrement hand frame, and redraw static if needed
1077 eBLACK_SCREEN, // Draw black screen
1078 eSHOW_STATIC, // Draw static
1079 eFLASH_FAKE_TITLE, // Draw fake title screen for 1 frame, then revert to black screen
1080 eSHOW_FAKE_TITLE, // Draw fake title screen permanently
1081 };
1082
1083 static constexpr const HandlerScriptOps HandlerScript[] = {
1084 eHAND_HIT ,
1085 eBLACK_SCREEN ,
1086 eSHOW_FAKE_TITLE ,
1087 eSHOW_STATIC , eHAND_RECOIL ,
1088 eNOP , eHAND_RECOIL ,
1089 eNOP , eHAND_RECOIL ,
1090 eBLACK_SCREEN ,
1091 eHAND_HIT ,
1092 eNOP ,
1093 eSHOW_FAKE_TITLE ,
1094 eSHOW_STATIC , eHAND_RECOIL ,
1095 eNOP , eHAND_RECOIL ,
1096 eNOP , eHAND_RECOIL ,
1097 eBLACK_SCREEN ,
1098 eHAND_HIT ,
1099 eNOP ,
1100 eFLASH_FAKE_TITLE, eHAND_RECOIL ,
1101 eFLASH_FAKE_TITLE, eHAND_RECOIL ,
1102 eFLASH_FAKE_TITLE, eHAND_RECOIL ,
1103 eSHOW_FAKE_TITLE
1104 };
1105
1106 Image_window8 *win;
1107 Shape_manager *sman;
1108 std::unique_ptr<Shape_file> handshp;
1109 std::unique_ptr<Image_buffer> handBackup;
1110 std::unique_ptr<Image_buffer> staticScreen;
1111 Shape_frame *screenShape;
1112 Shape_frame *handFrame;
1113 size_t scriptPosition;
1114 int centerx, centery;
1115 int handFrNum;
1116 HandlerScriptOps currBackground;
1117 bool playedStaticSFX;
1118
1119 public:
Hand_Handler(BG_Game * game,Vga_file & shapes,Image_window8 * _win,Shape_manager * _sman,int _centerx,int _centery)1120 Hand_Handler(BG_Game *game, Vga_file& shapes,
1121 Image_window8 *_win, Shape_manager *_sman,
1122 int _centerx, int _centery)
1123 : win(_win), sman(_sman), staticScreen(win->create_buffer(160, 99)),
1124 screenShape(shapes.get_shape(0x1D, 0)), handFrame(nullptr),
1125 scriptPosition(0), centerx(_centerx), centery(_centery), handFrNum(-1),
1126 currBackground(eBLACK_SCREEN), playedStaticSFX(false) {
1127 const str_int_pair &resource = game->get_resource("files/intro_hand");
1128 U7object shpobj(resource.str, resource.num);
1129 std::size_t len;
1130 auto handBuffer = shpobj.retrieve(len);
1131 IBufferDataSource ds(std::move(handBuffer), len);
1132 handshp = std::make_unique<Shape_file>(&ds);
1133 }
1134 // Returns true to keep going.
1135 bool draw_frame();
1136
backup_shape_bkgnd(Shape_frame * fra,std::unique_ptr<Image_buffer> & backup)1137 void backup_shape_bkgnd(Shape_frame *fra, std::unique_ptr<Image_buffer>& backup) {
1138 backup = win->create_buffer(fra->get_width(), fra->get_height());
1139
1140 win->get(backup.get(), centerx - 156 - fra->get_xleft(),
1141 centery + 78 - fra->get_yabove());
1142 }
1143
restore_shape_bkgnd(Shape_frame * fra,std::unique_ptr<Image_buffer> & backup)1144 void restore_shape_bkgnd(Shape_frame *fra, std::unique_ptr<Image_buffer>& backup) {
1145 win->put(backup.get(), centerx - 156 - fra->get_xleft(),
1146 centery + 78 - fra->get_yabove());
1147 backup.reset();
1148 }
1149 };
1150
1151 constexpr const Hand_Handler::HandlerScriptOps Hand_Handler::HandlerScript[];
1152
draw_frame()1153 bool Hand_Handler::draw_frame() {
1154 if (scriptPosition >= array_size(HandlerScript)) {
1155 return false;
1156 }
1157 HandlerScriptOps currOp = HandlerScript[scriptPosition];
1158 bool drawHand = false;
1159 // Do hand first
1160 switch (currOp) {
1161 case eHAND_HIT:
1162 // TODO: SFX for hand hitting monitor
1163 drawHand = true;
1164 handFrNum = 3;
1165 break;
1166 case eHAND_RECOIL:
1167 if (handFrNum > 0) {
1168 handFrNum--;
1169 drawHand = true;
1170 }
1171 break;
1172 default:
1173 break;
1174 };
1175 if (drawHand) {
1176 if (handFrame && handBackup) {
1177 restore_shape_bkgnd(handFrame, handBackup);
1178 }
1179 handFrame = handshp->get_frame(handFrNum);
1180 if (handFrame) {
1181 backup_shape_bkgnd(handFrame, handBackup);
1182 sman->paint_shape(centerx - 167, centery + 78, handFrame);
1183 }
1184 }
1185 // Lets now handle backgrounds.
1186 switch (currOp) {
1187 case eHAND_HIT:
1188 case eHAND_RECOIL:
1189 // Special case for hand opcodes: redraw static
1190 // if it is the current background.
1191 if (currBackground != eSHOW_STATIC) {
1192 break;
1193 }
1194 // FALL THROUGH
1195 case eSHOW_STATIC:
1196 case eNOP:
1197 if (currOp == eSHOW_STATIC) {
1198 currBackground = currOp;
1199 if (!playedStaticSFX) {
1200 playedStaticSFX = true;
1201 //play static SFX
1202 const File_spec sfxfile = BG_Game::get_sfx_subflex();
1203 Audio::get_ptr()->play_sound_effect(sfxfile, INTROSFX_MT32_FLX_INTRO_MT_MONITORSLAP_WAV);
1204 }
1205 }
1206 staticScreen->fill_static(0, 7, 15);
1207 win->put(staticScreen.get(),
1208 centerx + 12 - screenShape->get_width()/2,
1209 centery - 22 - screenShape->get_height()/2);
1210 win->show();
1211 drawHand = false;
1212 break;
1213 case eFLASH_FAKE_TITLE:
1214 case eSHOW_FAKE_TITLE:
1215 sman->paint_shape(centerx + 12, centery - 22, screenShape);
1216 win->show();
1217 drawHand = false;
1218 if (currOp == eSHOW_FAKE_TITLE) {
1219 currBackground = currOp;
1220 break;
1221 }
1222 // FALL THROUGH
1223 case eBLACK_SCREEN:
1224 win->fill8(0, screenShape->get_width(), screenShape->get_height(),
1225 centerx + 12 - screenShape->get_width()/2,
1226 centery - 22 - screenShape->get_height()/2);
1227 if (currOp == eBLACK_SCREEN) {
1228 currBackground = currOp;
1229 win->show();
1230 drawHand = false;
1231 }
1232 break;
1233 default: // Just in case
1234 return false;
1235 };
1236
1237 if (drawHand) {
1238 win->show();
1239 }
1240
1241 scriptPosition++;
1242 return scriptPosition < array_size(HandlerScript);
1243 }
1244 }// End of anonymous namespace
1245
scene_desk()1246 void BG_Game::scene_desk() {
1247 try {
1248 Audio::get_ptr()->start_music(home_song_midi, false, INTROMUS);
1249
1250 gwin->clear_screen();
1251 // Clip it to 320x200 region
1252 WinClip clip(win, centerx - 160, centery - 100, 320, 200);
1253
1254 pal->load(INTROPAL_DAT, PATCH_INTROPAL, 1);
1255 pal->apply();
1256
1257 // draw monitor (sh. 0x07, 0x08, 0x09, 0x0A: various parts of monitor)
1258 sman->paint_shape(centerx, centery, shapes.get_shape(0x07, 0));
1259 sman->paint_shape(centerx, centery, shapes.get_shape(0x09, 0));
1260 sman->paint_shape(centerx, centery, shapes.get_shape(0x08, 0));
1261 sman->paint_shape(centerx, centery, shapes.get_shape(0x0A, 0));
1262
1263 // Zoom out from zoomed in screen
1264 {
1265 unique_ptr<Image_buffer> unzoomed(win->create_buffer(320, 200));
1266 win->get(unzoomed.get(), 0 + (win->get_game_width() - 320) / 2, 0 + (win->get_game_height() - 200) / 2);
1267 unique_ptr<Image_buffer> zoomed(win->create_buffer(320, 200));
1268
1269 const Image_window::ScalerInfo &scaler = Image_window::Scalers[Image_window::point];
1270
1271 SDL_Surface *draw_surface = win->get_draw_surface();
1272 SDL_SurfaceOwner unzoomed_surf(unzoomed.get(), draw_surface);
1273 SDL_SurfaceOwner zoomed_surf(zoomed.get(), draw_surface);
1274
1275 Shape_frame *dotshp = shapes.get_shape(0x14, 0);
1276 unique_ptr<Image_buffer> dot(win->create_buffer(dotshp->get_width(), dotshp->get_height()));
1277 {
1278 unique_ptr<Image_buffer> backup(win->create_buffer(dot->get_width(), dot->get_height()));
1279 win->get(backup.get(), centerx + 12, centery - 22);
1280 sman->paint_shape(centerx + 12, centery - 22, dotshp);
1281 win->get(dot.get(), centerx + 12, centery - 22);
1282 win->put(backup.get(), centerx + 12, centery - 22);
1283 }
1284 unique_ptr<Image_buffer> backup(win->create_buffer(dot->get_width() + 2, dot->get_height() + 2));
1285 unzoomed->get(backup.get(), centerx + 12, centery - 22);
1286
1287 const int zx = 88;
1288 const int zy = 22;
1289 const int zw = 166;
1290 const int zh = 112;
1291
1292 uint32 next_ticks = SDL_GetTicks() + 10;
1293 for (int i = 0; i < 40; i++) {
1294 int sw = zw + (320 - zw) * i / 40;
1295 int sh = zh + (200 - zh) * i / 40;
1296 int sx = zx + (0 - zx) * i / 40;
1297 int sy = zy + (0 - zy) * i / 40;
1298
1299 // frame drop?
1300 if (next_ticks > SDL_GetTicks()) {
1301 unzoomed->put(backup.get(), centerx + 12, centery - 22);
1302 unzoomed->put(dot.get(), centerx + rand() % 3 - 1 + 12, centery + rand() % 3 - 1 - 22);
1303 scaler.arb->Scale(unzoomed_surf.get(), sx, sy, sw, sh, zoomed_surf.get(), 0, 0, 320, 200, true);
1304 win->put(zoomed.get(), 0 + (win->get_game_width() - 320) / 2, 0 + (win->get_game_height() - 200) / 2);
1305 win->show();
1306 int delta = next_ticks - SDL_GetTicks();
1307 if (delta < 0) delta = 0;
1308 WAITDELAY(delta);
1309 }
1310 next_ticks += 10;
1311 }
1312
1313 win->put(unzoomed.get(), 0 + (win->get_game_width() - 320) / 2, 0 + (win->get_game_height() - 200) / 2);
1314 win->show();
1315 }
1316
1317 {
1318 // draw arm hitting pc
1319 Hand_Handler hand(this, shapes, win, sman, centerx, centery);
1320 while (hand.draw_frame()) {
1321 WAITDELAY(60);
1322 }
1323
1324 WAITDELAY(1300);
1325 }
1326
1327 // "Something is obviously amiss"
1328 sman->paint_shape(centerx, centery + 50, shapes.get_shape(0x15, 0));
1329 win->show();
1330 WAITDELAY(3000);
1331
1332 // TODO: misaligned?
1333
1334 // scroll right (sh. 0x06: map to the right of the monitor)
1335 for (int i = 0; i <= 194; i += 2) { //was += 4
1336 sman->paint_shape(centerx - i, centery, shapes.get_shape(0x07, 0));
1337 sman->paint_shape(centerx - i, centery, shapes.get_shape(0x09, 0));
1338 sman->paint_shape(centerx - i, centery, shapes.get_shape(0x08, 0));
1339 sman->paint_shape(centerx - i, centery, shapes.get_shape(0x0A, 0));
1340 sman->paint_shape(centerx - i + 12, centery - 22,
1341 shapes.get_shape(0x1D, 0));
1342 sman->paint_shape(topx + 320 - i, topy, shapes.get_shape(0x06, 0));
1343
1344 if (i > 75 && i < 194) {
1345 // "It has been a long time..."
1346 sman->paint_shape(centerx, centery + 50,
1347 shapes.get_shape(0x16, 0));
1348 }
1349 win->show();
1350 WAITDELAY(110); //was 30
1351 }
1352
1353 WAITDELAY(1500);
1354 // scroll down (sh. 0x0B: mouse + orb of moons, below map)
1355 for (int i = 0; i <= 50; i += 2) {
1356 sman->paint_shape(centerx - 194, centery - i,
1357 shapes.get_shape(0x07, 0));
1358 sman->paint_shape(centerx - 194, centery - i,
1359 shapes.get_shape(0x09, 0));
1360 sman->paint_shape(centerx - 194, centery - i,
1361 shapes.get_shape(0x08, 0));
1362 sman->paint_shape(centerx - 194, centery - i,
1363 shapes.get_shape(0x0A, 0));
1364 sman->paint_shape(centerx - 194 + 12, centery - 22 - i,
1365 shapes.get_shape(0x1D, 0));
1366 sman->paint_shape(topx + 319 - 194, topy - i,
1367 shapes.get_shape(0x06, 0));
1368 sman->paint_shape(topx + 319, topy + 199 - i,
1369 shapes.get_shape(0x0B, 0));
1370 // "The mystical Orb beckons you"
1371 sman->paint_shape(centerx, topy, shapes.get_shape(0x17, 0));
1372 win->show();
1373 WAITDELAYCYCLE2(110);
1374 }
1375
1376 sman->paint_shape(centerx - 194, centery - 50, shapes.get_shape(0x07, 0));
1377 sman->paint_shape(centerx - 194, centery - 50, shapes.get_shape(0x09, 0));
1378 sman->paint_shape(centerx - 194, centery - 50, shapes.get_shape(0x08, 0));
1379 sman->paint_shape(centerx - 194, centery - 50, shapes.get_shape(0x0A, 0));
1380 sman->paint_shape(centerx - 182, centery - 72, shapes.get_shape(0x1D, 0));
1381 sman->paint_shape(topx + 319 - 194, topy - 50, shapes.get_shape(0x06, 0));
1382 sman->paint_shape(topx + 319, topy + 149, shapes.get_shape(0x0B, 0));
1383 // "It has opened gateways to Britannia in the past"
1384 sman->paint_shape(centerx, topy, shapes.get_shape(0x18, 0));
1385 win->show();
1386
1387 WAITDELAYCYCLE2(3200);
1388 pal->fade_out(100);
1389 gwin->clear_screen(true);
1390 } catch (const UserSkipException &/*x*/) {
1391 }
1392 }
1393
scene_moongate()1394 void BG_Game::scene_moongate() {
1395 // sh. 0x02, 0x03, 0x04, 0x05: various parts of moongate
1396 // sh. 0x00, 0x01: parting trees before clearing
1397 gwin->clear_screen(false);
1398 pal->load(INTROPAL_DAT, PATCH_INTROPAL, 5);
1399 pal->apply();
1400
1401 // "Behind your house is the circle of stones"
1402 sman->paint_shape(centerx, centery + 50, shapes.get_shape(0x19, 0));
1403 pal->fade_in(c_fade_in_time);
1404
1405 const File_spec sfxfile = get_sfx_subflex();
1406 Audio::get_ptr()->play_sound_effect(sfxfile, INTROSFX_MT32_FLX_INTRO_MT_MOONGATE_WAV);
1407
1408 // TODO: fade in screen while text is onscreen
1409
1410 WAITDELAY(4000);
1411
1412 // Bushes move out of the way
1413 for (int i = 50; i >= -170; i -= 2) { // was for(i=120;i>=-170;i-=6)
1414 sman->paint_shape(centerx + 1, centery + 1,
1415 shapes.get_shape(0x02, 0));
1416 sman->paint_shape(centerx + 1, centery + 1,
1417 shapes.get_shape(0x03, 0));
1418 sman->paint_shape(centerx + 1, centery + 1,
1419 shapes.get_shape(0x04, 0));
1420 sman->paint_shape(centerx + 1, centery + 1,
1421 shapes.get_shape(0x05, 0));
1422
1423 // TODO: if you watch the original closely, the bushes are scaled up in size
1424 // slightly as they move out.
1425 sman->paint_shape(centerx + i, topy - ((i - 60) / 4), shapes.get_shape(0x00, 0));
1426 sman->paint_shape(centerx - i, topy - ((i - 60) / 4), shapes.get_shape(0x01, 0));
1427
1428 if ((40 > i) && (i > -100)) {
1429 // "Why is a moongate already there?"
1430 sman->paint_shape(centerx, centery + 50, shapes.get_shape(0x1A, 0));
1431 } else if (i <= -100) {
1432 // "You have but one path to the answer"
1433 sman->paint_shape(centerx, centery + 50, shapes.get_shape(0x1C, 0));
1434 }
1435
1436 win->show();
1437 WAITDELAYCYCLE3(80);
1438 }
1439
1440 // Wait till the music finished playing
1441 while (Audio::get_ptr()->is_track_playing(home_song_midi))
1442 WAITDELAYCYCLE3(50);
1443
1444 // zoom (run) into moongate
1445 sman->paint_shape(centerx + 1, centery + 1, shapes.get_shape(0x02, 0));
1446 sman->paint_shape(centerx + 1, centery + 1, shapes.get_shape(0x03, 0));
1447 sman->paint_shape(centerx + 1, centery + 1, shapes.get_shape(0x04, 0));
1448 sman->paint_shape(centerx + 1, centery + 1, shapes.get_shape(0x05, 0));
1449
1450 unique_ptr<Image_buffer> unzoomed(win->create_buffer(320, 200));
1451 win->get(unzoomed.get(), 0 + (win->get_game_width() - 320) / 2, 0 + (win->get_game_height() - 200) / 2);
1452 unique_ptr<Image_buffer> zoomed(win->create_buffer(320, 200));
1453
1454 const Image_window::ScalerInfo &scaler = Image_window::Scalers[Image_window::point];
1455
1456 SDL_Surface *draw_surface = win->get_draw_surface();
1457
1458 SDL_SurfaceOwner unzoomed_surf(unzoomed.get(), draw_surface);
1459 SDL_SurfaceOwner zoomed_surf(zoomed.get(), draw_surface);
1460
1461 const int zx = 151;
1462 const int zy = 81;
1463 const int zw = 5;
1464 const int zh = 4;
1465
1466 Audio::get_ptr()->play_sound_effect(sfxfile, INTROSFX_MT32_FLX_INTRO_MT_SHOT_WAV);
1467
1468 uint32 next_ticks = SDL_GetTicks() + 10;
1469 for (int i = 159; i >= 0; i--) {
1470 int sw = zw + (320 - zw) * i / 160;
1471 int sh = zh + (200 - zh) * i / 160;
1472 int sx = zx + (0 - zx) * i / 160;
1473 int sy = zy + (0 - zy) * i / 160;
1474
1475 // frame drop?
1476 if (next_ticks > SDL_GetTicks()) {
1477 scaler.arb->Scale(unzoomed_surf.get(), sx, sy, sw, sh, zoomed_surf.get(), 0, 0, 320, 200, true);
1478 win->put(zoomed.get(), 0 + (win->get_game_width() - 320) / 2, 0 + (win->get_game_height() - 200) / 2);
1479 win->show();
1480 int delta = next_ticks - SDL_GetTicks();
1481 if (delta < 0) delta = 0;
1482 WAITDELAYCYCLE3(delta);
1483 }
1484 next_ticks += 5;
1485 }
1486 }
1487
get_menu_shape()1488 Shape_frame *BG_Game::get_menu_shape() {
1489 return menushapes.get_shape(0x2, 0);
1490 }
1491
1492
top_menu()1493 void BG_Game::top_menu() {
1494 Audio::get_ptr()->start_music(menu_midi, true, INTROMUS);
1495 sman->paint_shape(topx, topy, get_menu_shape());
1496 pal->load(INTROPAL_DAT, PATCH_INTROPAL, 6);
1497 pal->fade_in(60);
1498 }
1499
show_journey_failed()1500 void BG_Game::show_journey_failed() {
1501 pal->fade_out(50);
1502 gwin->clear_screen(true);
1503 sman->paint_shape(topx, topy, get_menu_shape());
1504 journey_failed_text();
1505 }
1506
1507 class ExVoiceBuffer {
1508 private:
1509 const char *file;
1510 const char *patch;
1511 int index;
1512 bool played;
1513 public:
1514 bool play_it();
1515
ExVoiceBuffer(const char * f,const char * p,int i)1516 ExVoiceBuffer(const char *f, const char *p, int i)
1517 : file(f), patch(p), index(i), played(false)
1518 { }
can_play() const1519 bool can_play() const {
1520 return file || patch;
1521 }
1522 };
1523
play_it()1524 bool ExVoiceBuffer::play_it() {
1525 size_t size;
1526 U7multiobject voc(file, patch, index);
1527 auto buffer = voc.retrieve(size);
1528 uint8 *buf = buffer.get();
1529 if (!memcmp(buf, "voc", sizeof("voc") - 1)) {
1530 // IFF junk.
1531 buf += 8;
1532 size -= 8;
1533 }
1534 Audio::get_ptr()->copy_and_play(buf, size, false);
1535 played = true;
1536
1537 return false;
1538 }
1539
end_game(bool success)1540 void BG_Game::end_game(bool success) {
1541 Font *font = fontManager.get_font("MENU_FONT");
1542
1543 if (!success) {
1544 TextScroller text(MAINSHP_FLX, 0x15, font, nullptr);
1545 gwin->clear_screen();
1546 pal->load(INTROPAL_DAT, PATCH_INTROPAL, 0);
1547 for (sint32 i = 0; i < text.get_count(); i++) {
1548 text.show_line(gwin, topx, topx + 320, topy + 20 + i * 12, i);
1549 }
1550
1551 pal->fade_in(c_fade_in_time);
1552 wait_delay(10000);
1553 pal->fade_out(c_fade_out_time);
1554
1555 gwin->clear_screen();
1556 font->center_text(ibuf, centerx, centery - 10, get_text_msg(end_of_ultima7));
1557 pal->fade_in(c_fade_in_time);
1558 wait_delay(4000);
1559 pal->fade_out(c_fade_out_time);
1560
1561 gwin->clear_screen();
1562 font->center_text(ibuf, centerx, centery - 10, get_text_msg(end_of_britannia));
1563 pal->fade_in(c_fade_in_time);
1564 wait_delay(4000);
1565 pal->fade_out(c_fade_out_time);
1566 gwin->clear_screen(true);
1567 return;
1568 }
1569
1570 Audio *audio = Audio::get_ptr();
1571 audio->stop_music();
1572 MyMidiPlayer *midi = audio->get_midi();
1573 if (midi) midi->set_timbre_lib(MyMidiPlayer::TIMBRE_LIB_ENDGAME);
1574
1575 bool speech = Audio::get_ptr()->is_audio_enabled() &&
1576 Audio::get_ptr()->is_speech_enabled();
1577 bool want_subs = !speech || Audio::get_ptr()->is_speech_with_subs();
1578
1579 // Clear screen
1580 gwin->clear_screen(true);
1581
1582 ExVoiceBuffer speech1(ENDGAME, PATCH_ENDGAME, 7);
1583 ExVoiceBuffer speech2(ENDGAME, PATCH_ENDGAME, 8);
1584 ExVoiceBuffer speech3(ENDGAME, PATCH_ENDGAME, 9);
1585
1586 /* There seems to be something wrong with the shapes. Needs investigating
1587 U7multiobject shapes(ENDGAME, PATCH_ENDGAME, 10);
1588 shapes.retrieve("endgame.shp");
1589 Shape_file sf("endgame.shp");
1590 int x = get_width()/2-160;
1591 int y = get_height()/2-100;
1592 cout << "Shape in Endgame.dat has " << sf.get_num_frames() << endl;
1593 */
1594
1595 // Fli Buffers
1596 playfli fli1(ENDGAME, PATCH_ENDGAME, 0);
1597 playfli fli2(ENDGAME, PATCH_ENDGAME, 1);
1598 playfli fli3(ENDGAME, PATCH_ENDGAME, 2);
1599
1600 fli1.play(win, 0, 0, 0);
1601
1602 // Start endgame music.
1603 if (midi) midi->start_music(ENDSCORE_XMI, 1, false);
1604
1605 try {
1606 unsigned int next = 0;
1607 for (unsigned int i = 0; i < 240; i++) {
1608 next = fli1.play(win, 0, 1, next);
1609 if (wait_delay(0)) {
1610 throw UserSkipException();
1611 }
1612 }
1613
1614 for (unsigned int i = 1; i < 150; i++) {
1615 next = fli1.play(win, i, i + 1, next);
1616 if (wait_delay(0)) {
1617 throw UserSkipException();
1618 }
1619 }
1620
1621 if (speech) speech1.play_it();
1622 Font *endfont2 = fontManager.get_font("END2_FONT");
1623 Font *endfont3 = fontManager.get_font("END3_FONT");
1624 Font *normal = fontManager.get_font("NORMAL_FONT");
1625
1626 const char *message = get_text_msg(you_cannot_do_that);
1627 int height = topy + 200 - endfont2->get_text_height() * 2;
1628 int width = (gwin->get_width() - endfont2->get_text_width(message)) / 2;
1629
1630 for (unsigned int i = 150; i < 204; i++) {
1631 next = fli1.play(win, i, i, next);
1632 if (want_subs)
1633 endfont2->draw_text(ibuf, width, height, message);
1634 win->show();
1635 if (wait_delay(0, 0, 1)) {
1636 throw UserSkipException();
1637 }
1638 }
1639
1640 // Set new music
1641 if (midi) midi->start_music(ENDSCORE_XMI, 2, false);
1642
1643 // Set speech
1644
1645 if (speech) speech2.play_it();
1646
1647 message = get_text_msg(damn_avatar);
1648 width = (gwin->get_width() - endfont2->get_text_width(message)) / 2;
1649
1650 for (unsigned int i = 0; i < 100; i++) {
1651 next = fli2.play(win, i, i, next);
1652 if (want_subs)
1653 endfont2->draw_text(ibuf, width, height, message);
1654 win->show();
1655 if (wait_delay(0, 0, 1)) {
1656 throw UserSkipException();
1657 }
1658 }
1659
1660 Palette *pal = fli2.get_palette();
1661 next = SDL_GetTicks();
1662 for (unsigned int i = 1000 + next; next < i; next += 10) {
1663 // Speed related frame skipping detection
1664 bool skip_frame = Game_window::get_instance()->get_frame_skipping() && SDL_GetTicks() >= next;
1665 while (SDL_GetTicks() < next)
1666 ;
1667 if (!skip_frame) {
1668 pal->set_brightness((i - next) / 10);
1669 pal->apply();
1670 }
1671 if (wait_delay(0, 0, 1)) {
1672 throw UserSkipException();
1673 }
1674 }
1675
1676 pal->set_brightness(80); // Set readable brightness
1677 // Text message 1
1678
1679 // Paint backgound black
1680 win->fill8(0);
1681
1682 // Paint text
1683 message = get_text_msg(blackgate_destroyed);
1684 width = (gwin->get_width() - normal->get_text_width(message)) / 2;
1685 height = (gwin->get_height() - normal->get_text_height()) / 2;
1686
1687 normal->draw_text(ibuf, width, height, message);
1688
1689 // Fade in for 1 sec (50 cycles)
1690 pal->fade(50, 1, 0);
1691
1692 // Display text for 3 seconds
1693 for (unsigned int i = 0; i < 30; i++) {
1694 if (wait_delay(100)) {
1695 throw UserSkipException();
1696 }
1697 }
1698
1699 // Fade out for 1 sec (50 cycles)
1700 pal->fade(50, 0, 0);
1701
1702 // Now the second text message
1703
1704 // Paint backgound black
1705 win->fill8(0);
1706
1707 // Paint text
1708 message = get_text_msg(guardian_has_stopped);
1709 width = (gwin->get_width() - normal->get_text_width(message)) / 2;
1710
1711 normal->draw_text(ibuf, width, height, message);
1712
1713 // Fade in for 1 sec (50 cycles)
1714 pal->fade(50, 1, 0);
1715
1716 // Display text for approx 3 seonds
1717 for (unsigned int i = 0; i < 30; i++) {
1718 if (wait_delay(100)) {
1719 throw UserSkipException();
1720 }
1721 }
1722
1723 // Fade out for 1 sec (50 cycles)
1724 pal->fade(50, 0, 0);
1725
1726 fli3.play(win, 0, 0, next);
1727 pal = fli3.get_palette();
1728 next = SDL_GetTicks();
1729 for (unsigned int i = 1000 + next; next < i; next += 10) {
1730 // Speed related frame skipping detection
1731 bool skip_frame = Game_window::get_instance()->get_frame_skipping() && SDL_GetTicks() >= next;
1732 while (SDL_GetTicks() < next)
1733 ;
1734 if (!skip_frame) {
1735 pal->set_brightness(100 - (i - next) / 10);
1736 pal->apply();
1737 }
1738 if (wait_delay(0, 0, 1)) {
1739 throw UserSkipException();
1740 }
1741 }
1742
1743 if (speech) speech3.play_it();
1744
1745 playfli::fliinfo finfo;
1746 fli3.info(&finfo);
1747
1748 int m;
1749 int starty = (gwin->get_height() - endfont3->get_text_height() * 8) / 2;
1750
1751 next = SDL_GetTicks();
1752 for (unsigned int i = next + 28000; i > next;) {
1753 for (unsigned int j = 0; j < static_cast<unsigned>(finfo.frames); j++) {
1754 next = fli3.play(win, j, j, next);
1755 if (want_subs) {
1756 for (m = 0; m < 8; m++)
1757 endfont3->center_text(ibuf, centerx, starty + endfont3->get_text_height()*m, get_text_msg(txt_screen0 + m));
1758 }
1759 win->show();
1760 if (wait_delay(10, 0, 1)) {
1761 throw UserSkipException();
1762 }
1763 }
1764 }
1765
1766 next = SDL_GetTicks();
1767 for (unsigned int i = 1000 + next; next < i; next += 10) {
1768 // Speed related frame skipping detection
1769 bool skip_frame = Game_window::get_instance()->get_frame_skipping() && SDL_GetTicks() >= next;
1770 while (SDL_GetTicks() < next)
1771 ;
1772 if (!skip_frame) {
1773 pal->set_brightness((i - next) / 10);
1774 pal->apply();
1775 }
1776 if (wait_delay(0, 0, 1)) {
1777 throw UserSkipException();
1778 }
1779 }
1780
1781 // Text Screen 1
1782 pal->set_brightness(80); // Set readable brightness
1783
1784 // Paint backgound black
1785 win->fill8(0);
1786
1787 //Because of the German version we have to fit 11 lines of height 20 into a screen of 200 pixels
1788 //so starty has needs to be a tiny bit in the negative but not -10
1789 starty = (gwin->get_height() - normal->get_text_height() * 11) / 2.5;
1790
1791 for (unsigned int i = 0; i < 11; i++) {
1792 message = get_text_msg(txt_screen1 + i);
1793 normal->draw_text(ibuf, centerx - normal->get_text_width(message) / 2, starty + normal->get_text_height()*i, message);
1794 }
1795
1796 // Fade in for 1 sec (50 cycles)
1797 pal->fade(50, 1, 0);
1798
1799 // Display text for 20 seconds (only 10 at the moment)
1800 for (unsigned int i = 0; i < 100; i++) {
1801 if (wait_delay(100)) {
1802 throw UserSkipException();
1803 }
1804 }
1805
1806 // Fade out for 1 sec (50 cycles)
1807 pal->fade(50, 0, 0);
1808
1809 if (wait_delay(10))
1810 throw UserSkipException();
1811
1812 // Text Screen 2
1813
1814 // Paint backgound black
1815 win->fill8(0);
1816
1817 starty = (gwin->get_height() - normal->get_text_height() * 9) / 2;
1818
1819 for (unsigned int i = 0; i < 9; i++) {
1820 message = get_text_msg(txt_screen2 + i);
1821 normal->draw_text(ibuf, centerx - normal->get_text_width(message) / 2, starty + normal->get_text_height()*i, message);
1822 }
1823
1824 // Fade in for 1 sec (50 cycles)
1825 pal->fade(50, 1, 0);
1826
1827 // Display text for 20 seonds (only 8 at the moment)
1828 for (unsigned int i = 0; i < 80; i++) {
1829 if (wait_delay(100)) {
1830 throw UserSkipException();
1831 }
1832 }
1833
1834 // Fade out for 1 sec (50 cycles)
1835 pal->fade(50, 0, 0);
1836
1837 if (wait_delay(10))
1838 throw UserSkipException();
1839
1840 // Text Screen 3
1841
1842 // Paint backgound black
1843 win->fill8(0);
1844
1845 starty = (gwin->get_height() - normal->get_text_height() * 8) / 2;
1846
1847 for (unsigned int i = 0; i < 8; i++) {
1848 message = get_text_msg(txt_screen3 + i);
1849 normal->draw_text(ibuf, centerx - normal->get_text_width(message) / 2, starty + normal->get_text_height()*i, message);
1850 }
1851
1852 // Fade in for 1 sec (50 cycles)
1853 pal->fade(50, 1, 0);
1854
1855 // Display text for 20 seonds (only 8 at the moment)
1856 for (unsigned int i = 0; i < 80; i++) {
1857 if (wait_delay(100)) {
1858 throw UserSkipException();
1859 }
1860 }
1861
1862 // Fade out for 1 sec (50 cycles)
1863 pal->fade(50, 0, 0);
1864
1865 if (wait_delay(10))
1866 throw UserSkipException();
1867
1868 // Text Screen 4
1869
1870 // Paint backgound black
1871 win->fill8(0);
1872
1873 starty = (gwin->get_height() - normal->get_text_height() * 5) / 2;
1874
1875 for (unsigned int i = 0; i < 5; i++) {
1876 message = get_text_msg(txt_screen4 + i);
1877 normal->draw_text(ibuf, centerx - normal->get_text_width(message) / 2, starty + normal->get_text_height()*i, message);
1878 }
1879
1880 // Fade in for 1 sec (50 cycles)
1881 pal->fade(50, 1, 0);
1882
1883 // Display text for 10 seonds (only 5 at the moment)
1884 for (unsigned int i = 0; i < 50; i++) {
1885 if (wait_delay(100)) {
1886 throw UserSkipException();
1887 }
1888 }
1889
1890 // Fade out for 1 sec (50 cycles)
1891 pal->fade(50, 0, 0);
1892 #if 0
1893 //TODO: only when finishing a game and not when viewed from menu
1894 if (when not in menu) {
1895 if (wait_delay(10)) break;
1896
1897 // Congratulations
1898
1899 // Paint backgound black
1900 win->fill8(0);
1901
1902 starty = (gwin->get_height() - normal->get_text_height() * 6) / 2;
1903
1904 //TODO: figure out the time it took to complete the game
1905 // in exultmsg.txt it is "%d year s , %d month s , & %d day s"
1906 // only showing years or months if there were any
1907 for (unsigned int i = 0; i < 9; i++) {
1908 message = get_text_msg(congrats + i);
1909 normal->draw_text(ibuf, centerx - normal->get_text_width(message) / 2, starty + normal->get_text_height()*i, message);
1910 }
1911
1912 // Fade in for 1 sec (50 cycles)
1913 pal->fade(50, 1, 0);
1914
1915 // Display text for 20 seonds (only 8 at the moment)
1916 for (unsigned int i = 0; i < 80; i++) {
1917 if (wait_delay(100)) {
1918 throw UserSkipException();
1919 }
1920 }
1921
1922 // Fade out for 1 sec (50 cycles)
1923 pal->fade(50, 0, 0);
1924 }
1925 #endif
1926
1927 } catch (const UserSkipException &/*x*/) {
1928 }
1929
1930 if (midi) {
1931 midi->stop_music();
1932 midi->set_timbre_lib(MyMidiPlayer::TIMBRE_LIB_GAME);
1933 }
1934
1935 audio->stop_music();
1936
1937 gwin->clear_screen(true);
1938 }
1939
show_quotes()1940 void BG_Game::show_quotes() {
1941 Audio::get_ptr()->start_music(quotes_midi, false, INTROMUS);
1942 TextScroller quotes(MAINSHP_FLX, 0x10,
1943 fontManager.get_font("MENU_FONT"),
1944 menushapes.extract_shape(0x14)
1945 );
1946 quotes.run(gwin);
1947 }
1948
show_credits()1949 void BG_Game::show_credits() {
1950 pal->load(INTROPAL_DAT, PATCH_INTROPAL, 6);
1951 Audio::get_ptr()->start_music(credits_midi, false, INTROMUS);
1952 TextScroller credits(MAINSHP_FLX, 0x0E,
1953 fontManager.get_font("MENU_FONT"),
1954 menushapes.extract_shape(0x14)
1955 );
1956 if (credits.run(gwin)) { // Watched through the entire sequence?
1957 std::ofstream quotesflg;
1958 U7open(quotesflg, "<SAVEGAME>/quotes.flg");
1959 quotesflg.close();
1960 }
1961 }
1962
new_game(Vga_file & shapes)1963 bool BG_Game::new_game(Vga_file &shapes) {
1964 int menuy = topy + 110;
1965 Font *font = fontManager.get_font("MENU_FONT");
1966
1967 Vga_file faces_vga;
1968 // Need to know if SI is installed
1969 bool si_installed =
1970 (gamemanager->is_si_installed() || gamemanager->is_ss_installed())
1971 && U7exists("<SERPENT_STATIC>/shapes.vga");
1972
1973 // List of files to load.
1974 std::vector<std::pair<std::string, int> > source;
1975 source.emplace_back(FACES_VGA, -1);
1976 // Multiracial faces.
1977 const str_int_pair &resource = get_resource("files/mrfacesvga");
1978 source.emplace_back(resource.str, resource.num);
1979 source.emplace_back(PATCH_FACES, -1);
1980 faces_vga.load(source);
1981
1982 const int max_name_len = 16;
1983 char npc_name[max_name_len + 1];
1984 char disp_name[max_name_len + 2];
1985 npc_name[0] = 0;
1986
1987 int selected = 0;
1988 int num_choices = 4;
1989 SDL_Event event;
1990 bool editing = true;
1991 bool redraw = true;
1992 bool ok = true;
1993
1994 // Skin info
1995 Avatar_default_skin *defskin = Shapeinfo_lookup::GetDefaultAvSkin();
1996 Skin_data *skindata =
1997 Shapeinfo_lookup::GetSkinInfoSafe(
1998 defskin->default_skin, defskin->default_female, si_installed);
1999
2000 Palette *pal = gwin->get_pal();
2001 // This should work because the palette in exult_bg.flx is
2002 // a single-file object.
2003 pal->load(File_spec(INTROPAL_DAT, 6),
2004 File_spec(get_resource("files/gameflx").str, EXULT_BG_FLX_U7MENUPAL_PAL),
2005 File_spec(PATCH_INTROPAL, 6), 0);
2006 auto *oldpal = new Palette();
2007 oldpal->load(INTROPAL_DAT, PATCH_INTROPAL, 6);
2008
2009 // Create palette translation table. Maybe make them static?
2010 auto *transto = new unsigned char[256];
2011 oldpal->create_palette_map(pal, transto);
2012 delete oldpal;
2013 pal->apply(true);
2014 do {
2015 Delay();
2016 if (redraw) {
2017 gwin->clear_screen();
2018 sman->paint_shape(topx, topy, shapes.get_shape(0x2, 0), false, transto);
2019 sman->paint_shape(topx + 10, menuy + 10, shapes.get_shape(0xC, selected == 0), false, transto);
2020 Shape_frame *sex_shape = shapes.get_shape(0xA, selected == 1);
2021 sman->paint_shape(topx + 10, menuy + 25, sex_shape, false, transto);
2022 int sex_width = sex_shape->get_width() + 10;
2023 if (sex_width > 35) sex_width += 25;
2024 else sex_width = 60;
2025
2026 sman->paint_shape(topx + sex_width, menuy + 25, shapes.get_shape(0xB, skindata->is_female), false, transto);
2027
2028 Shape_frame *portrait = faces_vga.get_shape(skindata->face_shape, skindata->face_frame);
2029 sman->paint_shape(topx + 290, menuy + 61, portrait);
2030
2031 sman->paint_shape(topx + 10, topy + 180, shapes.get_shape(0x8, selected == 2), false, transto);
2032 sman->paint_shape(centerx + 10, topy + 180, shapes.get_shape(0x7, selected == 3), false, transto);
2033 if (selected == 0)
2034 snprintf(disp_name, max_name_len + 2, "%s_", npc_name);
2035 else
2036 snprintf(disp_name, max_name_len + 2, "%s", npc_name);
2037 font->draw_text(ibuf, topx + 60, menuy + 10, disp_name, transto);
2038 gwin->get_win()->show();
2039 redraw = false;
2040 }
2041
2042 while (SDL_PollEvent(&event)) {
2043 Uint16 keysym_unicode = 0;
2044 bool isTextInput = false;
2045 if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
2046 SDL_Rect rectName = { topx + 10, menuy + 10, 130, 16 };
2047 SDL_Rect rectSex = { topx + 10, menuy + 25, 130, 16 };
2048 SDL_Rect rectOnward = { topx + 10, topy + 180, 130, 16 };
2049 SDL_Rect rectReturn = { centerx + 10, topy + 180, 130, 16 };
2050 SDL_Point point;
2051 gwin->get_win()->screen_to_game(event.button.x, event.button.y, gwin->get_fastmouse(), point.x, point.y);
2052 if (SDL_EnclosePoints(&point, 1, &rectName, nullptr)) {
2053 if (event.type == SDL_MOUSEBUTTONDOWN) {
2054 selected = 0;
2055 } else if (selected == 0 && touchui != nullptr) {
2056 touchui->promptForName(npc_name);
2057 }
2058 redraw = true;
2059 } else if (SDL_EnclosePoints(&point, 1, &rectSex, nullptr)) {
2060 if (event.type == SDL_MOUSEBUTTONDOWN) {
2061 selected = 1;
2062 } else if (selected == 1) {
2063 skindata = Shapeinfo_lookup::GetNextSelSkin(skindata, si_installed, true);
2064 }
2065 redraw = true;
2066 } else if (SDL_EnclosePoints(&point, 1, &rectOnward, nullptr)) {
2067 if (event.type == SDL_MOUSEBUTTONDOWN) {
2068 selected = 2;
2069 } else if (selected == 2) {
2070 editing = false;
2071 ok = true;
2072 }
2073 redraw = true;
2074 } else if (SDL_EnclosePoints(&point, 1, &rectReturn, nullptr)) {
2075 if (event.type == SDL_MOUSEBUTTONDOWN) {
2076 selected = 3;
2077 } else if (selected == 3) {
2078 editing = false;
2079 ok = false;
2080 }
2081 redraw = true;
2082 }
2083 } else if (event.type == TouchUI::eventType) {
2084 if (event.user.code == TouchUI::EVENT_CODE_TEXT_INPUT) {
2085 if (selected == 0 && event.user.data1 != nullptr) {
2086 strncpy(npc_name, static_cast<char*>(event.user.data1), max_name_len);
2087 npc_name[max_name_len] = '\0';
2088 free(event.user.data1);
2089 redraw = true;
2090 }
2091 }
2092 } else if (event.type == SDL_TEXTINPUT) {
2093 isTextInput = true;
2094 event.type = SDL_KEYDOWN;
2095 event.key.keysym.sym = SDLK_UNKNOWN;
2096 keysym_unicode = event.text.text[0];
2097 }
2098 if (event.type == SDL_KEYDOWN) {
2099 redraw = true;
2100 switch (event.key.keysym.sym) {
2101 case SDLK_SPACE:
2102 if (selected == 0) {
2103 int len = strlen(npc_name);
2104 if (len < max_name_len) {
2105 npc_name[len] = ' ';
2106 npc_name[len + 1] = 0;
2107 }
2108 } else if (selected == 1)
2109 skindata = Shapeinfo_lookup::GetNextSelSkin(skindata, si_installed, true);
2110 else if (selected == 2) {
2111 editing = false;
2112 ok = true;
2113 } else if (selected == 3)
2114 editing = ok = false;
2115 break;
2116 case SDLK_LEFT:
2117 if (selected == 1)
2118 skindata = Shapeinfo_lookup::GetPrevSelSkin(skindata, si_installed, true);
2119 break;
2120 case SDLK_RIGHT:
2121 if (selected == 1)
2122 skindata = Shapeinfo_lookup::GetNextSelSkin(skindata, si_installed, true);
2123 break;
2124 case SDLK_ESCAPE:
2125 editing = false;
2126 ok = false;
2127 break;
2128 case SDLK_TAB:
2129 case SDLK_DOWN:
2130 ++selected;
2131 if (selected == num_choices)
2132 selected = 0;
2133 break;
2134 case SDLK_UP:
2135 --selected;
2136 if (selected < 0)
2137 selected = num_choices - 1;
2138 break;
2139 case SDLK_RETURN:
2140 case SDLK_KP_ENTER:
2141 if (selected < 2)
2142 ++selected;
2143 else if (selected == 2) {
2144 editing = false;
2145 ok = true;
2146 } else
2147 editing = ok = false;
2148 break;
2149 case SDLK_BACKSPACE:
2150 if (selected == 0 && strlen(npc_name) > 0)
2151 npc_name[strlen(npc_name) - 1] = 0;
2152 break;
2153 default: {
2154 if ((isTextInput && selected == 0) || (!isTextInput && keysym_unicode > +'~' && selected == 0))
2155 {
2156 int len = strlen(npc_name);
2157 char chr = 0;
2158 if ((keysym_unicode & 0xFF80) == 0)
2159 chr = keysym_unicode & 0x7F;
2160
2161 if (chr >= ' ' && len < max_name_len) {
2162 npc_name[len] = chr;
2163 npc_name[len + 1] = 0;
2164 }
2165 } else
2166 redraw = false;
2167 }
2168 break;
2169 }
2170 }
2171 }
2172 } while (editing);
2173
2174 delete [] transto;
2175 gwin->clear_screen();
2176
2177 if (ok) {
2178 #ifdef DEBUG
2179 std::cout << "Skin is: " << skindata->skin_id << " Sex is: " << skindata->is_female << std::endl;
2180 #endif
2181 set_avskin(skindata->skin_id);
2182 set_avname(npc_name);
2183 set_avsex(skindata->is_female);
2184 pal->fade_out(c_fade_out_time);
2185 gwin->clear_screen(true);
2186 ok = gwin->init_gamedat(true);
2187 } else {
2188 pal->load(INTROPAL_DAT, PATCH_INTROPAL, 6);
2189 sman->paint_shape(topx, topy, shapes.get_shape(0x2, 0));
2190 pal->apply();
2191 }
2192 return ok;
2193 }
2194