fallout2-ce/src/main.cc

714 lines
18 KiB
C++
Raw Normal View History

2022-05-19 01:51:26 -07:00
#include "main.h"
2022-09-15 02:38:23 -07:00
#include <limits.h>
#include <string.h>
2022-06-18 07:56:37 -07:00
#include "art.h"
2022-05-19 01:51:26 -07:00
#include "autorun.h"
#include "character_selector.h"
#include "color.h"
#include "credits.h"
#include "cycle.h"
#include "db.h"
#include "debug.h"
#include "draw.h"
#include "endgame.h"
#include "game.h"
#include "game_mouse.h"
#include "game_movie.h"
#include "game_sound.h"
2022-10-04 23:23:27 -07:00
#include "input.h"
2022-10-03 06:42:34 -07:00
#include "kb.h"
2022-05-19 01:51:26 -07:00
#include "loadsave.h"
2023-02-17 04:55:14 -08:00
#include "mainmenu.h"
2022-05-19 01:51:26 -07:00
#include "map.h"
2022-10-03 02:41:33 -07:00
#include "mouse.h"
2022-05-19 01:51:26 -07:00
#include "object.h"
#include "palette.h"
2022-05-28 02:34:49 -07:00
#include "platform_compat.h"
2023-02-17 01:33:26 -08:00
#include "preferences.h"
#include "proto.h"
2022-05-19 01:51:26 -07:00
#include "random.h"
#include "scripts.h"
#include "selfrun.h"
#include "settings.h"
#include "sfall_config.h"
2022-10-05 00:35:05 -07:00
#include "svga.h"
2022-05-19 01:51:26 -07:00
#include "text_font.h"
#include "window.h"
2022-05-19 01:51:26 -07:00
#include "window_manager.h"
#include "window_manager_private.h"
2022-05-19 01:51:26 -07:00
#include "word_wrap.h"
2022-09-14 23:00:11 -07:00
#include "worldmap.h"
2022-05-19 01:51:26 -07:00
2022-09-23 05:43:44 -07:00
namespace fallout {
2022-05-21 10:42:14 -07:00
#define DEATH_WINDOW_WIDTH 640
#define DEATH_WINDOW_HEIGHT 480
2022-06-18 07:56:37 -07:00
static bool falloutInit(int argc, char** argv);
2022-09-01 08:41:37 -07:00
static int main_reset_system();
static void main_exit_system();
2022-06-18 07:56:37 -07:00
static int _main_load_new(char* fname);
2022-09-01 08:41:37 -07:00
static int main_loadgame_new();
static void main_unload_new();
2022-10-07 14:54:27 -07:00
static void mainLoop();
2022-06-18 07:56:37 -07:00
static void _main_selfrun_exit();
static void _main_selfrun_record();
static void _main_selfrun_play();
2022-06-18 07:56:37 -07:00
static void showDeath();
static void _main_death_voiceover_callback();
static int _mainDeathGrabTextFile(const char* fileName, char* dest);
static int _mainDeathWordWrap(char* text, int width, short* beginnings, short* count);
2022-05-19 01:51:26 -07:00
// 0x5194C8
2022-06-18 07:56:37 -07:00
static char _mainMap[] = "artemple.map";
2022-05-19 01:51:26 -07:00
// 0x5194D8
2022-06-18 07:56:37 -07:00
static int _main_game_paused = 0;
2022-05-19 01:51:26 -07:00
// 0x5194DC
2022-06-18 07:56:37 -07:00
static char** _main_selfrun_list = NULL;
2022-05-19 01:51:26 -07:00
// 0x5194E0
2022-06-18 07:56:37 -07:00
static int _main_selfrun_count = 0;
2022-05-19 01:51:26 -07:00
// 0x5194E4
2022-06-18 07:56:37 -07:00
static int _main_selfrun_index = 0;
2022-05-19 01:51:26 -07:00
// 0x5194E8
2022-06-18 07:56:37 -07:00
static bool _main_show_death_scene = false;
2022-05-19 01:51:26 -07:00
// A switch to pick selfrun vs. intro video for screensaver:
// - `false` - will play next selfrun recording
// - `true` - will play intro video
//
// This value will alternate on every attempt, even if there are no selfrun
// recordings.
//
// 0x5194EC
static bool gMainMenuScreensaverCycle = false;
2022-05-19 01:51:26 -07:00
// 0x614838
2022-06-18 07:56:37 -07:00
static bool _main_death_voiceover_done;
2022-05-19 01:51:26 -07:00
// 0x48099C
int falloutMain(int argc, char** argv)
{
if (!autorunMutexCreate()) {
return 1;
}
if (!falloutInit(argc, argv)) {
return 1;
}
// SFALL: Allow to skip intro movies
int skipOpeningMovies;
configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_SKIP_OPENING_MOVIES_KEY, &skipOpeningMovies);
if (skipOpeningMovies < 1) {
gameMoviePlay(MOVIE_IPLOGO, GAME_MOVIE_FADE_IN);
gameMoviePlay(MOVIE_INTRO, 0);
gameMoviePlay(MOVIE_CREDITS, 0);
}
2022-05-19 01:51:26 -07:00
if (mainMenuWindowInit() == 0) {
bool done = false;
while (!done) {
keyboardReset();
_gsound_background_play_level_music("07desert", 11);
mainMenuWindowUnhide(1);
mouseShowCursor();
2022-10-07 14:54:27 -07:00
int mainMenuRc = mainMenuWindowHandleEvents();
2022-05-19 01:51:26 -07:00
mouseHideCursor();
switch (mainMenuRc) {
case MAIN_MENU_INTRO:
mainMenuWindowHide(true);
gameMoviePlay(MOVIE_INTRO, GAME_MOVIE_PAUSE_MUSIC);
gameMoviePlay(MOVIE_CREDITS, 0);
break;
case MAIN_MENU_NEW_GAME:
mainMenuWindowHide(true);
mainMenuWindowFree();
if (characterSelectorOpen() == 2) {
gameMoviePlay(MOVIE_ELDER, GAME_MOVIE_STOP_MUSIC);
randomSeedPrerandom(-1);
2022-05-25 14:16:20 -07:00
// SFALL: Override starting map.
2022-05-25 14:16:20 -07:00
char* mapName = NULL;
if (configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_STARTING_MAP_KEY, &mapName)) {
if (*mapName == '\0') {
mapName = NULL;
}
}
2022-08-31 20:48:26 -07:00
char* mapNameCopy = compat_strdup(mapName != NULL ? mapName : _mainMap);
2022-05-25 14:16:20 -07:00
_main_load_new(mapNameCopy);
free(mapNameCopy);
2022-10-07 14:54:27 -07:00
mainLoop();
2022-05-19 01:51:26 -07:00
paletteFadeTo(gPaletteWhite);
2022-09-01 08:41:37 -07:00
// NOTE: Uninline.
main_unload_new();
// NOTE: Uninline.
main_reset_system();
2022-05-19 01:51:26 -07:00
if (_main_show_death_scene != 0) {
showDeath();
_main_show_death_scene = 0;
}
}
mainMenuWindowInit();
break;
case MAIN_MENU_LOAD_GAME:
if (1) {
2022-12-12 23:04:05 -08:00
int win = windowCreate(0, 0, screenGetWidth(), screenGetHeight(), _colorTable[0], WINDOW_MODAL | WINDOW_MOVE_ON_TOP);
2022-05-19 01:51:26 -07:00
mainMenuWindowHide(true);
mainMenuWindowFree();
2022-09-01 08:41:37 -07:00
// NOTE: Uninline.
main_loadgame_new();
2022-05-19 01:51:26 -07:00
colorPaletteLoad("color.pal");
paletteFadeTo(_cmap);
int loadGameRc = lsgLoadGame(LOAD_SAVE_MODE_FROM_MAIN_MENU);
if (loadGameRc == -1) {
debugPrint("\n ** Error running LoadGame()! **\n");
} else if (loadGameRc != 0) {
windowDestroy(win);
win = -1;
2022-10-07 14:54:27 -07:00
mainLoop();
2022-05-19 01:51:26 -07:00
}
paletteFadeTo(gPaletteWhite);
if (win != -1) {
windowDestroy(win);
}
2022-09-01 08:41:37 -07:00
// NOTE: Uninline.
main_unload_new();
// NOTE: Uninline.
main_reset_system();
2022-05-19 01:51:26 -07:00
if (_main_show_death_scene != 0) {
showDeath();
_main_show_death_scene = 0;
}
mainMenuWindowInit();
}
break;
case MAIN_MENU_TIMEOUT:
debugPrint("Main menu timed-out\n");
// FALLTHROUGH
case MAIN_MENU_SCREENSAVER:
_main_selfrun_play();
2022-05-19 01:51:26 -07:00
break;
case MAIN_MENU_OPTIONS:
2023-02-17 01:33:26 -08:00
mainMenuWindowHide(true);
doPreferences(true);
2022-05-19 01:51:26 -07:00
break;
case MAIN_MENU_CREDITS:
mainMenuWindowHide(true);
creditsOpen("credits.txt", -1, false);
break;
case MAIN_MENU_QUOTES:
// NOTE: There is a strange cmp at 0x480C50. Both operands are
// zero, set before the loop and do not modify afterwards. For
// clarity this condition is omitted.
mainMenuWindowHide(true);
creditsOpen("quotes.txt", -1, true);
break;
case MAIN_MENU_EXIT:
case -1:
done = true;
mainMenuWindowHide(true);
mainMenuWindowFree();
backgroundSoundDelete();
break;
case MAIN_MENU_SELFRUN:
_main_selfrun_record();
2022-05-19 01:51:26 -07:00
break;
}
}
}
2022-09-01 08:41:37 -07:00
// NOTE: Uninline.
main_exit_system();
2022-05-19 01:51:26 -07:00
autorunMutexClose();
return 0;
}
// 0x480CC0
2022-06-18 07:56:37 -07:00
static bool falloutInit(int argc, char** argv)
2022-05-19 01:51:26 -07:00
{
if (gameInitWithOptions("FALLOUT II", false, 0, 0, argc, argv) == -1) {
return false;
}
if (_main_selfrun_list != NULL) {
_main_selfrun_exit();
}
if (selfrunInitFileList(&_main_selfrun_list, &_main_selfrun_count) == 0) {
2022-05-19 01:51:26 -07:00
_main_selfrun_index = 0;
}
return true;
}
2022-09-01 08:41:37 -07:00
// NOTE: Inlined.
//
// 0x480D0C
static int main_reset_system()
{
gameReset();
return 1;
}
// NOTE: Inlined.
//
// 0x480D18
static void main_exit_system()
{
backgroundSoundDelete();
// NOTE: Uninline.
_main_selfrun_exit();
gameExit();
}
2022-05-19 01:51:26 -07:00
// 0x480D4C
2022-06-18 07:56:37 -07:00
static int _main_load_new(char* mapFileName)
2022-05-19 01:51:26 -07:00
{
_game_user_wants_to_quit = 0;
_main_show_death_scene = 0;
gDude->flags &= ~OBJECT_FLAT;
2022-05-19 01:51:26 -07:00
objectShow(gDude, NULL);
mouseHideCursor();
2022-12-12 23:04:05 -08:00
int win = windowCreate(0, 0, screenGetWidth(), screenGetHeight(), _colorTable[0], WINDOW_MODAL | WINDOW_MOVE_ON_TOP);
2022-05-19 01:51:26 -07:00
windowRefresh(win);
colorPaletteLoad("color.pal");
paletteFadeTo(_cmap);
_map_init();
gameMouseSetCursor(MOUSE_CURSOR_NONE);
mouseShowCursor();
mapLoadByName(mapFileName);
2022-09-15 01:42:02 -07:00
wmMapMusicStart();
2022-05-19 01:51:26 -07:00
paletteFadeTo(gPaletteWhite);
windowDestroy(win);
colorPaletteLoad("color.pal");
paletteFadeTo(_cmap);
return 0;
}
2022-09-01 08:41:37 -07:00
// NOTE: Inlined.
//
// 0x480DF8
static int main_loadgame_new()
{
_game_user_wants_to_quit = 0;
_main_show_death_scene = 0;
gDude->flags &= ~OBJECT_FLAT;
objectShow(gDude, NULL);
mouseHideCursor();
_map_init();
gameMouseSetCursor(MOUSE_CURSOR_NONE);
mouseShowCursor();
return 0;
}
// 0x480E34
static void main_unload_new()
{
objectHide(gDude, NULL);
_map_exit();
}
2022-05-19 01:51:26 -07:00
// 0x480E48
2022-10-07 14:54:27 -07:00
static void mainLoop()
2022-05-19 01:51:26 -07:00
{
bool cursorWasHidden = cursorIsHidden();
if (cursorWasHidden) {
mouseShowCursor();
}
_main_game_paused = 0;
scriptsEnable();
while (_game_user_wants_to_quit == 0) {
2022-10-07 14:54:27 -07:00
sharedFpsLimiter.mark();
2022-05-23 01:13:28 -07:00
2022-10-05 00:11:47 -07:00
int keyCode = inputGetInput();
2022-05-19 01:51:26 -07:00
gameHandleKey(keyCode, false);
scriptsHandleRequests();
mapHandleTransition();
if (_main_game_paused != 0) {
_main_game_paused = 0;
}
if ((gDude->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) != 0) {
endgameSetupDeathEnding(ENDGAME_DEATH_ENDING_REASON_DEATH);
_main_show_death_scene = 1;
_game_user_wants_to_quit = 2;
}
2022-05-23 01:13:28 -07:00
2022-10-07 14:54:27 -07:00
renderPresent();
sharedFpsLimiter.throttle();
2022-05-19 01:51:26 -07:00
}
scriptsDisable();
if (cursorWasHidden) {
mouseHideCursor();
}
}
// 0x480F38
2022-06-18 07:56:37 -07:00
static void _main_selfrun_exit()
2022-05-19 01:51:26 -07:00
{
if (_main_selfrun_list != NULL) {
selfrunFreeFileList(&_main_selfrun_list);
2022-05-19 01:51:26 -07:00
}
_main_selfrun_count = 0;
_main_selfrun_index = 0;
_main_selfrun_list = NULL;
}
// 0x480F64
static void _main_selfrun_record()
{
SelfrunData selfrunData;
bool ready = false;
char** fileList;
int fileListLength = fileNameListInit("maps\\*.map", &fileList, 0, 0);
if (fileListLength != 0) {
int selectedFileIndex = _win_list_select("Select Map", fileList, fileListLength, 0, 80, 80, 0x10000 | 0x100 | 4);
if (selectedFileIndex != -1) {
// NOTE: It's size is likely 13 chars (on par with SelfrunData
// fields), but due to the padding it takes 16 chars on stack.
char recordingName[SELFRUN_RECORDING_FILE_NAME_LENGTH];
recordingName[0] = '\0';
if (_win_get_str(recordingName, sizeof(recordingName) - 2, "Enter name for recording (8 characters max, no extension):", 100, 100) == 0) {
memset(&selfrunData, 0, sizeof(selfrunData));
if (selfrunPrepareRecording(recordingName, fileList[selectedFileIndex], &selfrunData) == 0) {
ready = true;
}
}
}
fileNameListFree(&fileList, 0);
}
if (ready) {
mainMenuWindowHide(true);
mainMenuWindowFree();
backgroundSoundDelete();
randomSeedPrerandom(0xBEEFFEED);
2022-09-01 08:41:37 -07:00
// NOTE: Uninline.
main_reset_system();
_proto_dude_init("premade\\combat.gcd");
_main_load_new(selfrunData.mapFileName);
selfrunRecordingLoop(&selfrunData);
paletteFadeTo(gPaletteWhite);
2022-09-01 08:41:37 -07:00
// NOTE: Uninline.
main_unload_new();
// NOTE: Uninline.
main_reset_system();
mainMenuWindowInit();
if (_main_selfrun_list != NULL) {
_main_selfrun_exit();
}
if (selfrunInitFileList(&_main_selfrun_list, &_main_selfrun_count) == 0) {
_main_selfrun_index = 0;
}
}
}
// 0x48109C
static void _main_selfrun_play()
{
if (!gMainMenuScreensaverCycle && _main_selfrun_count > 0) {
SelfrunData selfrunData;
if (selfrunPreparePlayback(_main_selfrun_list[_main_selfrun_index], &selfrunData) == 0) {
mainMenuWindowHide(true);
mainMenuWindowFree();
backgroundSoundDelete();
randomSeedPrerandom(0xBEEFFEED);
2022-09-01 08:41:37 -07:00
// NOTE: Uninline.
main_reset_system();
_proto_dude_init("premade\\combat.gcd");
_main_load_new(selfrunData.mapFileName);
selfrunPlaybackLoop(&selfrunData);
paletteFadeTo(gPaletteWhite);
2022-09-01 08:41:37 -07:00
// NOTE: Uninline.
main_unload_new();
// NOTE: Uninline.
main_reset_system();
mainMenuWindowInit();
}
_main_selfrun_index++;
if (_main_selfrun_index >= _main_selfrun_count) {
_main_selfrun_index = 0;
}
} else {
mainMenuWindowHide(true);
gameMoviePlay(MOVIE_INTRO, GAME_MOVIE_PAUSE_MUSIC);
}
gMainMenuScreensaverCycle = !gMainMenuScreensaverCycle;
}
2022-05-19 01:51:26 -07:00
// 0x48118C
2022-06-18 07:56:37 -07:00
static void showDeath()
2022-05-19 01:51:26 -07:00
{
artCacheFlush();
colorCycleDisable();
gameMouseSetCursor(MOUSE_CURSOR_NONE);
bool oldCursorIsHidden = cursorIsHidden();
if (oldCursorIsHidden) {
mouseShowCursor();
}
2022-05-21 10:42:14 -07:00
int deathWindowX = (screenGetWidth() - DEATH_WINDOW_WIDTH) / 2;
int deathWindowY = (screenGetHeight() - DEATH_WINDOW_HEIGHT) / 2;
int win = windowCreate(deathWindowX,
deathWindowY,
DEATH_WINDOW_WIDTH,
DEATH_WINDOW_HEIGHT,
0,
2022-12-12 23:04:05 -08:00
WINDOW_MOVE_ON_TOP);
2022-05-19 01:51:26 -07:00
if (win != -1) {
do {
unsigned char* windowBuffer = windowGetBuffer(win);
if (windowBuffer == NULL) {
break;
}
// DEATH.FRM
FrmImage backgroundFrmImage;
int fid = buildFid(OBJ_TYPE_INTERFACE, 309, 0, 0, 0);
if (!backgroundFrmImage.lock(fid)) {
2022-05-19 01:51:26 -07:00
break;
}
while (mouseGetEvent() != 0) {
2022-10-07 14:54:27 -07:00
sharedFpsLimiter.mark();
2022-10-05 00:11:47 -07:00
inputGetInput();
2022-10-07 14:54:27 -07:00
renderPresent();
sharedFpsLimiter.throttle();
2022-05-19 01:51:26 -07:00
}
keyboardReset();
inputEventQueueReset();
blitBufferToBuffer(backgroundFrmImage.getData(), 640, 480, 640, windowBuffer, 640);
backgroundFrmImage.unlock();
2022-05-19 01:51:26 -07:00
const char* deathFileName = endgameDeathEndingGetFileName();
if (settings.preferences.subtitles) {
2022-05-19 01:51:26 -07:00
char text[512];
if (_mainDeathGrabTextFile(deathFileName, text) == 0) {
debugPrint("\n((ShowDeath)): %s\n", text);
short beginnings[WORD_WRAP_MAX_COUNT];
short count;
if (_mainDeathWordWrap(text, 560, beginnings, &count) == 0) {
unsigned char* p = windowBuffer + 640 * (480 - fontGetLineHeight() * count - 8);
bufferFill(p - 602, 564, fontGetLineHeight() * count + 2, 640, 0);
p += 40;
for (int index = 0; index < count; index++) {
fontDrawText(p, text + beginnings[index], 560, 640, _colorTable[32767]);
p += 640 * fontGetLineHeight();
}
}
}
}
windowRefresh(win);
colorPaletteLoad("art\\intrface\\death.pal");
paletteFadeTo(_cmap);
_main_death_voiceover_done = false;
speechSetEndCallback(_main_death_voiceover_callback);
unsigned int delay;
if (speechLoad(deathFileName, 10, 14, 15) == -1) {
delay = 3000;
} else {
delay = UINT_MAX;
}
_gsound_speech_play_preloaded();
2022-05-20 01:02:31 -07:00
// SFALL: Fix the playback of the speech sound file for the death
// screen.
2022-10-05 00:11:47 -07:00
inputBlockForTocks(100);
2022-05-20 01:02:31 -07:00
2022-10-05 00:11:47 -07:00
unsigned int time = getTicks();
2022-05-19 01:51:26 -07:00
int keyCode;
do {
2022-10-07 14:54:27 -07:00
sharedFpsLimiter.mark();
2022-10-05 00:11:47 -07:00
keyCode = inputGetInput();
2022-10-07 14:54:27 -07:00
renderPresent();
sharedFpsLimiter.throttle();
2022-05-19 01:51:26 -07:00
} while (keyCode == -1 && !_main_death_voiceover_done && getTicksSince(time) < delay);
speechSetEndCallback(NULL);
speechDelete();
while (mouseGetEvent() != 0) {
2022-10-07 14:54:27 -07:00
sharedFpsLimiter.mark();
2022-10-05 00:11:47 -07:00
inputGetInput();
2022-10-07 14:54:27 -07:00
renderPresent();
sharedFpsLimiter.throttle();
2022-05-19 01:51:26 -07:00
}
if (keyCode == -1) {
2022-10-05 00:11:47 -07:00
inputPauseForTocks(500);
2022-05-19 01:51:26 -07:00
}
paletteFadeTo(gPaletteBlack);
colorPaletteLoad("color.pal");
} while (0);
windowDestroy(win);
}
if (oldCursorIsHidden) {
mouseHideCursor();
}
gameMouseSetCursor(MOUSE_CURSOR_ARROW);
colorCycleEnable();
}
// 0x4814A8
2022-06-18 07:56:37 -07:00
static void _main_death_voiceover_callback()
2022-05-19 01:51:26 -07:00
{
_main_death_voiceover_done = true;
}
// Read endgame subtitle.
//
// 0x4814B4
2022-06-18 07:56:37 -07:00
static int _mainDeathGrabTextFile(const char* fileName, char* dest)
2022-05-19 01:51:26 -07:00
{
const char* p = strrchr(fileName, '\\');
if (p == NULL) {
return -1;
}
2022-05-28 02:34:49 -07:00
char path[COMPAT_MAX_PATH];
2022-12-08 12:05:50 -08:00
snprintf(path, sizeof(path), "text\\%s\\cuts\\%s%s", settings.system.language.c_str(), p + 1, ".TXT");
2022-05-19 01:51:26 -07:00
File* stream = fileOpen(path, "rt");
if (stream == NULL) {
return -1;
}
while (true) {
int c = fileReadChar(stream);
if (c == -1) {
break;
}
if (c == '\n') {
c = ' ';
}
*dest++ = (c & 0xFF);
}
fileClose(stream);
*dest = '\0';
return 0;
}
// 0x481598
2022-06-18 07:56:37 -07:00
static int _mainDeathWordWrap(char* text, int width, short* beginnings, short* count)
2022-05-19 01:51:26 -07:00
{
while (true) {
char* sep = strchr(text, ':');
if (sep == NULL) {
2022-05-19 01:51:26 -07:00
break;
}
if (sep - 1 < text) {
2022-05-19 01:51:26 -07:00
break;
}
sep[0] = ' ';
sep[-1] = ' ';
2022-05-19 01:51:26 -07:00
}
if (wordWrap(text, width, beginnings, count) == -1) {
return -1;
}
// TODO: Probably wrong.
*count -= 1;
for (int index = 1; index < *count; index++) {
char* p = text + beginnings[index];
while (p >= text && *p != ' ') {
p--;
beginnings[index]--;
}
if (p != NULL) {
*p = '\0';
beginnings[index]++;
}
}
return 0;
}
2022-09-23 05:43:44 -07:00
} // namespace fallout