1 /*
2  * Copyright 2010-2014 OpenXcom Developers.
3  *
4  * This file is part of OpenXcom.
5  *
6  * OpenXcom is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * OpenXcom is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with OpenXcom.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 #include "IntroState.h"
20 #include <SDL_mixer.h>
21 #include "../Engine/Adlib/adlplayer.h"
22 #include "../Engine/Logger.h"
23 #include "../Engine/Game.h"
24 #include "../Engine/Options.h"
25 #include "../Engine/Flc.h"
26 #include "../Engine/CrossPlatform.h"
27 #include "../Engine/Screen.h"
28 #include "../Engine/Music.h"
29 #include "../Engine/Sound.h"
30 #include "../Resource/ResourcePack.h"
31 #include "MainMenuState.h"
32 
33 namespace OpenXcom
34 {
35 
36 /**
37  * Initializes all the elements in the Intro screen.
38  * @param game Pointer to the core game.
39  * @param wasLetterBoxed Was the game letterboxed?
40  */
IntroState(Game * game,bool wasLetterBoxed)41 IntroState::IntroState(Game *game, bool wasLetterBoxed) : State(game), _wasLetterBoxed(wasLetterBoxed)
42 {
43 	_oldMusic = Options::musicVolume;
44 	_oldSound = Options::soundVolume;
45 	Options::musicVolume = Options::soundVolume = std::max(_oldMusic, _oldSound);
46 	_game->setVolume(Options::soundVolume, Options::musicVolume, -1);
47 	_introFile = CrossPlatform::getDataFile("UFOINTRO/UFOINT.FLI");
48 	_introSoundFileDOS = CrossPlatform::getDataFile("SOUND/INTRO.CAT");
49 	_introSoundFileWin = CrossPlatform::getDataFile("SOUND/SAMPLE3.CAT");
50 }
51 
52 /**
53  *
54  */
~IntroState()55 IntroState::~IntroState()
56 {
57 }
58 
59 
60 typedef struct
61 {
62 	std::string catFile;
63 	int sound;
64 	int volume;
65 } soundInFile;
66 
67 
68 // the pure MS-DOS experience
69 static soundInFile introCatOnlySounds[]=
70 {
71 {"INTRO.CAT", 0x0, 32},
72 {"INTRO.CAT", 0x1, 32},
73 {"INTRO.CAT", 0x2, 32},
74 {"INTRO.CAT", 0x3, 32},
75 {"INTRO.CAT", 0x4, 32},
76 {"INTRO.CAT", 0x5, 32},
77 {"INTRO.CAT", 0x6, 32},
78 {"INTRO.CAT", 0x7, 32},
79 {"INTRO.CAT", 0x8, 32},
80 {"INTRO.CAT", 0x9, 32},
81 {"INTRO.CAT", 0xa, 32},
82 {"INTRO.CAT", 0xb, 32},
83 {"INTRO.CAT", 0xc, 32},
84 {"INTRO.CAT", 0xd, 32},
85 {"INTRO.CAT", 0xe, 32},
86 {"INTRO.CAT", 0xf, 32},
87 {"INTRO.CAT", 0x10, 32},
88 {"INTRO.CAT", 0x11, 32},
89 {"INTRO.CAT", 0x12, 32},
90 {"INTRO.CAT", 0x13, 32},
91 {"INTRO.CAT", 0x14, 32},
92 {"INTRO.CAT", 0x15, 32},
93 {"INTRO.CAT", 0x16, 32},
94 {"INTRO.CAT", 0x17, 32},
95 {"INTRO.CAT", 0x18, 32},
96 {"INTRO.CAT", 0x18, 32}
97 };
98 
99 
100 static soundInFile sample3CatOnlySounds[]=
101 {
102 {"SAMPLE3.CAT", 24, 32}, // machine gun
103 {"SAMPLE3.CAT", 5, 32},   // plasma rifle
104 {"SAMPLE3.CAT", 23, 32}, // rifle
105 {"SAMPLE3.CAT",  6, 32}, // some kind of death noise, urgh?
106 {"SAMPLE3.CAT", 9, 64}, // mutdie
107 {"SAMPLE3.CAT", 7, 64}, // dying alien
108 {"SAMPLE3.CAT", 27, 64}, // another dying alien
109 {"SAMPLE3.CAT", 4, 32}, // ??? ship flying? alien screech?
110 {"SAMPLE3.CAT", 0x8, 32}, // fscream
111 {"SAMPLE3.CAT", 11, 32}, // alarm
112 {"SAMPLE3.CAT", 4, 32}, // gun spinning up?
113 {"INTRO.CAT", 0xb, 32},  // reload; this one's not even in sample3
114 {"SAMPLE3.CAT",19, 48},  // whoosh
115 {"INTRO.CAT", 0xd, 32},  // feet, also not in sample3
116 {"SAMPLE3.CAT", 2, 32},  // low pulsating hum
117 {"SAMPLE3.CAT", 30, 32}, // energise
118 {"SAMPLE3.CAT", 21, 32}, // hatch
119 {"SAMPLE3.CAT", 0, 64}, // phizz -- no equivalent in sample3.cat?
120 {"SAMPLE3.CAT", 13, 32}, // warning
121 {"SAMPLE3.CAT", 14, 32}, // detected
122 {"SAMPLE3.CAT", 19, 64}, // UFO flyby whoosh?
123 {"SAMPLE3.CAT", 3, 32}, // growl
124 {"SAMPLE3.CAT", 15, 128}, // voice
125 {"SAMPLE3.CAT", 12, 32}, // beep 1
126 {"SAMPLE3.CAT", 18, 32}, // takeoff
127 {"SAMPLE3.CAT", 20, 32}  // another takeoff/landing sound?? if it exists?
128 };
129 
130 
131 
132 // an attempt at a mix of (subjectively) the best sounds from the two versions
133 // difficult because we can't find a definitive map from old sequence numbers to SAMPLE3.CAT indexes
134 // probably only the Steam version of the game comes with both INTRO.CAT and SAMPLE3.CAT
135 static soundInFile hybridIntroSounds[]=
136 {
137 {"SAMPLE3.CAT", 24, 32}, // machine gun
138 {"SAMPLE3.CAT", 5, 32},   // plasma rifle
139 {"SAMPLE3.CAT", 23, 32}, // rifle
140 {"INTRO.CAT",  3, 32}, // some kind of death noise, urgh?
141 {"INTRO.CAT", 0x4, 64}, // mutdie
142 {"INTRO.CAT", 0x5, 64}, // dying alien
143 {"INTRO.CAT", 0x6, 64}, // another dying alien
144 {"INTRO.CAT", 0x7, 32}, // ??? ship flying? alien screech?
145 {"SAMPLE3.CAT", 0x8, 32}, // fscream
146 {"SAMPLE3.CAT", 11, 32}, // alarm
147 {"SAMPLE3.CAT", 4, 32}, // gun spinning up?
148 {"INTRO.CAT", 0xb, 32},  // reload; this one's not even in sample3
149 {"SAMPLE3.CAT",19, 48},  // whoosh
150 {"INTRO.CAT", 0xd, 32},  // feet, also not in sample3
151 {"INTRO.CAT", 0xe, 32},  // low pulsating hum
152 {"SAMPLE3.CAT", 30, 32}, // energise
153 {"SAMPLE3.CAT", 21, 32}, // hatch
154 {"INTRO.CAT", 0x11, 64}, // phizz
155 {"SAMPLE3.CAT", 13, 32}, // warning
156 {"SAMPLE3.CAT", 14, 32}, // detected
157 {"SAMPLE3.CAT", 19, 64}, // UFO flyby whoosh?
158 {"INTRO.CAT", 0x15, 32}, // growl
159 {"SAMPLE3.CAT", 15, 128}, // voice
160 {"SAMPLE3.CAT", 12, 32}, // beep 1
161 {"SAMPLE3.CAT", 18, 32}, // takeoff
162 {"SAMPLE3.CAT", 20, 32}  // another takeoff/landing sound?? if it exists?
163 };
164 
165 // sample3: 18 is takeoff, 20 is landing; 19 is flyby whoosh sound, not sure for which craft
166 
167 static soundInFile *introSounds[] =
168 {
169 	hybridIntroSounds,
170 	introCatOnlySounds,
171 	sample3CatOnlySounds,
172 	0
173 };
174 
175 
176 typedef struct
177 {
178 	int frameNumber;
179 	int sound;
180 } introSoundEffect;
181 
182 static introSoundEffect introSoundTrack[] =
183 {
184 {0, 0x200}, // inserting this to keep the code simple
185 {149, 0x11},
186 {173, 0x0C},
187 {183, 0x0E},
188 {205, 0x15},
189 {211, 0x201},
190 {211, 0x407},
191 {223, 0x7},
192 {250, 0x1},
193 {253, 0x1},
194 {255, 0x1},
195 {257, 0x1},
196 {260, 0x1},
197 {261, 0x3},
198 {262, 0x1},
199 {264, 0x1},
200 {268, 0x1},
201 {270, 0x1},
202 {272, 0x5},
203 {272, 0x1},
204 {274, 0x1},
205 {278, 0x1},
206 {280, 0x1},
207 {282, 0x8},
208 {282, 0x1},
209 {284, 0x1},
210 {286, 0x1},
211 {288, 0x1},
212 {290, 0x1},
213 {292, 0x6},
214 {292, 0x1},
215 {296, 0x1},
216 {298, 0x1},
217 {300, 0x1},
218 {302, 0x1},
219 {304, 0x1},
220 {306, 0x1},
221 {308, 0x1},
222 {310, 0x1},
223 {312, 0x1},
224 {378, 0x202},
225 {378, 0x9}, // alarm
226 {386, 0x9},
227 {393, 0x9},
228 {399, 0x17}, // bleeps
229 {433, 0x17},
230 {463, 0x12}, // warning
231 {477, 0x12},
232 {487, 0x13}, // ufo detected
233 {495, 0x16}, // voice
234 {501, 0x16},
235 {512, 0xd},  // feet -- not in original
236 {514, 0xd},  // feet -- not in original
237 {522, 0x0B}, // rifle grab
238 {523, 0xd},  // feet -- not in original
239 {525, 0xd},  // feet -- not in original
240 {534, 0x18},
241 {535, 0x405},
242 {560, 0x407},
243 {577, 0x14},
244 {582, 0x405},
245 // {582, 0x18}, // landing! correcting to landing sound!
246 {582, 0x19},
247 {613, 0x407},
248 {615, 0x10},
249 {635, 0x14},
250 {638, 0x14},
251 {639, 0x14},
252 {644, 0x2},
253 {646, 0x2},
254 {648, 0x2},
255 {650, 0x2},
256 {652, 0x2},
257 {654, 0x2},
258 {656, 0x2},
259 {658, 0x2},
260 {660, 0x2},
261 {662, 0x2},
262 {664, 0x2},
263 {666, 0x2},
264 {668, 0x401},
265 {681, 0x406},
266 {687, 0x402},
267 {689, 0x407},
268 {694, 0x0A},
269 {711, 0x407},
270 {711, 0x0},
271 {714, 0x0},
272 {716, 0x4},
273 {717, 0x0},
274 {720, 0x0},
275 {723, 0x0},
276 {726, 0x5},
277 {726, 0x0},
278 {729, 0x0},
279 {732, 0x0},
280 {735, 0x0},
281 {738, 0x0},
282 {741, 0x0},
283 {742, 0x6},
284 {744, 0x0},
285 {747, 0x0},
286 {750, 0x0},
287 {753, 0x0},
288 {756, 0x0},
289 {759, 0x0},
290 {762, 0x0},
291 {765, 0x0},
292 {768, 0x0},
293 {771, 0x0},
294 {774, 0x0},
295 {777, 0x0},
296 {780, 0x0},
297 {783, 0x0},
298 {786, 0x0},
299 {790, 0x15},
300 {790, 0x15},
301 {807, 0x2},
302 {810, 0x2},
303 {812, 0x2},
304 {814, 0x2},
305 {816, 0x0},
306 {819, 0x0},
307 {822, 0x0},
308 {824, 0x40A},
309 {824, 0x5},
310 {827, 0x6},
311 {835, 0x0F},
312 {841, 0x0F},
313 {845, 0x0F},
314 {855, 0x407},
315 {879, 0x0C},
316 {65535, 0x0FFFF}
317 };
318 
319 
musicDone()320 static void musicDone()
321 {
322 	Flc::flc.quit = true;
323 }
324 
325 static struct AudioSequence
326 {
327 	ResourcePack *rp;
328 	Music *m;
329 	Sound *s;
330 	int trackPosition;
331 
AudioSequenceOpenXcom::AudioSequence332 	AudioSequence(ResourcePack *resources) : rp(resources), m(0), s(0), trackPosition(0)
333 	{
334 	}
335 
operator ()OpenXcom::AudioSequence336 	void operator ()()
337 	{
338 		while (Flc::flc.FrameCount >= introSoundTrack[trackPosition].frameNumber)
339 		{
340 			int command = introSoundTrack[trackPosition].sound;
341 
342 			if (command & 0x200)
343 			{
344 #ifndef __NO_MUSIC
345 				switch(command)
346 				{
347 				case 0x200:
348 					Log(LOG_DEBUG) << "Playing gmintro1";
349 					m = rp->getMusic("GMINTRO1");
350 					m->play(1);
351 					break;
352 				case 0x201:
353 					Log(LOG_DEBUG) << "Playing gmintro2";
354 					m = rp->getMusic("GMINTRO2");
355 					m->play(1);
356 					break;
357 				case 0x202:
358 					Log(LOG_DEBUG) << "Playing gmintro3";
359 					m = rp->getMusic("GMINTRO3");
360 					m->play(1);
361 					Mix_HookMusicFinished(musicDone);
362 					break;
363 				}
364 #endif
365 			}
366 			else if (command & 0x400)
367 			{
368 				Flc::flc.HeaderSpeed = (1000.0/70.0) * (command & 0xff);
369 				Log(LOG_DEBUG) << "Frame delay now: " << Flc::flc.HeaderSpeed;
370 			}
371 			else if (command <= 0x19)
372 			{
373 				for (soundInFile **sounds = introSounds; *sounds; ++sounds) // try hybrid sound set, then intro.cat or sample3.cat alone
374 				{
375 					soundInFile *sf = (*sounds) + command;
376 					int channel = trackPosition % 4; // use at most four channels to play sound effects
377 					double ratio = (double)Options::soundVolume / MIX_MAX_VOLUME;
378 					Log(LOG_DEBUG) << "playing: " << sf->catFile << ":" << sf->sound << " for index " << command;
379 					s = rp->getSound(sf->catFile, sf->sound);
380 					if (s)
381 					{
382 						s->play(channel);
383 						Mix_Volume(channel, sf->volume * ratio);
384 						break;
385 					}
386 					else Log(LOG_DEBUG) << "Couldn't play " << sf->catFile << ":" << sf->sound;
387 				}
388 			}
389 			++trackPosition;
390 		}
391 
392 	}
393 } *audioSequence;
394 
395 
audioHandler()396 static void audioHandler()
397 {
398 	(*audioSequence)();
399 }
400 
401 /**
402  * Play the intro.
403  */
init()404 void IntroState::init()
405 {
406 	State::init();
407 	Options::keepAspectRatio = _wasLetterBoxed;
408 	if (CrossPlatform::fileExists(_introFile) && (CrossPlatform::fileExists(_introSoundFileDOS) || CrossPlatform::fileExists(_introSoundFileWin)))
409 	{
410 		audioSequence = new AudioSequence(_game->getResourcePack());
411 		Flc::flc.realscreen = _game->getScreen();
412 		Flc::FlcInit(_introFile.c_str());
413 		Flc::flc.dx = (Options::baseXResolution - Screen::ORIGINAL_WIDTH) / 2;
414 		Flc::flc.dy = (Options::baseYResolution - Screen::ORIGINAL_HEIGHT) / 2;
415 		Flc::flc.loop = 0; // just the one time, please
416 		Flc::FlcMain(&audioHandler);
417 		Flc::FlcDeInit();
418 		delete audioSequence;
419 
420 
421 #ifndef __NO_MUSIC
422 		// fade out!
423 		Mix_FadeOutChannel(-1, 45 * 20);
424 		if (Mix_GetMusicType(0) != MUS_MID) { Mix_FadeOutMusic(45 * 20); func_fade(); } // SDL_Mixer has trouble with native midi and volume on windows, which is the most likely use case, so f@%# it.
425 		else { Mix_HaltMusic(); }
426 #endif
427 
428 		SDL_Color pal[256];
429 		SDL_Color pal2[256];
430 		memcpy(pal, _game->getScreen()->getPalette(), sizeof(SDL_Color) * 256);
431 		for (int i = 20; i > 0; --i)
432 		{
433 			SDL_Event event;
434 			if (SDL_PollEvent(&event) && event.type == SDL_KEYDOWN) break;
435 			for (int color = 0; color < 256; ++color)
436 			{
437 				pal2[color].r = (((int)pal[color].r) * i) / 20;
438 				pal2[color].g = (((int)pal[color].g) * i) / 20;
439 				pal2[color].b = (((int)pal[color].b) * i) / 20;
440 				pal2[color].unused = pal[color].unused;
441 			}
442 			_game->getScreen()->setPalette(pal2, 0, 256, true);
443 			_game->getScreen()->flip();
444 			SDL_Delay(45);
445 		}
446 		_game->getScreen()->clear();
447 		_game->getScreen()->flip();
448 		Options::musicVolume = _oldMusic;
449 		Options::soundVolume = _oldSound;
450 		_game->setVolume(Options::soundVolume, Options::musicVolume, Options::uiVolume);
451 
452 #ifndef __NO_MUSIC
453 		Sound::stop();
454 		Music::stop();
455 #endif
456 	}
457 	Screen::updateScale(Options::geoscapeScale, Options::geoscapeScale, Options::baseXGeoscape, Options::baseYGeoscape, true);
458 	_game->getScreen()->resetDisplay(false);
459 	_game->setState(new MainMenuState(_game));
460 }
461 
462 }
463 
464