2020-07-07 11:19:09 +00:00
/*
* * mapinfo . cpp
* *
* * Map record management
* *
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* * Copyright 2020 Christoph Oelckers
* * All rights reserved .
* *
* * Redistribution and use in source and binary forms , with or without
* * modification , are permitted provided that the following conditions
* * are met :
* *
* * 1. Redistributions of source code must retain the above copyright
* * notice , this list of conditions and the following disclaimer .
* * 2. Redistributions in binary form must reproduce the above copyright
* * notice , this list of conditions and the following disclaimer in the
* * documentation and / or other materials provided with the distribution .
* * 3. The name of the author may not be used to endorse or promote products
* * derived from this software without specific prior written permission .
* *
* * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ` ` AS IS ' ' AND ANY EXPRESS OR
* * IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES
* * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED .
* * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT , INDIRECT ,
* * INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT
* * NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE ,
* * DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY
* * THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF
* * THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* *
*/
2020-10-13 22:37:36 +00:00
# include "c_dispatch.h"
2020-07-07 11:19:09 +00:00
# include "mapinfo.h"
2020-07-07 18:27:21 +00:00
# include "raze_music.h"
2020-09-03 21:10:28 +00:00
# include "filesystem.h"
# include "printf.h"
2021-06-24 08:49:26 +00:00
# include "gamecontrol.h"
2021-04-08 14:35:26 +00:00
# include "raze_sound.h"
2021-09-05 18:35:10 +00:00
# include "zstring.h"
2023-10-01 09:59:35 +00:00
# include "statistics.h"
2020-07-07 11:19:09 +00:00
2020-10-04 16:31:48 +00:00
FString gSkillNames [ MAXSKILLS ] ;
int gDefaultVolume = 0 , gDefaultSkill = 1 ;
2021-04-25 23:45:16 +00:00
GlobalCutscenes globalCutscenes ;
2021-05-01 20:52:28 +00:00
TArray < ClusterDef > clusters ;
TArray < VolumeRecord > volumes ;
TArray < TPointer < MapRecord > > mapList ; // must be allocated as pointers because it can whack the currentlLevel pointer if this was a flat array.
2021-09-05 18:35:10 +00:00
static TMap < FString , FString > musicReplacements ;
2023-09-30 21:06:27 +00:00
MapRecord * currentLevel ; // level that is currently played.
MapLocals Level ;
2020-07-07 11:19:09 +00:00
2020-10-13 22:37:36 +00:00
CCMD ( listmaps )
{
2021-04-25 23:45:16 +00:00
for ( auto & map : mapList )
2020-10-13 22:37:36 +00:00
{
2021-04-28 20:20:03 +00:00
int lump = fileSystem . FindFile ( map - > fileName ) ;
2020-10-13 23:13:36 +00:00
if ( lump > = 0 )
{
int rfnum = fileSystem . GetFileContainer ( lump ) ;
2021-05-01 20:52:28 +00:00
Printf ( " %s - %s (%s) \n " , map - > LabelName ( ) , map - > DisplayName ( ) , fileSystem . GetResourceFileName ( rfnum ) ) ;
2020-10-13 23:13:36 +00:00
}
else
{
2021-04-28 20:20:03 +00:00
Printf ( " %s - %s (defined but does not exist) \n " , map - > fileName . GetChars ( ) , map - > DisplayName ( ) ) ;
2020-10-13 23:13:36 +00:00
}
2020-10-13 22:37:36 +00:00
}
}
2020-07-07 11:19:09 +00:00
MapRecord * FindMapByName ( const char * nm )
{
2021-07-25 22:58:00 +00:00
if ( ! nm | | ! * nm ) return nullptr ;
2021-04-25 23:45:16 +00:00
for ( auto & map : mapList )
2020-07-07 11:19:09 +00:00
{
2021-04-28 20:20:03 +00:00
if ( map - > labelName . CompareNoCase ( nm ) = = 0 )
2020-07-07 11:19:09 +00:00
{
2021-04-28 20:20:03 +00:00
return map . Data ( ) ;
2020-07-07 11:19:09 +00:00
}
}
2022-12-10 09:20:01 +00:00
// retry with the path being removed.
FString s = ExtractFileBase ( nm ) ;
for ( auto & map : mapList )
{
if ( map - > labelName . CompareNoCase ( s ) = = 0 )
{
return map . Data ( ) ;
}
}
2020-07-07 11:19:09 +00:00
return nullptr ;
}
MapRecord * FindMapByLevelNum ( int num )
{
2021-04-25 23:45:16 +00:00
for ( auto & map : mapList )
2020-07-07 11:19:09 +00:00
{
2021-04-28 20:20:03 +00:00
if ( map - > levelNumber = = num )
2020-07-07 11:19:09 +00:00
{
2021-04-28 20:20:03 +00:00
return map . Data ( ) ;
2020-07-07 11:19:09 +00:00
}
}
return nullptr ;
}
2021-05-01 22:35:56 +00:00
VolumeRecord * FindVolume ( int index )
{
for ( auto & vol : volumes )
{
if ( vol . index = = index ) return & vol ;
}
return nullptr ;
}
ClusterDef * FindCluster ( int index )
{
for ( auto & vol : clusters )
{
if ( vol . index = = index ) return & vol ;
}
return nullptr ;
}
ClusterDef * AllocateCluster ( )
{
return & clusters [ clusters . Reserve ( 1 ) ] ;
}
VolumeRecord * AllocateVolume ( )
{
return & volumes [ volumes . Reserve ( 1 ) ] ;
}
2021-05-02 07:08:57 +00:00
MapRecord * FindMapByIndexOnly ( int cluster , int num )
{
2021-05-03 21:00:24 +00:00
int levelnum = makelevelnum ( cluster , num ) ;
2021-05-02 07:08:57 +00:00
for ( auto & map : mapList )
{
2021-05-03 21:00:24 +00:00
if ( map - > levelNumber = = levelnum ) return map . Data ( ) ;
2021-05-02 07:08:57 +00:00
}
return nullptr ;
}
MapRecord * FindMapByIndex ( int cluster , int num )
{
auto map = FindMapByLevelNum ( num ) ;
2021-05-03 21:00:24 +00:00
if ( ! map & & num < 1000 ) map = FindMapByLevelNum ( makelevelnum ( cluster , num ) ) ;
2021-05-02 07:08:57 +00:00
return map ;
}
MapRecord * FindNextMap ( MapRecord * thismap )
{
MapRecord * next = nullptr ;
if ( ! thismap - > NextMap . Compare ( " - " ) ) return nullptr ; // '-' means to forcibly end the game here.
if ( thismap - > NextMap . IsNotEmpty ( ) ) next = FindMapByName ( thismap - > NextMap ) ;
2021-05-02 08:35:43 +00:00
if ( ! next ) next = FindMapByLevelNum ( thismap - > levelNumber + 1 ) ;
2021-05-02 07:08:57 +00:00
return next ;
}
MapRecord * FindNextSecretMap ( MapRecord * thismap )
{
MapRecord * next = nullptr ;
if ( ! thismap - > NextSecret . Compare ( " - " ) ) return nullptr ; // '-' means to forcibly end the game here.
if ( thismap - > NextSecret . IsNotEmpty ( ) ) next = FindMapByName ( thismap - > NextSecret ) ;
return next ? next : FindNextMap ( thismap ) ;
}
2021-09-05 18:35:10 +00:00
void SetMusicReplacement ( const char * mapname , const char * music )
{
musicReplacements [ mapname ] = music ;
}
void ReplaceMusics ( bool namehack )
{
TMap < FString , FString > : : Iterator it ( musicReplacements ) ;
TMap < FString , FString > : : Pair * pair ;
while ( it . NextPair ( pair ) )
{
FString mapname = pair - > Key ;
FString music = pair - > Value ;
SetMusicForMap ( mapname , music , namehack ) ;
}
musicReplacements . Clear ( ) ;
}
2021-05-02 07:08:57 +00:00
2020-07-07 18:27:21 +00:00
bool SetMusicForMap ( const char * mapname , const char * music , bool namehack )
2020-07-07 11:19:09 +00:00
{
static const char * specials [ ] = { " intro " , " briefing " , " loading " } ;
2021-05-11 23:50:41 +00:00
for ( unsigned i = 0 ; i < 3 ; i + + )
2020-07-07 11:19:09 +00:00
{
if ( ! stricmp ( mapname , specials [ i ] ) )
{
2021-04-08 14:35:26 +00:00
if ( specialmusic . Size ( ) < = i ) specialmusic . Resize ( i + 1 ) ;
specialmusic [ i ] = music ;
2020-07-07 11:19:09 +00:00
return true ;
}
}
2020-07-07 18:27:21 +00:00
auto index = FindMapByName ( mapname ) ;
2020-07-07 11:19:09 +00:00
// This is for the DEFS parser's MUSIC command which never bothered to check for the real map name.
2020-07-07 18:27:21 +00:00
if ( index = = nullptr & & namehack )
2020-07-07 11:19:09 +00:00
{
int lev , ep ;
2022-08-04 17:13:55 +00:00
int8_t b1 , b2 ;
2020-07-07 11:19:09 +00:00
int numMatches = sscanf ( mapname , " %c%d%c%d " , & b1 , & ep , & b2 , & lev ) ;
if ( numMatches ! = 4 | | toupper ( b1 ) ! = ' E ' | | toupper ( b2 ) ! = ' L ' )
return false ;
2021-05-02 08:35:43 +00:00
index = FindMapByIndexOnly ( ep , lev ) ;
2020-07-07 11:19:09 +00:00
}
2020-07-07 18:27:21 +00:00
if ( index ! = nullptr )
2020-07-07 11:19:09 +00:00
{
2020-07-07 18:27:21 +00:00
index - > music = music ;
2020-07-07 11:19:09 +00:00
return true ;
}
2021-09-05 18:35:10 +00:00
DPrintf ( DMSG_WARNING , " Could not replace %s music with %s \n " , mapname , music ) ;
2020-07-07 11:19:09 +00:00
return false ;
}
2020-07-07 18:27:21 +00:00
MapRecord * AllocateMap ( )
2020-07-07 11:19:09 +00:00
{
2021-04-28 20:20:03 +00:00
auto & p = mapList [ mapList . Reserve ( 1 ) ] ;
p . Alloc ( ) ;
return p . Data ( ) ;
2020-07-07 11:19:09 +00:00
}
2020-07-07 18:27:21 +00:00
MapRecord * SetupUserMap ( const char * boardfilename , const char * defaultmusic )
2020-07-07 11:19:09 +00:00
{
2021-06-24 08:49:26 +00:00
if ( g_gameType & GAMEFLAG_SHAREWARE )
{
Printf ( PRINT_BOLD , " Cannot use user maps in shareware. \n " ) ;
return nullptr ;
}
2021-04-25 23:45:16 +00:00
for ( auto & map : mapList )
2020-07-07 11:19:09 +00:00
{
2021-04-28 20:20:03 +00:00
if ( map - > fileName . CompareNoCase ( boardfilename ) = = 0 )
2020-07-07 11:19:09 +00:00
{
2021-04-28 20:20:03 +00:00
return map . Data ( ) ;
2020-07-07 11:19:09 +00:00
}
}
2020-09-03 21:10:28 +00:00
if ( ! fileSystem . FileExists ( boardfilename ) )
{
Printf ( TEXTCOLOR_RED " map: file \" %s \" not found. \n " , boardfilename ) ;
return nullptr ;
}
2020-07-07 18:27:21 +00:00
auto map = AllocateMap ( ) ;
2020-07-07 11:19:09 +00:00
map - > name = " " ;
map - > SetFileName ( boardfilename ) ;
map - > flags = MI_USERMAP | MI_FORCEEOG ;
2021-12-26 12:17:10 +00:00
int lookup = LookupMusic ( boardfilename , true ) ;
if ( lookup > = 0 ) map - > music = fileSystem . GetFileFullName ( lookup ) ;
else map - > music = defaultmusic ;
2020-07-07 11:19:09 +00:00
return map ;
}
2023-10-01 09:59:35 +00:00
void MapLocals : : fillSummary ( SummaryInfo & sum )
{
sum . kills = kills . got ;
sum . maxkills = kills . max ;
sum . secrets = secrets . got ;
sum . maxsecrets = std : : max ( secrets . got , secrets . max ) ; // If we found more than there are, increase the total. Blood's secret maintenance is too broken to get right.
sum . supersecrets = superSecrets . got ;
sum . time = PlayClock ;
// todo: centralize the remaining info as well.
}