SWORD1: PSX: Implement credits

The PSX version doesn't have a credits Smacker video
but has a CREDITS.DAT file containing credits strings which
have to be displayed as a scrolling list of names and roles.
This commit is contained in:
AndywinXp 2023-10-16 11:18:22 +02:00
parent e19ac44440
commit 5daae32032
5 changed files with 408 additions and 13 deletions

View File

@ -3312,4 +3312,374 @@ const uint8 Control::_mediaHouseLanguageStrings[20][43] = {
"DRIVE FULL!",
};
/* ---------- PSX CREDITS CODE ---------- */
int32 Control::getCreditsStringLength(uint8 *str, uint8 *font) {
int32 width = 0;
FrameHeader *f;
while (*str) {
f = (FrameHeader *)_resMan->fetchFrame(font, *str - 32);
width += f->width;
str++;
if (*str)
width += PSX_CREDITS_SPACING;
}
return width;
}
int32 Control::getCreditsFontHeight(uint8 *font) {
FrameHeader *f;
f = (FrameHeader *)_resMan->fetchFrame(font, 'A' - 32);
return (f->height / 2);
}
void Control::createCreditsTextSprite(uint8 *data, int32 pitch, uint8 *str, uint8 *font) {
uint16 x = 0;
FrameHeader *f;
uint8 *src, *dst;
while (*str) {
f = (FrameHeader *)_resMan->fetchFrame(font, *str - 32);
src = (uint8 *)f + sizeof(FrameHeader);
dst = data + x;
for (int i = 0; i < f->height / 2; i++) {
memcpy(dst, src, f->width);
src += f->width;
dst += pitch;
}
x += (f->width + PSX_CREDITS_SPACING);
str++;
}
}
void Control::renderCreditsTextSprite(uint8 *data, uint8 *screenBuf, int16 x, int16 y, int16 width, int16 height) {
// Coordinates corrections from disasm
// (remember that the PSX framebuffer is bigger than our target 640x480 screen)
x -= 129;
y -= 72;
// Boundary checks
if (x >= SCREEN_WIDTH || y >= SCREEN_FULL_DEPTH)
return;
if (x + width <= 0 || y + height <= 0)
return;
// Are there rows outside the screen?
// Calculate how many doubled rows of the sprite are outside the screen on the top
int16 skippedDoubledRows = (y < 0) ? -y : 0;
int16 skippedRowsInData = skippedDoubledRows / 2;
data += width * skippedRowsInData; // Adjust data pointer based on the number of skipped rows in the sprite
height -= skippedDoubledRows; // Adjust height based on the number of skipped doubled rows
if (y < 0) {
y = 0;
}
uint8 *dst = screenBuf + x + SCREEN_WIDTH * y;
for (int i = 0; i < height; i += 2) { // Increment by 2 for the doubled height
// Boundary check for y
if (y + i >= SCREEN_FULL_DEPTH)
break;
// First horizontal line
for (int j = 0; j < width; j++) {
// Boundary checks for x
if (x + j < 0)
continue;
if (x + j >= SCREEN_WIDTH)
break;
if (data[j])
dst[j] = data[j];
}
dst += SCREEN_WIDTH;
// Second horizontal line (duplicated)
for (int j = 0; j < width; j++) {
// Boundary checks for x
if (x + j < 0)
continue;
if (x + j >= SCREEN_WIDTH)
break;
if (data[j])
dst[j] = data[j];
}
dst += SCREEN_WIDTH; // Move to the next line
data += width; // Move to the next row of source sprite
}
}
void Control::psxEndCredits() {
int16 h;
int16 nextCredit = PSX_NUM_CREDITS + 1;
uint8 *creditLine = nullptr;
uint8 *titleLine = nullptr;
int32 *creditData = nullptr;
Common::File creditsFile;
int32 creditsFileSize = 0;
int32 totalCreditsNum = 0;
uint8 *creditSprite[PSX_NUM_CREDITS];
uint8 *titleSprite[PSX_NUM_CREDITS];
int16 creditWidth[PSX_NUM_CREDITS];
int16 titleWidth[PSX_NUM_CREDITS];
int16 creditsHeight[PSX_NUM_CREDITS] = {
400, 440, 480, 520, 560, 600, 640,
680, 720, 760, 800, 840, 880, 920
};
for (int i = 0; i < PSX_NUM_CREDITS; i++) {
creditSprite[i] = nullptr;
titleSprite[i] = nullptr;
creditWidth[i] = 0;
titleWidth[i] = 0;
}
// If we're here, the resource is already there, no need to open it
uint8 *font = (uint8 *)_resMan->fetchRes(GAME_FONT);
switch (SwordEngine::_systemVars.language) {
case BS1_ENGLISH:
totalCreditsNum = 101;
creditsFileSize = 2798;
break;
case BS1_GERMAN:
totalCreditsNum = 83;
creditsFileSize = 2382;
break;
case BS1_FRENCH:
totalCreditsNum = 83;
creditsFileSize = 2382;
break;
case BS1_SPANISH:
totalCreditsNum = 83;
creditsFileSize = 2412;
break;
case BS1_ITALIAN:
totalCreditsNum = 101;
creditsFileSize = 2823;
break;
default:
totalCreditsNum = 101;
creditsFileSize = 2798;
}
_sound->clearAllFx();
_screen->startFadePaletteUp(1);
for (int i = 0; i < PSX_NUM_CREDITS; i++)
creditsHeight[i] = 400 + i * 40;
h = getCreditsFontHeight(font);
_screen->fnSetFadeTargetPalette(193, 1, 0, TEXT_WHITE);
_sound->streamMusicFile(101, 1);
_sound->updateMusicStreaming();
uint8 *creditsScreenBuf = (uint8 *)malloc(SCREEN_WIDTH * SCREEN_FULL_DEPTH);
if (!creditsScreenBuf) {
warning("Control::psxEndCredits(): Couldn't allocate memory for credits screen buffer");
return;
}
memset(creditsScreenBuf, 0, SCREEN_WIDTH * SCREEN_FULL_DEPTH);
creditData = (int32 *)malloc(creditsFileSize);
if (!creditData) {
warning("Control::psxEndCredits(): Couldn't allocate memory for text data");
free(creditsScreenBuf);
return;
}
if (!creditsFile.exists("CREDITS.DAT")) {
debug(2, "Control::psxEndCredits(): Couldn't find CREDITS.DAT");
free(creditsScreenBuf);
free(creditData);
return;
}
creditsFile.open("CREDITS.DAT");
if (!creditsFile.isOpen()) {
debug(2, "Control::psxEndCredits(): Couldn't open CREDITS.DAT");
free(creditsScreenBuf);
free(creditData);
return;
}
creditsFile.read(creditData, creditsFileSize);
creditsFile.close();
bool allSet = true;
for (int i = 0; i < PSX_NUM_CREDITS; i++) {
_sound->updateMusicStreaming();
_sound->setCrossFadeIncrement();
creditLine = ((uint8 *)creditData + creditData[i + totalCreditsNum]);
titleLine = ((uint8 *)creditData + creditData[i]);
creditWidth[i] = (getCreditsStringLength(creditLine, font) + 1) & 0xFFFE;
titleWidth[i] = (getCreditsStringLength(titleLine, font) + 1) & 0xFFFE;
if (creditWidth[i]) {
creditSprite[i] = (uint8 *)malloc(h * creditWidth[i]);
if (!creditSprite[i]) {
warning("Control::psxEndCredits(): Couldn't allocate memory for text sprites");
allSet = false;
break; // Break so the clean-up code is executed
}
memset(creditSprite[i], 0, h * creditWidth[i]);
} else {
creditSprite[i] = nullptr;
}
if (titleWidth[i]) {
titleSprite[i] = (uint8 *)malloc(h * titleWidth[i]);
if (!titleSprite[i]) {
warning("Control::psxEndCredits(): Couldn't allocate memory for text sprites");
allSet = false;
break; // Break so the clean-up code is executed
}
memset(titleSprite[i], 0, h * titleWidth[i]);
} else {
titleSprite[i] = nullptr;
}
createCreditsTextSprite(creditSprite[i], creditWidth[i], creditLine, font);
createCreditsTextSprite(titleSprite[i], titleWidth[i], titleLine, font);
}
_keyPressed.reset();
while (allSet && creditsHeight[PSX_NUM_CREDITS - 1] > -120 &&
!Engine::shouldQuit() &&
_keyPressed.keycode != Common::KEYCODE_ESCAPE) {
memset(creditsScreenBuf, 0, SCREEN_WIDTH * SCREEN_FULL_DEPTH);
for (int i = 0; i < PSX_NUM_CREDITS; i++) {
// Name
renderCreditsTextSprite(
creditSprite[i],
creditsScreenBuf,
PSX_CREDITS_MIDDLE + Logic::_scriptVars[SCROLL_OFFSET_X],
PSX_CREDITS_OFFSET + creditsHeight[i],
creditWidth[i],
h * 2);
// Role
renderCreditsTextSprite(
titleSprite[i],
creditsScreenBuf,
PSX_CREDITS_MIDDLE + Logic::_scriptVars[SCROLL_OFFSET_X] - 30 - titleWidth[i],
PSX_CREDITS_OFFSET + creditsHeight[i],
titleWidth[i],
h * 2);
creditsHeight[i] -= 2;
}
_system->copyRectToScreen(creditsScreenBuf, SCREEN_WIDTH, 0, 0, SCREEN_WIDTH, SCREEN_FULL_DEPTH);
delay(33); // Run credits at about 30 FPS
// Always remember to update sound :-)
_sound->updateMusicStreaming();
_sound->setCrossFadeIncrement();
// Scroll the credits!
if (creditsHeight[0] < -120) {
if (nextCredit <= totalCreditsNum) {
for (int i = 0; i < PSX_NUM_CREDITS; i++) {
creditsHeight[i] += 40;
}
if (creditSprite[0] != nullptr)
free(creditSprite[0]);
if (titleSprite[0] != nullptr)
free(titleSprite[0]);
for (int i = 0; i < PSX_NUM_CREDITS - 1; i++) {
creditSprite[i] = creditSprite[i + 1];
titleSprite[i] = titleSprite[i + 1];
creditWidth[i] = creditWidth[i + 1];
titleWidth[i] = titleWidth[i + 1];
}
creditLine = ((uint8 *)creditData + creditData[nextCredit - 1 + totalCreditsNum]);
titleLine = ((uint8 *)creditData + creditData[nextCredit - 1]);
creditWidth[PSX_NUM_CREDITS - 1] = (getCreditsStringLength(creditLine, font) + 1) & 0xFFFE;
titleWidth[PSX_NUM_CREDITS - 1] = (getCreditsStringLength(titleLine, font) + 1) & 0xFFFE;
if (creditWidth[PSX_NUM_CREDITS - 1]) {
creditSprite[PSX_NUM_CREDITS - 1] = (uint8 *)malloc(h * creditWidth[PSX_NUM_CREDITS - 1]);
if (!creditSprite[PSX_NUM_CREDITS - 1]) {
warning("Control::psxEndCredits(): Couldn't allocate memory for text sprites");
break;
}
memset(creditSprite[PSX_NUM_CREDITS - 1], 0, h * creditWidth[PSX_NUM_CREDITS - 1]);
} else {
creditSprite[PSX_NUM_CREDITS - 1] = nullptr;
}
if (titleWidth[PSX_NUM_CREDITS - 1]) {
titleSprite[PSX_NUM_CREDITS - 1] = (uint8 *)malloc(h * titleWidth[PSX_NUM_CREDITS - 1]);
if (!titleSprite[PSX_NUM_CREDITS - 1]) {
warning("Control::psxEndCredits(): Couldn't allocate memory for text sprites");
break;
}
memset(titleSprite[PSX_NUM_CREDITS - 1], 0, h * titleWidth[PSX_NUM_CREDITS - 1]);
} else {
titleSprite[PSX_NUM_CREDITS - 1] = nullptr;
}
createCreditsTextSprite(creditSprite[PSX_NUM_CREDITS - 1], creditWidth[PSX_NUM_CREDITS - 1], creditLine, font);
createCreditsTextSprite(titleSprite[PSX_NUM_CREDITS - 1], titleWidth[PSX_NUM_CREDITS - 1], titleLine, font);
nextCredit += 1;
}
}
}
for (int i = 0; i < PSX_NUM_CREDITS; i++) {
if (creditSprite[i] != nullptr)
free(creditSprite[i]);
if (titleSprite[i] != nullptr)
free(titleSprite[i]);
}
free(creditData);
_screen->startFadePaletteDown(1);
_vm->waitForFade();
memset(creditsScreenBuf, 0, SCREEN_WIDTH * SCREEN_FULL_DEPTH);
_system->copyRectToScreen(creditsScreenBuf, SCREEN_WIDTH, 0, 0, SCREEN_WIDTH, SCREEN_FULL_DEPTH);
free(creditsScreenBuf);
_keyPressed.reset();
}
} // End of namespace Sword1

