/*
* Copyright 2010-2016 OpenXcom Developers.
*
* This file is part of OpenXcom.
*
* OpenXcom is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenXcom is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenXcom. If not, see <http://www.gnu.org/licenses/>.
*/
#include "VideoState.h"
#include <algorithm>
#include <SDL_mixer.h>
#include "../Engine/Adlib/adlplayer.h"
#include "../Engine/Logger.h"
#include "../Engine/Game.h"
#include "../Engine/Options.h"
#include "../Engine/FlcPlayer.h"
#include "../Engine/CrossPlatform.h"
#include "../Engine/FileMap.h"
#include "../Engine/Screen.h"
#include "../Engine/Music.h"
#include "../Engine/Sound.h"
#include "../Mod/Mod.h"
#include "../Mod/RuleVideo.h"
#include "CutsceneState.h"
#include "../Interface/Cursor.h"
namespace OpenXcom
{
/**
* Initializes all the elements in the Intro screen.
* @param game Pointer to the core game.
* @param wasLetterBoxed Was the game letterboxed?
*/
VideoState::VideoState(const std::vector<std::string> *videos, const std::vector<std::string> *tracks, bool useUfoAudioSequence)
: _videos(videos), _tracks(tracks), _useUfoAudioSequence(useUfoAudioSequence)
{
}
/**
*
*/
VideoState::~VideoState()
{
}
typedef struct
{
std::string catFile;
int sound;
int volume;
} soundInFile;
// the pure MS-DOS experience
static soundInFile introCatOnlySounds[]=
{
{"INTRO.CAT", 0x0, 32},
{"INTRO.CAT", 0x1, 32},
{"INTRO.CAT", 0x2, 32},
{"INTRO.CAT", 0x3, 32},
{"INTRO.CAT", 0x4, 32},
{"INTRO.CAT", 0x5, 32},
{"INTRO.CAT", 0x6, 32},
{"INTRO.CAT", 0x7, 32},
{"INTRO.CAT", 0x8, 32},
{"INTRO.CAT", 0x9, 32},
{"INTRO.CAT", 0xa, 32},
{"INTRO.CAT", 0xb, 32},
{"INTRO.CAT", 0xc, 32},
{"INTRO.CAT", 0xd, 32},
{"INTRO.CAT", 0xe, 32},
{"INTRO.CAT", 0xf, 32},
{"INTRO.CAT", 0x10, 32},
{"INTRO.CAT", 0x11, 32},
{"INTRO.CAT", 0x12, 32},
{"INTRO.CAT", 0x13, 32},
{"INTRO.CAT", 0x14, 32},
{"INTRO.CAT", 0x15, 32},
{"INTRO.CAT", 0x16, 32},
{"INTRO.CAT", 0x17, 32},
{"INTRO.CAT", 0x18, 32},
{"INTRO.CAT", 0x18, 32}
};
static soundInFile sample3CatOnlySounds[]=
{
{"SAMPLE3.CAT", 24, 32}, // machine gun
{"SAMPLE3.CAT", 5, 32}, // plasma rifle
{"SAMPLE3.CAT", 23, 32}, // rifle
{"SAMPLE3.CAT", 6, 32}, // some kind of death noise, urgh?
{"SAMPLE3.CAT", 9, 64}, // mutdie
{"SAMPLE3.CAT", 7, 64}, // dying alien
{"SAMPLE3.CAT", 27, 64}, // another dying alien
{"SAMPLE3.CAT", 4, 32}, // ??? ship flying? alien screech?
{"SAMPLE3.CAT", 0x8, 32}, // fscream
{"SAMPLE3.CAT", 11, 32}, // alarm
{"SAMPLE3.CAT", 4, 32}, // gun spinning up?
{"INTRO.CAT", 0xb, 32}, // reload; this one's not even in sample3
{"SAMPLE3.CAT",19, 48}, // whoosh
{"INTRO.CAT", 0xd, 32}, // feet, also not in sample3
{"SAMPLE3.CAT", 2, 32}, // low pulsating hum
{"SAMPLE3.CAT", 30, 32}, // energise
{"SAMPLE3.CAT", 21, 32}, // hatch
{"SAMPLE3.CAT", 0, 64}, // phizz -- no equivalent in sample3.cat?
{"SAMPLE3.CAT", 13, 32}, // warning
{"SAMPLE3.CAT", 14, 32}, // detected
{"SAMPLE3.CAT", 19, 64}, // UFO flyby whoosh?
{"SAMPLE3.CAT", 3, 32}, // growl
{"SAMPLE3.CAT", 15, 128}, // voice
{"SAMPLE3.CAT", 12, 32}, // beep 1
{"SAMPLE3.CAT", 18, 32}, // takeoff
{"SAMPLE3.CAT", 20, 32} // another takeoff/landing sound?? if it exists?
};
// an attempt at a mix of (subjectively) the best sounds from the two versions
// difficult because we can't find a definitive map from old sequence numbers to SAMPLE3.CAT indexes
// probably only the Steam version of the game comes with both INTRO.CAT and SAMPLE3.CAT
static soundInFile hybridIntroSounds[]=
{
{"SAMPLE3.CAT", 24, 32}, // machine gun
{"SAMPLE3.CAT", 5, 32}, // plasma rifle
{"SAMPLE3.CAT", 23, 32}, // rifle
{"INTRO.CAT", 3, 32}, // some kind of death noise, urgh?
{"INTRO.CAT", 0x4, 64}, // mutdie
{"INTRO.CAT", 0x5, 64}, // dying alien
{"INTRO.CAT", 0x6, 64}, // another dying alien
{"INTRO.CAT", 0x7, 32}, // ??? ship flying? alien screech?
{"SAMPLE3.CAT", 0x8, 32}, // fscream
{"SAMPLE3.CAT", 11, 32}, // alarm
{"SAMPLE3.CAT", 4, 32}, // gun spinning up?
{"INTRO.CAT", 0xb, 32}, // reload; this one's not even in sample3
{"SAMPLE3.CAT",19, 48}, // whoosh
{"INTRO.CAT", 0xd, 32}, // feet, also not in sample3
{"INTRO.CAT", 0xe, 32}, // low pulsating hum
{"SAMPLE3.CAT", 30, 32}, // energise
{"SAMPLE3.CAT", 21, 32}, // hatch
{"INTRO.CAT", 0x11, 64}, // phizz
{"SAMPLE3.CAT", 13, 32}, // warning
{"SAMPLE3.CAT", 14, 32}, // detected
{"SAMPLE3.CAT", 19, 64}, // UFO flyby whoosh?
{"INTRO.CAT", 0x15, 32}, // growl
{"SAMPLE3.CAT", 15, 128}, // voice
{"SAMPLE3.CAT", 12, 32}, // beep 1
{"SAMPLE3.CAT", 18, 32}, // takeoff
{"SAMPLE3.CAT", 20, 32} // another takeoff/landing sound?? if it exists?
};
// sample3: 18 is takeoff, 20 is landing; 19 is flyby whoosh sound, not sure for which craft
static soundInFile *introSounds[] =
{
hybridIntroSounds,
introCatOnlySounds,
sample3CatOnlySounds,
0
};
typedef struct
{
int frameNumber;
int sound;
} introSoundEffect;
static introSoundEffect introSoundTrack[] =
{
{0, 0x200}, // inserting this to keep the code simple
{149, 0x11},
{173, 0x0C},
{183, 0x0E},
{205, 0x15},
{211, 0x201},
{211, 0x407},
{223, 0x7},
{250, 0x1},
{253, 0x1},
{255, 0x1},
{257, 0x1},
{260, 0x1},
{261, 0x3},
{262, 0x1},
{264, 0x1},
{268, 0x1},
{270, 0x1},
{272, 0x5},
{272, 0x1},
{274, 0x1},
{278, 0x1},
{280, 0x1},
{282, 0x8},
{282, 0x1},
{284, 0x1},
{286, 0x1},
{288, 0x1},
{290, 0x1},
{292, 0x6},
{292, 0x1},
{296, 0x1},
{298, 0x1},
{300, 0x1},
{302, 0x1},
{304, 0x1},
{306, 0x1},
{308, 0x1},
{310, 0x1},
{312, 0x1},
{378, 0x202},
{378, 0x9}, // alarm
{386, 0x9},
{393, 0x9},
{399, 0x17}, // bleeps
{433, 0x17},
{463, 0x12}, // warning
{477, 0x12},
{487, 0x13}, // ufo detected
{495, 0x16}, // voice
{501, 0x16},
{512, 0xd}, // feet -- not in original
{514, 0xd}, // feet -- not in original
{522, 0x0B}, // rifle grab
{523, 0xd}, // feet -- not in original
{525, 0xd}, // feet -- not in original
{534, 0x18},
{535, 0x405},
{560, 0x407},
{577, 0x14},
{582, 0x405},
// {582, 0x18}, // landing! correcting to landing sound!
{582, 0x19},
{613, 0x407},
{615, 0x10},
{635, 0x14},
{638, 0x14},
{639, 0x14},
{644, 0x2},
{646, 0x2},
{648, 0x2},
{650, 0x2},
{652, 0x2},
{654, 0x2},
{656, 0x2},
{658, 0x2},
{660, 0x2},
{662, 0x2},
{664, 0x2},
{666, 0x2},
{668, 0x401},
{681, 0x406},
{687, 0x402},
{689, 0x407},
{694, 0x0A},
{711, 0x407},
{711, 0x0},
{714, 0x0},
{716, 0x4},
{717, 0x0},
{720, 0x0},
{723, 0x0},
{726, 0x5},
{726, 0x0},
{729, 0x0},
{732, 0x0},
{735, 0x0},
{738, 0x0},
{741, 0x0},
{742, 0x6},
{744, 0x0},
{747, 0x0},
{750, 0x0},
{753, 0x0},
{756, 0x0},
{759, 0x0},
{762, 0x0},
{765, 0x0},
{768, 0x0},
{771, 0x0},
{774, 0x0},
{777, 0x0},
{780, 0x0},
{783, 0x0},
{786, 0x0},
{790, 0x15},
{790, 0x15},
{807, 0x2},
{810, 0x2},
{812, 0x2},
{814, 0x2},
{816, 0x0},
{819, 0x0},
{822, 0x0},
{824, 0x40A},
{824, 0x5},
{827, 0x6},
{835, 0x0F},
{841, 0x0F},
{845, 0x0F},
{855, 0x407},
{879, 0x0C},
{65535, 0x0FFFF}
};
static struct AudioSequence
{
Mod *mod;
Music *m;
Sound *s;
int trackPosition;
FlcPlayer *_flcPlayer;
AudioSequence(Mod *_mod, FlcPlayer *flcPlayer) : mod(_mod), m(0), s(0), trackPosition(0), _flcPlayer(flcPlayer)
{ }
void operator()()
{
while (_flcPlayer->getFrameCount() >= introSoundTrack[trackPosition].frameNumber)
{
int command = introSoundTrack[trackPosition].sound;
if (command & 0x200)
{
#ifndef __NO_MUSIC
switch(command)
{
case 0x200:
Log(LOG_DEBUG) << "Playing gmintro1";
m = mod->getMusic("GMINTRO1");
m->play(1);
break;
case 0x201:
Log(LOG_DEBUG) << "Playing gmintro2";
m = mod->getMusic("GMINTRO2");
m->play(1);
break;
case 0x202:
Log(LOG_DEBUG) << "Playing gmintro3";
m = mod->getMusic("GMINTRO3");
m->play(1);
//Mix_HookMusicFinished(_FlcPlayer::stop);
break;
}
#endif
}
else if (command & 0x400)
{
int newSpeed = (command & 0xff);
_flcPlayer->setHeaderSpeed(newSpeed);
Log(LOG_DEBUG) << "Frame delay now: " << newSpeed;
}
else if (command <= 0x19)
{
for (soundInFile **sounds = introSounds; *sounds; ++sounds) // try hybrid sound set, then intro.cat or sample3.cat alone
{
soundInFile *sf = (*sounds) + command;
int channel = trackPosition % 4; // use at most four channels to play sound effects
double ratio = (double)Options::soundVolume / MIX_MAX_VOLUME;
Log(LOG_DEBUG) << "playing: " << sf->catFile << ":" << sf->sound << " for index " << command;
s = mod->getSound(sf->catFile, sf->sound, false);
if (s)
{
s->play(channel);
Mix_Volume(channel, sf->volume * ratio);
break;
}
else Log(LOG_DEBUG) << "Couldn't play " << sf->catFile << ":" << sf->sound;
}
}
++trackPosition;
}
}
} *audioSequence = NULL;
static void audioHandler()
{
(*audioSequence)();
}
void VideoState::init()
{
State::init();
bool wasLetterboxed = CutsceneState::initDisplay();
bool ufoIntroSoundFileDosExists = false;
bool ufoIntroSoundFileWinExists = false;
int prevMusicVol = Options::musicVolume;
int prevSoundVol = Options::soundVolume;
if (_useUfoAudioSequence)
{
const std::set<std::string> &soundDir = FileMap::getVFolderContents("SOUND");
ufoIntroSoundFileDosExists = soundDir.end() != soundDir.find("intro.cat");
ufoIntroSoundFileWinExists = soundDir.end() != soundDir.find("sample3.cat");
if (!ufoIntroSoundFileDosExists && !ufoIntroSoundFileWinExists)
{
_useUfoAudioSequence = false;
}
else
{
// ensure user can hear both music and sound effects for the
// vanilla intro sequence
Options::musicVolume = Options::soundVolume = std::max(prevMusicVol, prevSoundVol);
_game->setVolume(Options::soundVolume, Options::musicVolume, -1);
}
}
_game->getCursor()->setVisible(false);
int dx = (Options::baseXResolution - Screen::ORIGINAL_WIDTH) / 2;
int dy = (Options::baseYResolution - Screen::ORIGINAL_HEIGHT) / 2;
// We can only do a fade out in 8bpp, otherwise instantly end it
bool fade = (_game->getScreen()->getSurface()->getSurface()->format->BitsPerPixel == 8);
const int FADE_DELAY = 45;
const int FADE_STEPS = 20;
FlcPlayer *flcPlayer = NULL;
size_t audioCounter = 0;
for (std::vector<std::string>::const_iterator it = _videos->begin(); it != _videos->end(); ++it)
{
bool useInternalAudio = true;
if (!_tracks->empty() && _tracks->size() > audioCounter && _game->getMod()->getMusic(_tracks->at(audioCounter)))
{
_game->getMod()->getMusic(_tracks->at(audioCounter))->play(0);
useInternalAudio = false;
}
audioCounter++;
const std::string& videoFileName = FileMap::getFilePath(*it);
if (!CrossPlatform::fileExists(videoFileName))
{
continue;
}
if (!flcPlayer)
{
flcPlayer = new FlcPlayer();
}
if (_useUfoAudioSequence)
{
audioSequence = new AudioSequence(_game->getMod(), flcPlayer);
}
flcPlayer->init(videoFileName.c_str(),
_useUfoAudioSequence ? &audioHandler : NULL,
_game, useInternalAudio, dx, dy);
flcPlayer->play(_useUfoAudioSequence);
if (_useUfoAudioSequence)
{
flcPlayer->delay(10000);
delete audioSequence;
audioSequence = NULL;
}
flcPlayer->deInit();
if (flcPlayer->wasSkipped())
{
fade = false;
break;
}
}
if (flcPlayer)
{
delete flcPlayer;
}
#ifndef __NO_MUSIC
// fade out!
if (fade)
{
Mix_FadeOutChannel(-1, FADE_DELAY * FADE_STEPS);
// SDL_Mixer has trouble with native midi and volume on windows,
// which is the most likely use case, so f@%# it.
if (Mix_GetMusicType(0) != MUS_MID)
{
Mix_FadeOutMusic(FADE_DELAY * FADE_STEPS);
func_fade();
}
else
{
Mix_HaltMusic();
}
}
else
{
Mix_HaltChannel(-1);
Mix_HaltMusic();
}
#endif
if (fade)
{
SDL_Color pal[256];
SDL_Color pal2[256];
memcpy(pal, _game->getScreen()->getPalette(), sizeof(SDL_Color) * 256);
for (int i = FADE_STEPS; i > 0; --i)
{
SDL_Event event;
if (SDL_PollEvent(&event) && event.type == SDL_KEYDOWN) break;
for (int color = 0; color < 256; ++color)
{
pal2[color].r = (((int)pal[color].r) * i) / 20;
pal2[color].g = (((int)pal[color].g) * i) / 20;
pal2[color].b = (((int)pal[color].b) * i) / 20;
pal2[color].unused = pal[color].unused;
}
_game->getScreen()->setPalette(pal2, 0, 256, true);
_game->getScreen()->flip();
SDL_Delay(FADE_DELAY);
}
}
_game->getScreen()->clear();
_game->getScreen()->flip();
if (_useUfoAudioSequence)
{
Options::musicVolume = prevMusicVol;
Options::soundVolume = prevSoundVol;
_game->setVolume(Options::soundVolume, Options::musicVolume, Options::uiVolume);
}
#ifndef __NO_MUSIC
Sound::stop();
Music::stop();
#endif
_game->getCursor()->setVisible(true);
CutsceneState::resetDisplay(wasLetterboxed);
_game->popState();
}
}
↑ V807 Decreased performance. Consider creating a pointer to avoid using the '_game->getScreen()' expression repeatedly.
↑ V809 Verifying that a pointer value is not NULL is not required. The 'if (flcPlayer)' check can be removed.