561 lines
33 KiB
C++
561 lines
33 KiB
C++
//
|
|
// Copyright 2020 Electronic Arts Inc.
|
|
//
|
|
// TiberianDawn.DLL and RedAlert.dll and corresponding source code 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.
|
|
|
|
// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed
|
|
// in the hope that it will be useful, but with permitted additional restrictions
|
|
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
|
|
// distributed with this program. You should have received a copy of the
|
|
// GNU General Public License along with permitted additional restrictions
|
|
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
|
|
|
|
/* $Header: F:\projects\c&c\vcs\code\theme.cpv 2.18 16 Oct 1995 16:51:10 JOE_BOSTIC $ */
|
|
/***********************************************************************************************
|
|
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
|
|
***********************************************************************************************
|
|
* *
|
|
* Project Name : Command & Conquer *
|
|
* *
|
|
* File Name : THEME.CPP *
|
|
* *
|
|
* Programmer : Joe L. Bostic *
|
|
* *
|
|
* Start Date : August 14, 1994 *
|
|
* *
|
|
* Last Update : May 29, 1995 [JLB] *
|
|
* *
|
|
*---------------------------------------------------------------------------------------------*
|
|
* Functions: *
|
|
* ThemeClass::Scan -- Scans all scores for availability. *
|
|
* ThemeClass::AI -- Process the theme engine and restart songs. *
|
|
* ThemeClass::Base_Name -- Fetches the base filename for the theme specified. *
|
|
* ThemeClass::From_Name -- Determines theme number from specified name. *
|
|
* ThemeClass::Full_Name -- Retrieves the full score name. *
|
|
* ThemeClass::Is_Allowed -- Checks to see if the specified theme is legal. *
|
|
* ThemeClass::Next_Song -- Calculates the next song number to play. *
|
|
* ThemeClass::Play_Song -- Starts the specified song play NOW. *
|
|
* ThemeClass::Queue_Song -- Queues the song to the play queue. *
|
|
* ThemeClass::Still_Playing -- Determines if music is still playing. *
|
|
* ThemeClass::Stop -- Stops the current theme from playing. *
|
|
* ThemeClass::ThemeClass -- Default constructor for the theme manager class. *
|
|
* ThemeClass::Theme_File_Name -- Constructs a filename for the specified theme. *
|
|
* ThemeClass::Track_Length -- Caclulates the length of the song (in seconds). *
|
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
#include "function.h"
|
|
|
|
#include "theme.h"
|
|
|
|
|
|
/*
|
|
** These are the actual filename list for the theme sample files.
|
|
*/
|
|
ThemeClass::ThemeControl ThemeClass::_themes[THEME_COUNT] = {
|
|
{"AIRSTRIK", TXT_THEME_AIRSTRIKE, 0, 200, false, false,false,true},
|
|
{"80MX226M", TXT_THEME_80MX, 0, 248, false, false,false,true},
|
|
{"CHRG226M", TXT_THEME_CHRG, 0, 256, true, false,false,true},
|
|
{"CREP226M", TXT_THEME_CREP, 0, 222, true, false,false,true},
|
|
{"DRIL226M", TXT_THEME_DRIL, 0, 272, true, false,false,true},
|
|
{"DRON226M", TXT_THEME_DRON, 0, 275, true, false,false,true},
|
|
{"FIST226M", TXT_THEME_FIST, 0, 212, true, false,false,true},
|
|
{"RECN226M", TXT_THEME_RECON, 0, 261, true, false,false,true},
|
|
{"VOIC226M", TXT_THEME_VOICE, 0, 306, true, false,false,true},
|
|
{"HEAVYG", TXT_THEME_HEAVYG, 0, 180, true, false,false,true},
|
|
{"J1", TXT_THEME_J1, 4, 187, true, false,false,true},
|
|
// {"J1", TXT_THEME_J1, 4, 187, false, false,false,true},
|
|
{"JDI_V2", TXT_THEME_JDI_V2, 5, 183, true, false,false,true},
|
|
{"RADIO", TXT_THEME_RADIO, 6, 183, true, false,false,true},
|
|
{"RAIN", TXT_THEME_RAIN, 7, 156, true, false,false,true},
|
|
{"AOI", TXT_THEME_AOI, 0, 168, true, true, false,true},
|
|
{"CCTHANG", TXT_THEME_CCTHANG, 12, 193, true, false,false,true},
|
|
{"DIE", TXT_THEME_DIE, 11, 162, false, false,false,true},
|
|
{"FWP", TXT_THEME_FWP, 10, 53, true, false,false,true},
|
|
{"IND", TXT_THEME_IND, 1, 175, true, false,false,true},
|
|
{"IND2", TXT_THEME_IND2, 1, 38, true, false,false,true},
|
|
{"JUSTDOIT", TXT_THEME_JUSTDOIT, 9, 142, true, false,false,true},
|
|
{"LINEFIRE", TXT_THEME_LINEFIRE, 8, 125, true, false,false,true},
|
|
{"MARCH", TXT_THEME_MARCH, 7, 157, true, false,false,true},
|
|
{"TARGET", TXT_THEME_TARGET, 0, 173, true, false,false,true},
|
|
{"NOMERCY", TXT_THEME_NOMERCY, 2, 204, true, false,false,true},
|
|
{"OTP", TXT_THEME_OTP, 3, 182, true, false,false,true},
|
|
{"PRP", TXT_THEME_PRP, 4, 211, true, false,false,true},
|
|
{"ROUT", TXT_THEME_ROUT, 12, 121, false, true, false,true},
|
|
{"HEART", TXT_THEME_HEART, 5, 206, false, true, false,true},
|
|
{"STOPTHEM", TXT_THEME_STOPTHEM, 0, 190, true, false,false,true},
|
|
{"TROUBLE", TXT_THEME_TROUBLE, 6, 191, true, true, false,true},
|
|
{"WARFARE", TXT_THEME_WARFARE, 0, 182, true, false,false,true},
|
|
{"BEFEARED", TXT_THEME_BEFEARED, 13, 164, false, true, false,true},
|
|
{"I_AM", TXT_THEME_IAM, 6, 161, false, false,false,true},
|
|
{"WIN1", TXT_THEME_WIN1, 0, 41, false, true, true,true},
|
|
{"MAP1", TXT_THEME_WIN1, 0, 61, false, false,true,true},
|
|
{"VALKYRIE", TXT_THEME_VALK, 0, 306, false, false,true,true},
|
|
};
|
|
|
|
|
|
/***********************************************************************************************
|
|
* ThemeClass::Base_Name -- Fetches the base filename for the theme specified. *
|
|
* *
|
|
* This routine is used to retrieve a pointer to the base filename for the theme *
|
|
* specified. *
|
|
* *
|
|
* INPUT: theme -- The theme number to convert into a base filename. *
|
|
* *
|
|
* OUTPUT: Returns with a pointer to the base filename for the theme specified. If the *
|
|
* theme number is invalid, then a pointer to "No Theme" is returned instead. *
|
|
* *
|
|
* WARNINGS: none *
|
|
* *
|
|
* HISTORY: *
|
|
* 05/29/1995 JLB : Created. *
|
|
*=============================================================================================*/
|
|
char const * ThemeClass::Base_Name(ThemeType theme) const
|
|
{
|
|
if (theme != THEME_NONE) {
|
|
return(_themes[theme].Name);
|
|
}
|
|
return("No theme");
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* ThemeClass::ThemeClass -- Default constructor for the theme manager class. *
|
|
* *
|
|
* This is the default constructor for the theme class object. *
|
|
* *
|
|
* INPUT: none *
|
|
* *
|
|
* OUTPUT: none *
|
|
* *
|
|
* WARNINGS: none *
|
|
* *
|
|
* HISTORY: *
|
|
* 01/16/1995 JLB : Created. *
|
|
*=============================================================================================*/
|
|
ThemeClass::ThemeClass(void)
|
|
{
|
|
Current = -1;
|
|
Score = THEME_NONE;
|
|
Pending = THEME_NONE;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* ThemeClass::Full_Name -- Retrieves the full score name. *
|
|
* *
|
|
* This routine will fetch and return with a pointer to the full name of the theme *
|
|
* specified. *
|
|
* *
|
|
* INPUT: theme -- The theme to fetch the full name for. *
|
|
* *
|
|
* OUTPUT: Returns with a pointer to the full name for this score. This pointer may point to *
|
|
* EMS memory. *
|
|
* *
|
|
* WARNINGS: none *
|
|
* *
|
|
* HISTORY: *
|
|
* 01/16/1995 JLB : Created. *
|
|
*=============================================================================================*/
|
|
char const * ThemeClass::Full_Name(ThemeType theme) const
|
|
{
|
|
if (theme != THEME_NONE) {
|
|
return(Text_String(_themes[theme].Fullname));
|
|
}
|
|
return(NULL);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* ThemeClass::AI -- Process the theme engine and restart songs. *
|
|
* *
|
|
* This is a maintenance function that will restart an appropriate theme if the current one *
|
|
* has finished. This routine should be called frequently. *
|
|
* *
|
|
* INPUT: none *
|
|
* *
|
|
* OUTPUT: none *
|
|
* *
|
|
* WARNINGS: none *
|
|
* *
|
|
* HISTORY: *
|
|
* 09/08/1994 JLB : Created. *
|
|
* 01/23/1995 JLB : Picks new song just as it is about to play it. *
|
|
*=============================================================================================*/
|
|
void ThemeClass::AI(void)
|
|
{
|
|
if (SampleType && !Debug_Quiet) {
|
|
if (ScoresPresent && Options.ScoreVolume && !Still_Playing() && Pending != THEME_NONE) {
|
|
|
|
/*
|
|
** If the pending song needs to be picked, then pick it now.
|
|
*/
|
|
if (Pending == THEME_PICK_ANOTHER) {
|
|
Pending = Next_Song(Score);
|
|
}
|
|
|
|
/*
|
|
** Start the song playing and then flag it so that a new song will
|
|
** be picked when this one ends.
|
|
*/
|
|
Play_Song(Pending);
|
|
Pending = THEME_PICK_ANOTHER;
|
|
}
|
|
Sound_Callback();
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* ThemeClass::Next_Song -- Calculates the next song number to play. *
|
|
* *
|
|
* use this routine to figure out what song number to play. It examines the option settings *
|
|
* for repeat and shuffle so that it can return the correct value. *
|
|
* *
|
|
* INPUT: theme -- The origin (last) index. The new value is related to this for all but *
|
|
* the shuffling method of play. *
|
|
* *
|
|
* OUTPUT: Returns with the song number for the next song to play. *
|
|
* *
|
|
* WARNINGS: none *
|
|
* *
|
|
* HISTORY: *
|
|
* 01/16/1995 JLB : Created. *
|
|
* 01/19/1995 JLB : Will not play the same song twice when in shuffle mode. *
|
|
*=============================================================================================*/
|
|
ThemeType ThemeClass::Next_Song(ThemeType theme)
|
|
{
|
|
if (theme == THEME_NONE) {
|
|
theme = Next_Song(THEME_PICK_ANOTHER);
|
|
} else {
|
|
if (theme == THEME_PICK_ANOTHER || (!_themes[theme].Repeat && !Options.IsScoreRepeat)) {
|
|
if (Options.IsScoreShuffle) {
|
|
|
|
/*
|
|
** Shuffle the theme, but never pick the same theme that was just
|
|
** playing.
|
|
*/
|
|
ThemeType newtheme;
|
|
do {
|
|
newtheme = Sim_Random_Pick(THEME_FIRST, THEME_LAST);
|
|
} while(newtheme == theme || !Is_Allowed(newtheme));
|
|
theme = newtheme;
|
|
|
|
} else {
|
|
|
|
/*
|
|
** Sequential score playing.
|
|
*/
|
|
do {
|
|
theme++;
|
|
if (theme > THEME_LAST) {
|
|
theme = THEME_FIRST;
|
|
}
|
|
} while(!Is_Allowed(theme));
|
|
}
|
|
}
|
|
}
|
|
return(theme);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* ThemeClass::Queue_Song -- Queues the song to the play queue. *
|
|
* *
|
|
* This routine will cause the current song to fade and the specified song to start. This *
|
|
* is the normal and friendly method of changing the current song. *
|
|
* *
|
|
* INPUT: theme -- The song to start playing. If -1 is pssed in, then just the current song *
|
|
* is faded. *
|
|
* *
|
|
* OUTPUT: none *
|
|
* *
|
|
* WARNINGS: none *
|
|
* *
|
|
* HISTORY: *
|
|
* 01/16/1995 JLB : Created. *
|
|
*=============================================================================================*/
|
|
void ThemeClass::Queue_Song(ThemeType theme)
|
|
{
|
|
if (ScoresPresent && SampleType && !Debug_Quiet && (Pending == THEME_NONE || Pending == THEME_PICK_ANOTHER)) {
|
|
if (!Options.ScoreVolume && theme != THEME_NONE) return;
|
|
|
|
Pending = theme;
|
|
Fade_Sample(Current, THEME_DELAY);
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* ThemeClass::Play_Song -- Starts the specified song play NOW. *
|
|
* *
|
|
* This routine is used to start the specified theme playing right now. If there is already *
|
|
* a theme playing, it is cut short so that this one may start. *
|
|
* *
|
|
* INPUT: theme -- The theme number to start playing. *
|
|
* *
|
|
* OUTPUT: Returns with the sample play handle. *
|
|
* *
|
|
* WARNINGS: This cuts off any current song in a abrubt manner. Only use this routine when *
|
|
* necessary. *
|
|
* *
|
|
* HISTORY: *
|
|
* 01/16/1995 JLB : Created. *
|
|
*=============================================================================================*/
|
|
int ThemeClass::Play_Song(ThemeType theme)
|
|
{
|
|
if (ScoresPresent && SampleType && !Debug_Quiet && Options.ScoreVolume) {
|
|
Stop();
|
|
Score = theme;
|
|
if (theme >= THEME_FIRST) {
|
|
|
|
#ifdef DEMO
|
|
if (_themes[theme].Scenario != 99) {
|
|
CCFileClass file(Theme_File_Name(theme));
|
|
if (file.Is_Available()) {
|
|
Current = File_Stream_Sample_Vol(Theme_File_Name(theme), 0xFF, true);
|
|
} else {
|
|
Current = -1;
|
|
}
|
|
} else {
|
|
Current = -1;
|
|
}
|
|
#else
|
|
Current = File_Stream_Sample_Vol(Theme_File_Name(theme), 0xFF, true);
|
|
#endif
|
|
}
|
|
}
|
|
return(Current);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* ThemeClass::Theme_File_Name -- Constructs a filename for the specified theme. *
|
|
* *
|
|
* This routine will construct (into a static buffer) a filename that matches the theme *
|
|
* number specified. This constructed filename is returned as a pointer. The filename will *
|
|
* remain valid until the next call to this routine. *
|
|
* *
|
|
* INPUT: theme -- The theme number to convert to a filename. *
|
|
* *
|
|
* OUTPUT: Returns with a pointer to the constructed filename for the specified theme number. *
|
|
* *
|
|
* WARNINGS: none *
|
|
* *
|
|
* HISTORY: *
|
|
* 01/16/1995 JLB : Created. *
|
|
* 05/09/1995 JLB : Theme variation support. *
|
|
*=============================================================================================*/
|
|
char const * ThemeClass::Theme_File_Name(ThemeType theme)
|
|
{
|
|
static char name[_MAX_FNAME+_MAX_EXT];
|
|
|
|
if (_themes[theme].Variation && Special.IsVariation) {
|
|
_makepath(name, NULL, NULL, _themes[theme].Name, ".VAR");
|
|
CCFileClass file(name);
|
|
if (file.Is_Available()) {
|
|
return((char const *)(&name[0]));
|
|
}
|
|
}
|
|
_makepath(name, NULL, NULL, _themes[theme].Name, ".AUD");
|
|
return((char const *)(&name[0]));
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* ThemeClass::Track_Length -- Caclulates the length of the song (in seconds). *
|
|
* *
|
|
* Use this routine to calculate the length of the song. The length is determined by *
|
|
* reading the header of the song and dividing the sample rate into the sample length. *
|
|
* *
|
|
* INPUT: theme -- The song number to examine to find its length. *
|
|
* *
|
|
* OUTPUT: Returns with the length of the specified theme. This length is in the form of *
|
|
* seconds. *
|
|
* *
|
|
* WARNINGS: This routine goes to disk to fetch this information. Don't call frivolously. *
|
|
* *
|
|
* HISTORY: *
|
|
* 01/16/1995 JLB : Created. *
|
|
*=============================================================================================*/
|
|
int ThemeClass::Track_Length(ThemeType theme)
|
|
{
|
|
if ((unsigned)theme < THEME_COUNT) {
|
|
return(_themes[theme].Duration);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* ThemeClass::Stop -- Stops the current theme from playing. *
|
|
* *
|
|
* Use this routine to stop the current theme. After this routine is called, no more music *
|
|
* will play until the Start() function is called. *
|
|
* *
|
|
* INPUT: none *
|
|
* *
|
|
* OUTPUT: none *
|
|
* *
|
|
* WARNINGS: none *
|
|
* *
|
|
* HISTORY: *
|
|
* 09/08/1994 JLB : Created. *
|
|
*=============================================================================================*/
|
|
void ThemeClass::Stop(void)
|
|
{
|
|
if (ScoresPresent && SampleType && !Debug_Quiet) {
|
|
if (Current != -1) {
|
|
Stop_Sample(Current);
|
|
Current = -1;
|
|
Score = THEME_NONE;
|
|
Pending = THEME_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* ThemeClass::Still_Playing -- Determines if music is still playing. *
|
|
* *
|
|
* Use this routine to determine if music is still playing. *
|
|
* *
|
|
* INPUT: none *
|
|
* *
|
|
* OUTPUT: bool; Is the music still audible? *
|
|
* *
|
|
* WARNINGS: none *
|
|
* *
|
|
* HISTORY: *
|
|
* 12/20/1994 JLB : Created. *
|
|
*=============================================================================================*/
|
|
int ThemeClass::Still_Playing(void)
|
|
{
|
|
if (ScoresPresent && SampleType && Current != -1 && !Debug_Quiet) {
|
|
return(Sample_Status(Current));
|
|
}
|
|
return(false);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* ThemeClass::Is_Allowed -- Checks to see if the specified theme is legal. *
|
|
* *
|
|
* Use this routine to determine if a theme is allowed to be played. A theme is not allowed *
|
|
* if the scenario is too early for that score, or the score only is allowed in special *
|
|
* cases. *
|
|
* *
|
|
* INPUT: index -- The score the check to see if it is allowed to play. *
|
|
* *
|
|
* OUTPUT: Is the specified score allowed to play in the normal score playlist? *
|
|
* *
|
|
* WARNINGS: none *
|
|
* *
|
|
* HISTORY: *
|
|
* 05/09/1995 JLB : Created. *
|
|
*=============================================================================================*/
|
|
bool ThemeClass::Is_Allowed(ThemeType index) const
|
|
{
|
|
#ifdef DEMO
|
|
char buffer[128];
|
|
|
|
sprintf(buffer, "%s.AUD", Base_Name(index));
|
|
CCFileClass file(buffer);
|
|
if (_themes[index].Scenario == 99 || !file.Is_Available()) {
|
|
_themes[index].Scenario = 99;
|
|
return(false);
|
|
}
|
|
#endif
|
|
|
|
return(
|
|
_themes[index].Available &&
|
|
(_themes[index].Normal ||
|
|
// (index == THEME_MAP1 && ScenarioInit) ||
|
|
((Special.IsVariation && _themes[index].Variation && index != THEME_WIN1) &&
|
|
#ifndef DEMO
|
|
(GameToPlay != GAME_NORMAL || _themes[index].Scenario <= (int)Scenario) &&
|
|
#endif
|
|
(index != THEME_J1 || Special.IsJurassic))));
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* ThemeClass::From_Name -- Determines theme number from specified name. *
|
|
* *
|
|
* Use this routine to convert a name (either the base filename of the theme, or a partial *
|
|
* substring of the full name) into the matching ThemeType value. Typical use of this is *
|
|
* when parsing the INI file for theme control values. *
|
|
* *
|
|
* INPUT: name -- Pointer to base filename of theme or a partial substring of the full *
|
|
* theme name. *
|
|
* *
|
|
* OUTPUT: Returns with the matching theme number. If no match could be found, then *
|
|
* THEME_NONE is returned. *
|
|
* *
|
|
* WARNINGS: If a filename is specified the comparison is case insensitive. When scanning *
|
|
* the full theme name, the comparison is case sensitive. *
|
|
* *
|
|
* HISTORY: *
|
|
* 05/29/1995 JLB : Created. *
|
|
*=============================================================================================*/
|
|
ThemeType ThemeClass::From_Name(char const * name)
|
|
{
|
|
if (name && strlen(name) > 0) {
|
|
/*
|
|
** First search for an exact name match with the filename
|
|
** of the theme. This is guaranteed to be unique.
|
|
*/
|
|
ThemeType theme;
|
|
for (theme = THEME_FIRST; theme < THEME_COUNT; theme++) {
|
|
if (stricmp(_themes[theme].Name, name) == 0) {
|
|
return(theme);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** If the filename scan failed to find a match, then scan for
|
|
** a substring within the full name of the score. This might
|
|
** yeild a match, but is not guaranteed to be unique.
|
|
*/
|
|
for (theme = THEME_FIRST; theme < THEME_COUNT; theme++) {
|
|
if (strstr(Text_String(_themes[theme].Fullname), name) != NULL) {
|
|
return(theme);
|
|
}
|
|
}
|
|
}
|
|
|
|
return(THEME_NONE);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* ThemeClass::Scan -- Scans all scores for availability. *
|
|
* *
|
|
* This routine should be called whenever a score mixfile is registered. It will scan *
|
|
* to see if any score is unavailable. If this is the case, then the score will be so *
|
|
* flagged in order not to appear on the play list. This condition is likely to occur *
|
|
* when expansion mission disks contain a different score mix than the release version. *
|
|
* *
|
|
* INPUT: none *
|
|
* *
|
|
* OUTPUT: none *
|
|
* *
|
|
* WARNINGS: none *
|
|
* *
|
|
* HISTORY: *
|
|
* 01/04/1996 JLB : Created. *
|
|
*=============================================================================================*/
|
|
void ThemeClass::Scan(void)
|
|
{
|
|
for (ThemeType theme = THEME_FIRST; theme < THEME_COUNT; theme++) {
|
|
// if (theme == THEME_J1 && !Special.IsJurassic) {
|
|
// _themes[theme].Available = false;
|
|
// } else {
|
|
_themes[theme].Available = CCFileClass(Theme_File_Name(theme)).Is_Available();
|
|
// }
|
|
}
|
|
}
|
|
|