View File

@ -105,6 +105,11 @@ class Logic;
#define SP_OVERLAP 2
#define TEXTBUTTONID 7
#define PSX_CREDITS_SPACING (-3)
#define PSX_CREDITS_MIDDLE 450
#define PSX_CREDITS_OFFSET 150
#define PSX_NUM_CREDITS 14
struct Button {
int32 x1;
int32 y1;
@ -126,6 +131,7 @@ public:
void checkForOldSaveGames();
bool isPanelShown();
const uint8 *getPauseString();
void psxEndCredits();
void setSaveDescription(int slot, const char *desc) {
Common::strcpy_s((char *)_fileDescriptions[slot], sizeof(_fileDescriptions[slot]), desc);
@ -198,6 +204,12 @@ private:
int displayMessage(const char *altButton, MSVC_PRINTF const char *message, ...) GCC_PRINTF(3, 4);
// PSX Credits functions
int32 getCreditsFontHeight(uint8 *font);
int32 getCreditsStringLength(uint8 *str, uint8 *font);
void renderCreditsTextSprite(uint8 *data, uint8 *dst, int16 x, int16 y, int16 width, int16 height);
void createCreditsTextSprite(uint8 *data, int32 pitch, uint8 *str, uint8 *font);
Common::MemoryWriteStreamDynamic *_tempThumbnail;
static const uint8 _languageStrings[8 * 20][43];
static const uint8 _akellaLanguageStrings[20][43];

View File

@ -95,6 +95,10 @@ void Logic::initialize() {
SwordEngine::_systemVars.speechFinished = true;
}
void Logic::setControlPanelObject(Control *control) {
_control = control;
}
void Logic::newScreen(uint32 screen) {
Object *compact = (Object *)_objMan->fetchObject(PLAYER);
@ -984,21 +988,26 @@ int Logic::fnPlaySequence(Object *cpt, int32 id, int32 sequenceId, int32 d, int3
// meantime, we don't want any looping sound effects still playing.
_sound->clearAllFx();
MoviePlayer *player = makeMoviePlayer(sequenceId, _vm, _textMan, _resMan, _sound, _system);
if (player) {
_screen->clearScreen();
if (player->load(sequenceId))
player->play();
delete player;
if (SwordEngine::isPsx() && sequenceId == 19) {
_control->psxEndCredits();
} else {
MoviePlayer *player = makeMoviePlayer(sequenceId, _vm, _textMan, _resMan, _sound, _system);
if (player) {
_screen->clearScreen();
if (player->load(sequenceId))
player->play();
delete player;
// In some instances, when you start a video when the palette is still fading
// and the video is finished earlier, another palette fade(-out) is performed with the
// wrong palette. This happens when traveling to Spain or Ireland. It couldn't happen
// in the original, as it asked for the CD before loading the scene.
// Let's fix this by forcing a black fade palette on the next fade out. If a fade-in
// is then scheduled, we will clear the flag without doing anything different from the usual.
_screen->setNextFadeOutToBlack();
// In some instances, when you start a video when the palette is still fading
// and the video is finished earlier, another palette fade(-out) is performed with the
// wrong palette. This happens when traveling to Spain or Ireland. It couldn't happen
// in the original, as it asked for the CD before loading the scene.
// Let's fix this by forcing a black fade palette on the next fade out. If a fade-in
// is then scheduled, we will clear the flag without doing anything different from the usual.
_screen->setNextFadeOutToBlack();
}
}
return SCRIPT_CONT;
}

View File

@ -47,6 +47,7 @@ class Menu;
class Router;
class Screen;
class Mouse;
class Control;
class Logic;
typedef int (Logic::*BSMcodeTable)(Object *, int32, int32, int32, int32, int32, int32, int32);
@ -57,6 +58,7 @@ public:
Logic(SwordEngine *vm, ObjectMan *pObjMan, ResMan *resMan, Screen *pScreen, Mouse *pMouse, Sound *pSound, Menu *pMenu, OSystem *system, Audio::Mixer *mixer);
~Logic();
void initialize();
void setControlPanelObject(Control *control);
void newScreen(uint32 screen);
void engine();
void updateScreenParams();
@ -81,6 +83,7 @@ private:
Text *_textMan;
EventManager *_eventMan;
Menu *_menu;
Control *_control;
uint32 _newScript; // <= ugly, but I can't avoid it.
uint8 _speechClickDelay = 0;
Common::RandomSource _rnd;

View File

@ -172,6 +172,7 @@ Common::Error SwordEngine::init() {
_objectMan->initialize();
_mouse->initialize();
_control = new Control(this, _saveFileMan, _resMan, _objectMan, _system, _mouse, _sound, _screen, _logic);
_logic->setControlPanelObject(_control);
return Common::kNoError;
}