fallout2-ce/src/endgame.cc

1243 lines
32 KiB
C++
Raw Permalink Normal View History

2022-05-19 01:51:26 -07:00
#include "endgame.h"
2022-09-15 02:38:23 -07:00
#include <ctype.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
2022-06-19 04:27:29 -07:00
#include "art.h"
2022-05-19 01:51:26 -07:00
#include "color.h"
#include "credits.h"
#include "cycle.h"
#include "db.h"
#include "dbox.h"
#include "debug.h"
#include "draw.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-05-19 01:51:26 -07:00
#include "map.h"
#include "memory.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"
#include "pipboy.h"
2022-06-18 06:35:38 -07:00
#include "platform_compat.h"
2022-05-19 01:51:26 -07:00
#include "random.h"
#include "settings.h"
2022-05-19 01:51:26 -07:00
#include "stat.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_manager.h"
#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-19 01:51:26 -07:00
// The maximum number of subtitle lines per slide.
#define ENDGAME_ENDING_MAX_SUBTITLES (50)
#define ENDGAME_ENDING_WINDOW_WIDTH 640
#define ENDGAME_ENDING_WINDOW_HEIGHT 480
2022-06-18 06:35:38 -07:00
typedef struct EndgameDeathEnding {
int gvar;
int value;
int worldAreaKnown;
int worldAreaNotKnown;
int min_level;
int percentage;
char voiceOverBaseName[16];
// This flag denotes that the conditions for this ending is met and it was
// selected as a candidate for final random selection.
bool enabled;
} EndgameDeathEnding;
typedef struct EndgameEnding {
int gvar;
int value;
int art_num;
char voiceOverBaseName[12];
int direction;
} EndgameEnding;
static void endgameEndingRenderPanningScene(int direction, const char* narratorFileName);
static void endgameEndingRenderStaticScene(int fid, const char* narratorFileName);
static int endgameEndingHandleContinuePlaying();
static int endgameEndingSlideshowWindowInit();
static void endgameEndingSlideshowWindowFree();
static void endgameEndingVoiceOverInit(const char* fname);
static void endgameEndingVoiceOverReset();
static void endgameEndingVoiceOverFree();
static void endgameEndingLoadPalette(int type, int id);
static void _endgame_voiceover_callback();
static int endgameEndingSubtitlesLoad(const char* filePath);
static void endgameEndingRefreshSubtitles();
static void endgameEndingSubtitlesFree();
static void _endgame_movie_callback();
static void _endgame_movie_bk_process();
static int endgameEndingInit();
static void endgameEndingFree();
static int endgameDeathEndingValidate(int* percentage);
static void endgameEndingUpdateOverlay();
2022-06-18 06:35:38 -07:00
2022-05-19 01:51:26 -07:00
// The number of lines in current subtitles file.
//
// It's used as a length for two arrays:
// - [gEndgameEndingSubtitles]
// - [gEndgameEndingSubtitlesTimings]
//
// This value does not exceed [ENDGAME_ENDING_SUBTITLES_CAPACITY].
//
// 0x518668
2022-06-18 06:35:38 -07:00
static int gEndgameEndingSubtitlesLength = 0;
2022-05-19 01:51:26 -07:00
// The number of characters in current subtitles file.
//
// This value is used to determine
//
// 0x51866C
2022-06-18 06:35:38 -07:00
static int gEndgameEndingSubtitlesCharactersCount = 0;
2022-05-19 01:51:26 -07:00
// 0x518670
2022-06-18 06:35:38 -07:00
static int gEndgameEndingSubtitlesCurrentLine = 0;
2022-05-19 01:51:26 -07:00
// 0x518674
2022-06-18 06:35:38 -07:00
static int _endgame_maybe_done = 0;
2022-05-19 01:51:26 -07:00
// enddeath.txt
//
// 0x518678
2024-01-16 07:57:49 -08:00
static EndgameDeathEnding* gEndgameDeathEndings = nullptr;
2022-05-19 01:51:26 -07:00
// The number of death endings in [gEndgameDeathEndings] array.
//
// 0x51867C
2022-06-18 06:35:38 -07:00
static int gEndgameDeathEndingsLength = 0;
2022-05-19 01:51:26 -07:00
// Base file name for death ending.
//
// This value does not include extension.
//
// 0x570A90
2022-06-18 06:35:38 -07:00
static char gEndgameDeathEndingFileName[40];
2022-05-19 01:51:26 -07:00
// This flag denotes whether speech sound was successfully loaded for
// the current slide.
//
// 0x570AB8
2022-06-18 06:35:38 -07:00
static bool gEndgameEndingVoiceOverSpeechLoaded;
2022-05-19 01:51:26 -07:00
// 0x570ABC
2022-06-18 06:35:38 -07:00
static char gEndgameEndingSubtitlesLocalizedPath[COMPAT_MAX_PATH];
2022-05-19 01:51:26 -07:00
// The flag used to denote voice over speech for current slide has ended.
//
// 0x570BC0
2022-06-18 06:35:38 -07:00
static bool gEndgameEndingSpeechEnded;
2022-05-19 01:51:26 -07:00
// endgame.txt
//
// 0x570BC4
2022-06-18 06:35:38 -07:00
static EndgameEnding* gEndgameEndings;
2022-05-19 01:51:26 -07:00
// The array of text lines in current subtitles file.
//
// The length is specified in [gEndgameEndingSubtitlesLength]. It's capacity
// is [ENDGAME_ENDING_SUBTITLES_CAPACITY].
//
// 0x570BC8
2022-06-18 06:35:38 -07:00
static char** gEndgameEndingSubtitles;
2022-05-19 01:51:26 -07:00
// 0x570BCC
2022-06-18 06:35:38 -07:00
static bool gEndgameEndingSubtitlesEnabled;
2022-05-19 01:51:26 -07:00
// The flag used to denote voice over subtitles for current slide has ended.
//
// 0x570BD0
2022-06-18 06:35:38 -07:00
static bool gEndgameEndingSubtitlesEnded;
2022-05-19 01:51:26 -07:00
// 0x570BD4
2022-06-18 06:35:38 -07:00
static bool _endgame_map_enabled;
2022-05-19 01:51:26 -07:00
// 0x570BD8
2022-06-18 06:35:38 -07:00
static bool _endgame_mouse_state;
2022-05-19 01:51:26 -07:00
// The number of endings in [gEndgameEndings] array.
//
// 0x570BDC
2022-06-18 06:35:38 -07:00
static int gEndgameEndingsLength = 0;
2022-05-19 01:51:26 -07:00
// This flag denotes whether subtitles was successfully loaded for
// the current slide.
//
// 0x570BE0
2022-06-18 06:35:38 -07:00
static bool gEndgameEndingVoiceOverSubtitlesLoaded;
2022-05-19 01:51:26 -07:00
// Reference time is a timestamp when subtitle is first displayed.
//
// This value is used together with [gEndgameEndingSubtitlesTimings] array to
// determine when next line needs to be displayed.
//
// 0x570BE4
2022-06-18 06:35:38 -07:00
static unsigned int gEndgameEndingSubtitlesReferenceTime;
2022-05-19 01:51:26 -07:00
// The array of timings for each line in current subtitles file.
//
// The length is specified in [gEndgameEndingSubtitlesLength]. It's capacity
// is [ENDGAME_ENDING_SUBTITLES_CAPACITY].
//
// 0x570BE8
2022-06-18 06:35:38 -07:00
static unsigned int* gEndgameEndingSubtitlesTimings;
2022-05-19 01:51:26 -07:00
// Font that was current before endgame slideshow window was created.
//
// 0x570BEC
2022-06-18 06:35:38 -07:00
static int gEndgameEndingSlideshowOldFont;
2022-05-19 01:51:26 -07:00
// 0x570BF0
2022-06-18 06:35:38 -07:00
static unsigned char* gEndgameEndingSlideshowWindowBuffer;
2022-05-19 01:51:26 -07:00
// 0x570BF4
2022-06-18 06:35:38 -07:00
static int gEndgameEndingSlideshowWindow;
2022-05-19 01:51:26 -07:00
static int gEndgameEndingOverlay;
2022-05-19 01:51:26 -07:00
// 0x43F788
void endgamePlaySlideshow()
{
if (endgameEndingSlideshowWindowInit() == -1) {
return;
}
for (int index = 0; index < gEndgameEndingsLength; index++) {
EndgameEnding* ending = &(gEndgameEndings[index]);
int value = gameGetGlobalVar(ending->gvar);
if (value == ending->value) {
if (ending->art_num == 327) {
endgameEndingRenderPanningScene(ending->direction, ending->voiceOverBaseName);
} else {
int fid = buildFid(OBJ_TYPE_INTERFACE, ending->art_num, 0, 0, 0);
2022-05-19 01:51:26 -07:00
endgameEndingRenderStaticScene(fid, ending->voiceOverBaseName);
}
}
}
endgameEndingSlideshowWindowFree();
}
// 0x43F810
void endgamePlayMovie()
{
backgroundSoundDelete();
isoDisable();
paletteFadeTo(gPaletteBlack);
_endgame_maybe_done = 0;
tickersAdd(_endgame_movie_bk_process);
backgroundSoundSetEndCallback(_endgame_movie_callback);
backgroundSoundLoad("akiss", 12, 14, 15);
2022-10-05 00:11:47 -07:00
inputPauseForTocks(3000);
2022-05-19 01:51:26 -07:00
// NOTE: Result is ignored. I guess there was some kind of switch for male
// vs. female ending, but it was not implemented.
critterGetStat(gDude, STAT_GENDER);
creditsOpen("credits.txt", -1, false);
backgroundSoundDelete();
2024-01-16 07:57:49 -08:00
backgroundSoundSetEndCallback(nullptr);
2022-05-19 01:51:26 -07:00
tickersRemove(_endgame_movie_bk_process);
backgroundSoundDelete();
colorPaletteLoad("color.pal");
paletteFadeTo(_cmap);
isoEnable();
endgameEndingHandleContinuePlaying();
}
// 0x43F8C4
2022-06-18 06:35:38 -07:00
static int endgameEndingHandleContinuePlaying()
2022-05-19 01:51:26 -07:00
{
bool isoWasEnabled = isoDisable();
bool gameMouseWasVisible;
if (isoWasEnabled) {
gameMouseWasVisible = gameMouseObjectsIsVisible();
} else {
gameMouseWasVisible = false;
}
if (gameMouseWasVisible) {
gameMouseObjectsHide();
}
bool oldCursorIsHidden = cursorIsHidden();
if (oldCursorIsHidden) {
mouseShowCursor();
}
int oldCursor = gameMouseGetCursor();
gameMouseSetCursor(MOUSE_CURSOR_ARROW);
int rc;
MessageListItem messageListItem;
messageListItem.num = 30;
if (messageListGetItem(&gMiscMessageList, &messageListItem)) {
2024-01-16 07:57:49 -08:00
rc = showDialogBox(messageListItem.text, nullptr, 0, 169, 117, _colorTable[32328], nullptr, _colorTable[32328], DIALOG_BOX_YES_NO);
2022-05-19 01:51:26 -07:00
if (rc == 0) {
_game_user_wants_to_quit = 2;
}
} else {
rc = -1;
}
gameMouseSetCursor(oldCursor);
if (oldCursorIsHidden) {
mouseHideCursor();
}
if (gameMouseWasVisible) {
gameMouseObjectsShow();
}
if (isoWasEnabled) {
isoEnable();
}
return rc;
}
// 0x43FBDC
2022-06-18 06:35:38 -07:00
static void endgameEndingRenderPanningScene(int direction, const char* narratorFileName)
2022-05-19 01:51:26 -07:00
{
int fid = buildFid(OBJ_TYPE_INTERFACE, 327, 0, 0, 0);
2022-05-19 01:51:26 -07:00
CacheEntry* backgroundHandle;
Art* background = artLock(fid, &backgroundHandle);
2024-01-16 07:57:49 -08:00
if (background != nullptr) {
2022-05-19 01:51:26 -07:00
int width = artGetWidth(background, 0, 0);
int height = artGetHeight(background, 0, 0);
unsigned char* backgroundData = artGetFrameData(background, 0, 0);
bufferFill(gEndgameEndingSlideshowWindowBuffer, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, ENDGAME_ENDING_WINDOW_WIDTH, _colorTable[0]);
2022-05-19 01:51:26 -07:00
endgameEndingLoadPalette(6, 327);
// CE: Update overlay.
endgameEndingUpdateOverlay();
2022-05-19 01:51:26 -07:00
unsigned char palette[768];
memcpy(palette, _cmap, 768);
paletteSetEntries(gPaletteBlack);
endgameEndingVoiceOverInit(narratorFileName);
// TODO: Unclear math.
int v8 = width - 640;
int v32 = v8 / 4;
unsigned int v9 = 16 * v8 / v8;
unsigned int v9_ = 16 * v8;
if (gEndgameEndingVoiceOverSpeechLoaded) {
unsigned int v10 = 1000 * speechGetDuration();
if (v10 > v9_ / 2) {
v9 = (v10 + v9 * (v8 / 2)) / v8;
}
}
int start;
int end;
if (direction == -1) {
start = width - 640;
end = 0;
} else {
start = 0;
end = width - 640;
}
tickersDisable();
bool subtitlesLoaded = false;
unsigned int since = 0;
while (start != end) {
2022-10-07 14:54:27 -07:00
sharedFpsLimiter.mark();
2022-05-19 01:51:26 -07:00
int v12 = 640 - v32;
// TODO: Complex math, setup scene in debugger.
if (getTicksSince(since) >= v9) {
blitBufferToBuffer(backgroundData + start, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, width, gEndgameEndingSlideshowWindowBuffer, ENDGAME_ENDING_WINDOW_WIDTH);
2022-05-19 01:51:26 -07:00
if (subtitlesLoaded) {
endgameEndingRefreshSubtitles();
}
windowRefresh(gEndgameEndingSlideshowWindow);
2022-10-05 00:11:47 -07:00
since = getTicks();
2022-05-19 01:51:26 -07:00
bool v14;
double v31;
if (start > v32) {
if (v12 > start) {
v14 = false;
} else {
int v28 = v32 - (start - v12);
v31 = (double)v28 / (double)v32;
v14 = true;
}
} else {
v14 = true;
v31 = (double)start / (double)v32;
}
if (v14) {
unsigned char darkenedPalette[768];
for (int index = 0; index < 768; index++) {
darkenedPalette[index] = (unsigned char)trunc(palette[index] * v31);
}
paletteSetEntries(darkenedPalette);
}
start += direction;
if (direction == 1 && (start == v32)) {
// NOTE: Uninline.
endgameEndingVoiceOverReset();
subtitlesLoaded = true;
} else if (direction == -1 && (start == v12)) {
// NOTE: Uninline.
endgameEndingVoiceOverReset();
subtitlesLoaded = true;
}
}
soundContinueAll();
2022-10-05 00:11:47 -07:00
if (inputGetInput() != -1) {
2022-05-19 01:51:26 -07:00
// NOTE: Uninline.
endgameEndingVoiceOverFree();
break;
}
2022-10-07 14:54:27 -07:00
renderPresent();
sharedFpsLimiter.throttle();
2022-05-19 01:51:26 -07:00
}
tickersEnable();
artUnlock(backgroundHandle);
paletteFadeTo(gPaletteBlack);
bufferFill(gEndgameEndingSlideshowWindowBuffer, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, ENDGAME_ENDING_WINDOW_WIDTH, _colorTable[0]);
2022-05-19 01:51:26 -07:00
windowRefresh(gEndgameEndingSlideshowWindow);
}
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
}
}
// 0x440004
2022-06-18 06:35:38 -07:00
static void endgameEndingRenderStaticScene(int fid, const char* narratorFileName)
2022-05-19 01:51:26 -07:00
{
CacheEntry* backgroundHandle;
Art* background = artLock(fid, &backgroundHandle);
2024-01-16 07:57:49 -08:00
if (background == nullptr) {
2022-05-19 01:51:26 -07:00
return;
}
unsigned char* backgroundData = artGetFrameData(background, 0, 0);
2024-01-16 07:57:49 -08:00
if (backgroundData != nullptr) {
blitBufferToBuffer(backgroundData, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, ENDGAME_ENDING_WINDOW_WIDTH, gEndgameEndingSlideshowWindowBuffer, ENDGAME_ENDING_WINDOW_WIDTH);
2022-05-19 01:51:26 -07:00
windowRefresh(gEndgameEndingSlideshowWindow);
endgameEndingLoadPalette(FID_TYPE(fid), fid & 0xFFF);
2022-05-19 01:51:26 -07:00
// CE: Update overlay.
endgameEndingUpdateOverlay();
2022-05-19 01:51:26 -07:00
endgameEndingVoiceOverInit(narratorFileName);
unsigned int delay;
if (gEndgameEndingVoiceOverSubtitlesLoaded || gEndgameEndingVoiceOverSpeechLoaded) {
delay = UINT_MAX;
} else {
delay = 3000;
}
paletteFadeTo(_cmap);
2022-10-05 00:11:47 -07:00
inputPauseForTocks(500);
2022-05-19 01:51:26 -07:00
// NOTE: Uninline.
endgameEndingVoiceOverReset();
2022-10-05 00:11:47 -07:00
unsigned int referenceTime = getTicks();
2022-05-19 01:51:26 -07:00
tickersDisable();
int keyCode;
while (true) {
2022-10-07 14:54:27 -07:00
sharedFpsLimiter.mark();
2022-10-05 00:11:47 -07:00
keyCode = inputGetInput();
2022-05-19 01:51:26 -07:00
if (keyCode != -1) {
break;
}
if (gEndgameEndingSpeechEnded) {
break;
}
if (gEndgameEndingSubtitlesEnded) {
break;
}
if (getTicksSince(referenceTime) > delay) {
break;
}
blitBufferToBuffer(backgroundData, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, ENDGAME_ENDING_WINDOW_WIDTH, gEndgameEndingSlideshowWindowBuffer, ENDGAME_ENDING_WINDOW_WIDTH);
2022-05-19 01:51:26 -07:00
endgameEndingRefreshSubtitles();
windowRefresh(gEndgameEndingSlideshowWindow);
soundContinueAll();
2022-10-07 14:54:27 -07:00
renderPresent();
sharedFpsLimiter.throttle();
2022-05-19 01:51:26 -07:00
}
tickersEnable();
speechDelete();
endgameEndingSubtitlesFree();
gEndgameEndingVoiceOverSpeechLoaded = false;
gEndgameEndingVoiceOverSubtitlesLoaded = false;
if (keyCode == -1) {
2022-10-05 00:11:47 -07:00
inputPauseForTocks(500);
2022-05-19 01:51:26 -07:00
}
paletteFadeTo(gPaletteBlack);
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
}
}
artUnlock(backgroundHandle);
}
// 0x43F99C
2022-06-18 06:35:38 -07:00
static int endgameEndingSlideshowWindowInit()
2022-05-19 01:51:26 -07:00
{
if (endgameEndingInit() != 0) {
return -1;
}
backgroundSoundDelete();
_endgame_map_enabled = isoDisable();
colorCycleDisable();
gameMouseSetCursor(MOUSE_CURSOR_NONE);
bool oldCursorIsHidden = cursorIsHidden();
_endgame_mouse_state = oldCursorIsHidden == 0;
if (oldCursorIsHidden) {
mouseShowCursor();
}
gEndgameEndingSlideshowOldFont = fontGetCurrent();
fontSetCurrent(101);
paletteFadeTo(gPaletteBlack);
// CE: Every slide has a separate color palette which is incompatible with
// main color palette. Setup overlay to hide everything.
gEndgameEndingOverlay = windowCreate(0, 0, screenGetWidth(), screenGetHeight(), _colorTable[0], WINDOW_MOVE_ON_TOP);
if (gEndgameEndingOverlay == -1) {
return -1;
}
int windowEndgameEndingX = (screenGetWidth() - ENDGAME_ENDING_WINDOW_WIDTH) / 2;
int windowEndgameEndingY = (screenGetHeight() - ENDGAME_ENDING_WINDOW_HEIGHT) / 2;
2022-06-04 15:01:49 -07:00
gEndgameEndingSlideshowWindow = windowCreate(windowEndgameEndingX,
windowEndgameEndingY,
ENDGAME_ENDING_WINDOW_WIDTH,
ENDGAME_ENDING_WINDOW_HEIGHT,
_colorTable[0],
2022-12-12 23:04:05 -08:00
WINDOW_MOVE_ON_TOP);
2022-05-19 01:51:26 -07:00
if (gEndgameEndingSlideshowWindow == -1) {
return -1;
}
gEndgameEndingSlideshowWindowBuffer = windowGetBuffer(gEndgameEndingSlideshowWindow);
2024-01-16 07:57:49 -08:00
if (gEndgameEndingSlideshowWindowBuffer == nullptr) {
2022-05-19 01:51:26 -07:00
return -1;
}
colorCycleDisable();
speechSetEndCallback(_endgame_voiceover_callback);
gEndgameEndingSubtitlesEnabled = settings.preferences.subtitles;
2022-05-19 01:51:26 -07:00
if (!gEndgameEndingSubtitlesEnabled) {
return 0;
}
2022-12-08 12:05:50 -08:00
snprintf(gEndgameEndingSubtitlesLocalizedPath, sizeof(gEndgameEndingSubtitlesLocalizedPath), "text\\%s\\cuts\\", settings.system.language.c_str());
2022-05-19 01:51:26 -07:00
2022-05-21 08:22:03 -07:00
gEndgameEndingSubtitles = (char**)internal_malloc(sizeof(*gEndgameEndingSubtitles) * ENDGAME_ENDING_MAX_SUBTITLES);
2024-01-16 07:57:49 -08:00
if (gEndgameEndingSubtitles == nullptr) {
2022-05-19 01:51:26 -07:00
gEndgameEndingSubtitlesEnabled = false;
return 0;
}
for (int index = 0; index < ENDGAME_ENDING_MAX_SUBTITLES; index++) {
2024-01-16 07:57:49 -08:00
gEndgameEndingSubtitles[index] = nullptr;
2022-05-19 01:51:26 -07:00
}
2022-05-21 08:22:03 -07:00
gEndgameEndingSubtitlesTimings = (unsigned int*)internal_malloc(sizeof(*gEndgameEndingSubtitlesTimings) * ENDGAME_ENDING_MAX_SUBTITLES);
2024-01-16 07:57:49 -08:00
if (gEndgameEndingSubtitlesTimings == nullptr) {
2022-05-19 01:51:26 -07:00
internal_free(gEndgameEndingSubtitles);
gEndgameEndingSubtitlesEnabled = false;
return 0;
}
return 0;
}
// 0x43FB28
2022-06-18 06:35:38 -07:00
static void endgameEndingSlideshowWindowFree()
2022-05-19 01:51:26 -07:00
{
if (gEndgameEndingSubtitlesEnabled) {
endgameEndingSubtitlesFree();
internal_free(gEndgameEndingSubtitlesTimings);
internal_free(gEndgameEndingSubtitles);
2024-01-16 07:57:49 -08:00
gEndgameEndingSubtitles = nullptr;
2022-05-19 01:51:26 -07:00
gEndgameEndingSubtitlesEnabled = false;
}
// NOTE: Uninline.
endgameEndingFree();
fontSetCurrent(gEndgameEndingSlideshowOldFont);
2024-01-16 07:57:49 -08:00
speechSetEndCallback(nullptr);
2022-05-19 01:51:26 -07:00
windowDestroy(gEndgameEndingSlideshowWindow);
windowDestroy(gEndgameEndingOverlay);
2022-05-19 01:51:26 -07:00
if (!_endgame_mouse_state) {
mouseHideCursor();
}
gameMouseSetCursor(MOUSE_CURSOR_ARROW);
colorPaletteLoad("color.pal");
paletteFadeTo(_cmap);
colorCycleEnable();
if (_endgame_map_enabled) {
isoEnable();
}
}
// 0x4401A0
2022-06-18 06:35:38 -07:00
static void endgameEndingVoiceOverInit(const char* fileBaseName)
2022-05-19 01:51:26 -07:00
{
2022-05-28 02:34:49 -07:00
char path[COMPAT_MAX_PATH];
2022-05-19 01:51:26 -07:00
// NOTE: Uninline.
endgameEndingVoiceOverFree();
gEndgameEndingVoiceOverSpeechLoaded = false;
gEndgameEndingVoiceOverSubtitlesLoaded = false;
// Build speech file path.
2022-12-08 12:05:50 -08:00
snprintf(path, sizeof(path), "%s%s", "narrator\\", fileBaseName);
2022-05-19 01:51:26 -07:00
if (speechLoad(path, 10, 14, 15) != -1) {
gEndgameEndingVoiceOverSpeechLoaded = true;
}
if (gEndgameEndingSubtitlesEnabled) {
// Build subtitles file path.
2022-12-08 12:05:50 -08:00
snprintf(path, sizeof(path), "%s%s.txt", gEndgameEndingSubtitlesLocalizedPath, fileBaseName);
2022-05-19 01:51:26 -07:00
if (endgameEndingSubtitlesLoad(path) != 0) {
return;
}
double durationPerCharacter;
if (gEndgameEndingVoiceOverSpeechLoaded) {
durationPerCharacter = (double)speechGetDuration() / (double)gEndgameEndingSubtitlesCharactersCount;
} else {
durationPerCharacter = 0.08;
}
unsigned int timing = 0;
for (int index = 0; index < gEndgameEndingSubtitlesLength; index++) {
2022-08-31 08:52:01 -07:00
double charactersCount = static_cast<double>(strlen(gEndgameEndingSubtitles[index]));
2022-05-19 01:51:26 -07:00
// NOTE: There is floating point math at 0x4402E6 used to add
// timing.
timing += (unsigned int)trunc(charactersCount * durationPerCharacter * 1000.0);
gEndgameEndingSubtitlesTimings[index] = timing;
}
gEndgameEndingVoiceOverSubtitlesLoaded = true;
}
}
// NOTE: This function was inlined at every call site.
//
// 0x440324
2022-06-18 06:35:38 -07:00
static void endgameEndingVoiceOverReset()
2022-05-19 01:51:26 -07:00
{
gEndgameEndingSubtitlesEnded = false;
gEndgameEndingSpeechEnded = false;
if (gEndgameEndingVoiceOverSpeechLoaded) {
_gsound_speech_play_preloaded();
}
if (gEndgameEndingVoiceOverSubtitlesLoaded) {
2022-10-05 00:11:47 -07:00
gEndgameEndingSubtitlesReferenceTime = getTicks();
2022-05-19 01:51:26 -07:00
}
}
// NOTE: This function was inlined at every call site.
//
// 0x44035C
2022-06-18 06:35:38 -07:00
static void endgameEndingVoiceOverFree()
2022-05-19 01:51:26 -07:00
{
speechDelete();
endgameEndingSubtitlesFree();
gEndgameEndingVoiceOverSpeechLoaded = false;
gEndgameEndingVoiceOverSubtitlesLoaded = false;
}
// 0x440378
2022-06-18 06:35:38 -07:00
static void endgameEndingLoadPalette(int type, int id)
2022-05-19 01:51:26 -07:00
{
char fileName[13];
if (artCopyFileName(type, id, fileName) != 0) {
return;
}
// Remove extension from file name.
char* pch = strrchr(fileName, '.');
2024-01-16 07:57:49 -08:00
if (pch != nullptr) {
2022-05-19 01:51:26 -07:00
*pch = '\0';
}
if (strlen(fileName) <= 8) {
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), "%s\\%s.pal", "art\\intrface", fileName);
2022-05-19 01:51:26 -07:00
colorPaletteLoad(path);
}
}
// 0x4403F0
2022-06-18 06:35:38 -07:00
static void _endgame_voiceover_callback()
2022-05-19 01:51:26 -07:00
{
gEndgameEndingSpeechEnded = true;
}
// Loads subtitles file.
//
// 0x4403FC
2022-06-18 06:35:38 -07:00
static int endgameEndingSubtitlesLoad(const char* filePath)
2022-05-19 01:51:26 -07:00
{
endgameEndingSubtitlesFree();
File* stream = fileOpen(filePath, "rt");
2024-01-16 07:57:49 -08:00
if (stream == nullptr) {
2022-05-19 01:51:26 -07:00
return -1;
}
// FIXME: There is at least one subtitle for Arroyo ending (nar_ar1) that
// does not fit into this buffer.
char string[256];
while (fileReadString(string, sizeof(string), stream)) {
char* pch;
// Find and clamp string at EOL.
pch = strchr(string, '\n');
2024-01-16 07:57:49 -08:00
if (pch != nullptr) {
2022-05-19 01:51:26 -07:00
*pch = '\0';
}
// Find separator. The value before separator is ignored (as opposed to
// movie subtitles, where the value before separator is a timing).
pch = strchr(string, ':');
2024-01-16 07:57:49 -08:00
if (pch != nullptr) {
2022-05-19 01:51:26 -07:00
if (gEndgameEndingSubtitlesLength < ENDGAME_ENDING_MAX_SUBTITLES) {
gEndgameEndingSubtitles[gEndgameEndingSubtitlesLength] = internal_strdup(pch + 1);
gEndgameEndingSubtitlesLength++;
2022-08-31 08:52:01 -07:00
gEndgameEndingSubtitlesCharactersCount += static_cast<int>(strlen(pch + 1));
2022-05-19 01:51:26 -07:00
}
}
}
fileClose(stream);
return 0;
}
// Refreshes subtitles.
//
// 0x4404EC
2022-06-18 06:35:38 -07:00
static void endgameEndingRefreshSubtitles()
2022-05-19 01:51:26 -07:00
{
if (gEndgameEndingSubtitlesLength <= gEndgameEndingSubtitlesCurrentLine) {
if (gEndgameEndingVoiceOverSubtitlesLoaded) {
gEndgameEndingSubtitlesEnded = true;
}
return;
}
if (getTicksSince(gEndgameEndingSubtitlesReferenceTime) > gEndgameEndingSubtitlesTimings[gEndgameEndingSubtitlesCurrentLine]) {
gEndgameEndingSubtitlesCurrentLine++;
return;
}
char* text = gEndgameEndingSubtitles[gEndgameEndingSubtitlesCurrentLine];
2024-01-16 07:57:49 -08:00
if (text == nullptr) {
2022-05-19 01:51:26 -07:00
return;
}
short beginnings[WORD_WRAP_MAX_COUNT];
short count;
if (wordWrap(text, 540, beginnings, &count) != 0) {
return;
}
int height = fontGetLineHeight();
int y = 480 - height * count;
for (int index = 0; index < count - 1; index++) {
char* beginning = text + beginnings[index];
char* ending = text + beginnings[index + 1];
if (ending[-1] == ' ') {
ending--;
}
char c = *ending;
*ending = '\0';
int width = fontGetStringWidth(beginning);
int x = (640 - width) / 2;
bufferFill(gEndgameEndingSlideshowWindowBuffer + 640 * y + x, width, height, 640, _colorTable[0]);
fontDrawText(gEndgameEndingSlideshowWindowBuffer + 640 * y + x, beginning, width, 640, _colorTable[32767]);
*ending = c;
y += height;
}
}
// 0x4406CC
2022-06-18 06:35:38 -07:00
static void endgameEndingSubtitlesFree()
2022-05-19 01:51:26 -07:00
{
for (int index = 0; index < gEndgameEndingSubtitlesLength; index++) {
2024-01-16 07:57:49 -08:00
if (gEndgameEndingSubtitles[index] != nullptr) {
2022-05-19 01:51:26 -07:00
internal_free(gEndgameEndingSubtitles[index]);
2024-01-16 07:57:49 -08:00
gEndgameEndingSubtitles[index] = nullptr;
2022-05-19 01:51:26 -07:00
}
}
gEndgameEndingSubtitlesCurrentLine = 0;
gEndgameEndingSubtitlesCharactersCount = 0;
gEndgameEndingSubtitlesLength = 0;
}
// 0x440728
2022-06-18 06:35:38 -07:00
static void _endgame_movie_callback()
2022-05-19 01:51:26 -07:00
{
_endgame_maybe_done = 1;
}
// 0x440734
2022-06-18 06:35:38 -07:00
static void _endgame_movie_bk_process()
2022-05-19 01:51:26 -07:00
{
if (_endgame_maybe_done) {
backgroundSoundLoad("10labone", 11, 14, 16);
2024-01-16 07:57:49 -08:00
backgroundSoundSetEndCallback(nullptr);
2022-05-19 01:51:26 -07:00
tickersRemove(_endgame_movie_bk_process);
}
}
// 0x440770
2022-06-18 06:35:38 -07:00
static int endgameEndingInit()
2022-05-19 01:51:26 -07:00
{
File* stream;
char str[256];
char *ch, *tok;
const char* delim = " \t,";
EndgameEnding entry;
EndgameEnding* entries;
2022-08-31 08:52:01 -07:00
size_t narratorFileNameLength;
2022-05-19 01:51:26 -07:00
2024-01-16 07:57:49 -08:00
if (gEndgameEndings != nullptr) {
2022-05-19 01:51:26 -07:00
internal_free(gEndgameEndings);
2024-01-16 07:57:49 -08:00
gEndgameEndings = nullptr;
2022-05-19 01:51:26 -07:00
}
gEndgameEndingsLength = 0;
stream = fileOpen("data\\endgame.txt", "rt");
2024-01-16 07:57:49 -08:00
if (stream == nullptr) {
2022-05-19 01:51:26 -07:00
return -1;
}
while (fileReadString(str, sizeof(str), stream)) {
ch = str;
while (isspace(*ch)) {
ch++;
}
if (*ch == '#') {
continue;
}
tok = strtok(ch, delim);
2024-01-16 07:57:49 -08:00
if (tok == nullptr) {
2022-05-19 01:51:26 -07:00
continue;
}
entry.gvar = atoi(tok);
2024-01-16 07:57:49 -08:00
tok = strtok(nullptr, delim);
if (tok == nullptr) {
2022-05-19 01:51:26 -07:00
continue;
}
entry.value = atoi(tok);
2024-01-16 07:57:49 -08:00
tok = strtok(nullptr, delim);
if (tok == nullptr) {
2022-05-19 01:51:26 -07:00
continue;
}
entry.art_num = atoi(tok);
2024-01-16 07:57:49 -08:00
tok = strtok(nullptr, delim);
if (tok == nullptr) {
2022-05-19 01:51:26 -07:00
continue;
}
strcpy(entry.voiceOverBaseName, tok);
2022-08-31 08:52:01 -07:00
narratorFileNameLength = strlen(entry.voiceOverBaseName);
if (isspace(entry.voiceOverBaseName[narratorFileNameLength - 1])) {
entry.voiceOverBaseName[narratorFileNameLength - 1] = '\0';
2022-05-19 01:51:26 -07:00
}
2024-01-16 07:57:49 -08:00
tok = strtok(nullptr, delim);
if (tok != nullptr) {
2022-05-19 01:51:26 -07:00
entry.direction = atoi(tok);
} else {
entry.direction = 1;
}
2022-05-21 08:22:03 -07:00
entries = (EndgameEnding*)internal_realloc(gEndgameEndings, sizeof(*entries) * (gEndgameEndingsLength + 1));
2024-01-16 07:57:49 -08:00
if (entries == nullptr) {
2022-05-19 01:51:26 -07:00
goto err;
}
memcpy(&(entries[gEndgameEndingsLength]), &entry, sizeof(entry));
gEndgameEndings = entries;
gEndgameEndingsLength++;
}
fileClose(stream);
return 0;
err:
fileClose(stream);
return -1;
}
// NOTE: There are no references to this function. It was inlined.
//
// 0x44095C
2022-06-18 06:35:38 -07:00
static void endgameEndingFree()
2022-05-19 01:51:26 -07:00
{
2024-01-16 07:57:49 -08:00
if (gEndgameEndings != nullptr) {
2022-05-19 01:51:26 -07:00
internal_free(gEndgameEndings);
2024-01-16 07:57:49 -08:00
gEndgameEndings = nullptr;
2022-05-19 01:51:26 -07:00
}
gEndgameEndingsLength = 0;
}
// endgameDeathEndingInit
// 0x440984
int endgameDeathEndingInit()
{
File* stream;
char str[256];
char* ch;
const char* delim = " \t,";
char* tok;
EndgameDeathEnding entry;
EndgameDeathEnding* entries;
2022-08-31 08:52:01 -07:00
size_t narratorFileNameLength;
2022-05-19 01:51:26 -07:00
strcpy(gEndgameDeathEndingFileName, "narrator\\nar_5");
stream = fileOpen("data\\enddeath.txt", "rt");
2024-01-16 07:57:49 -08:00
if (stream == nullptr) {
2022-05-19 01:51:26 -07:00
return -1;
}
while (fileReadString(str, 256, stream)) {
ch = str;
while (isspace(*ch)) {
ch++;
}
if (*ch == '#') {
continue;
}
tok = strtok(ch, delim);
2024-01-16 07:57:49 -08:00
if (tok == nullptr) {
2022-05-19 01:51:26 -07:00
continue;
}
entry.gvar = atoi(tok);
2024-01-16 07:57:49 -08:00
tok = strtok(nullptr, delim);
if (tok == nullptr) {
2022-05-19 01:51:26 -07:00
continue;
}
entry.value = atoi(tok);
2024-01-16 07:57:49 -08:00
tok = strtok(nullptr, delim);
if (tok == nullptr) {
2022-05-19 01:51:26 -07:00
continue;
}
entry.worldAreaKnown = atoi(tok);
2024-01-16 07:57:49 -08:00
tok = strtok(nullptr, delim);
if (tok == nullptr) {
2022-05-19 01:51:26 -07:00
continue;
}
entry.worldAreaNotKnown = atoi(tok);
2024-01-16 07:57:49 -08:00
tok = strtok(nullptr, delim);
if (tok == nullptr) {
2022-05-19 01:51:26 -07:00
continue;
}
entry.min_level = atoi(tok);
2024-01-16 07:57:49 -08:00
tok = strtok(nullptr, delim);
if (tok == nullptr) {
2022-05-19 01:51:26 -07:00
continue;
}
entry.percentage = atoi(tok);
2024-01-16 07:57:49 -08:00
tok = strtok(nullptr, delim);
if (tok == nullptr) {
2022-05-19 01:51:26 -07:00
continue;
}
// this code is slightly different from the original, but does the same thing
2022-08-31 08:52:01 -07:00
narratorFileNameLength = strlen(tok);
strncpy(entry.voiceOverBaseName, tok, narratorFileNameLength);
2022-05-19 01:51:26 -07:00
entry.enabled = false;
2022-08-31 08:52:01 -07:00
if (isspace(entry.voiceOverBaseName[narratorFileNameLength - 1])) {
entry.voiceOverBaseName[narratorFileNameLength - 1] = '\0';
2022-05-19 01:51:26 -07:00
}
2022-05-21 08:22:03 -07:00
entries = (EndgameDeathEnding*)internal_realloc(gEndgameDeathEndings, sizeof(*entries) * (gEndgameDeathEndingsLength + 1));
2024-01-16 07:57:49 -08:00
if (entries == nullptr) {
2022-05-19 01:51:26 -07:00
goto err;
}
memcpy(&(entries[gEndgameDeathEndingsLength]), &entry, sizeof(entry));
gEndgameDeathEndings = entries;
gEndgameDeathEndingsLength++;
}
fileClose(stream);
return 0;
err:
fileClose(stream);
return -1;
}
// 0x440BA8
int endgameDeathEndingExit()
{
2024-01-16 07:57:49 -08:00
if (gEndgameDeathEndings != nullptr) {
2022-05-19 01:51:26 -07:00
internal_free(gEndgameDeathEndings);
2024-01-16 07:57:49 -08:00
gEndgameDeathEndings = nullptr;
2022-05-19 01:51:26 -07:00
gEndgameDeathEndingsLength = 0;
}
return 0;
}
// endgameSetupDeathEnding
// 0x440BD0
void endgameSetupDeathEnding(int reason)
{
if (!gEndgameDeathEndingsLength) {
debugPrint("\nError: endgameSetupDeathEnding: No endgame death info!");
return;
}
// Build death ending file path.
strcpy(gEndgameDeathEndingFileName, "narrator\\");
int percentage = 0;
endgameDeathEndingValidate(&percentage);
int selectedEnding = 0;
bool specialEndingSelected = false;
switch (reason) {
case ENDGAME_DEATH_ENDING_REASON_DEATH:
if (gameGetGlobalVar(GVAR_MODOC_SHITTY_DEATH) != 0) {
selectedEnding = 12;
specialEndingSelected = true;
}
break;
case ENDGAME_DEATH_ENDING_REASON_TIMEOUT:
gameMoviePlay(MOVIE_TIMEOUT, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC);
break;
}
if (!specialEndingSelected) {
int chance = randomBetween(0, percentage);
int accum = 0;
for (int index = 0; index < gEndgameDeathEndingsLength; index++) {
EndgameDeathEnding* deathEnding = &(gEndgameDeathEndings[index]);
if (deathEnding->enabled) {
accum += deathEnding->percentage;
if (accum >= chance) {
break;
}
selectedEnding++;
}
}
}
EndgameDeathEnding* deathEnding = &(gEndgameDeathEndings[selectedEnding]);
strcat(gEndgameDeathEndingFileName, deathEnding->voiceOverBaseName);
debugPrint("\nendgameSetupDeathEnding: Death Filename Picked: %s", gEndgameDeathEndingFileName);
}
// Validates conditions imposed by death endings.
//
// Upon return [percentage] is set as a sum of all valid endings' percentages.
// Always returns 0.
//
// 0x440CF4
2022-06-18 06:35:38 -07:00
static int endgameDeathEndingValidate(int* percentage)
2022-05-19 01:51:26 -07:00
{
*percentage = 0;
for (int index = 0; index < gEndgameDeathEndingsLength; index++) {
EndgameDeathEnding* deathEnding = &(gEndgameDeathEndings[index]);
deathEnding->enabled = false;
if (deathEnding->gvar != -1) {
if (gameGetGlobalVar(deathEnding->gvar) >= deathEnding->value) {
continue;
}
}
if (deathEnding->worldAreaKnown != -1) {
2022-09-15 01:42:02 -07:00
if (!wmAreaIsKnown(deathEnding->worldAreaKnown)) {
2022-05-19 01:51:26 -07:00
continue;
}
}
if (deathEnding->worldAreaNotKnown != -1) {
2022-09-15 01:42:02 -07:00
if (wmAreaIsKnown(deathEnding->worldAreaNotKnown)) {
2022-05-19 01:51:26 -07:00
continue;
}
}
if (pcGetStat(PC_STAT_LEVEL) < deathEnding->min_level) {
continue;
}
deathEnding->enabled = true;
*percentage += deathEnding->percentage;
}
return 0;
}
// Returns file name for voice over for death ending.
//
// This path does not include extension.
//
// 0x440D8C
char* endgameDeathEndingGetFileName()
{
if (gEndgameDeathEndingsLength == 0) {
debugPrint("\nError: endgameSetupDeathEnding: No endgame death info!");
strcpy(gEndgameDeathEndingFileName, "narrator\\nar_4");
}
debugPrint("\nendgameSetupDeathEnding: Death Filename: %s", gEndgameDeathEndingFileName);
return gEndgameDeathEndingFileName;
}
2022-09-23 05:43:44 -07:00
void endgameEndingUpdateOverlay()
{
bufferFill(windowGetBuffer(gEndgameEndingOverlay),
windowGetWidth(gEndgameEndingOverlay),
windowGetHeight(gEndgameEndingOverlay),
windowGetWidth(gEndgameEndingOverlay),
intensityColorTable[_colorTable[0]][0]);
windowRefresh(gEndgameEndingOverlay);
}
2022-09-23 05:43:44 -07:00
} // namespace fallout