Compare commits

...
Author SHA1 Message Date
cambragol 3822e25201 Update inventory.cc
Added a comment regarding the button flaw on Inventory, Use On and Loot screens.

Also noted that scaling and flipping the main menu button is a temp solution/stop-gap measure.
2025-05-25 02:21:55 +09:00
Mike Klaas 2ba6cbee3a Merge branch 'Splash-and-Help-stretching' into Inventory-Buttons-Fix 2025-05-21 09:02:35 -07:00
cambragol cb90129ebd Merge branch 'main' into Inventory-Buttons-Fix 2025-05-21 09:16:34 +09:00
cambragol 13d5fd2e83 Update inventory.cc
flipped buttons to match top right light source
2025-05-21 08:39:17 +09:00
cambragol 7c3cbbac05 Update inventory.cc 2025-05-19 21:52:43 +09:00
cambragol a5a317ab40 Update inventory.cc 2025-05-19 21:42:52 +09:00
cambragol 372483a5ca Update game.cc
synchronize some settings
2025-05-18 17:00:52 +09:00
cambragol d46549dd11 Update game.cc 2025-05-18 16:43:26 +09:00
cambragol c6970e35de Update draw.h 2025-05-18 16:40:13 +09:00
cambragol 68e7d3e903 Update draw.cc 2025-05-18 16:39:23 +09:00
4 changed files with 310 additions and 119 deletions
+51
View File
@@ -1,6 +1,8 @@
#include "draw.h"
#include <cstring>
#include <string.h>
#include <vector>
#include "color.h"
#include "svga.h"
@@ -339,4 +341,53 @@ void transSrcCopy(unsigned char* dest, int destPitch, unsigned char* src, int sr
}
}
void blitBufferToBufferStretchAndFixEdges(
unsigned char* src, int srcW, int srcH, int srcPitch,
unsigned char* dst, int dstW, int dstH, int dstPitch,
int numStates)
{
// Temp buffer for over-stretched result (per state)
const int stretchW = dstW + 1;
const int stretchH = dstH + 1;
const int stretchPitch = stretchW;
std::vector<unsigned char> tempBuffer(stretchW * stretchH * numStates);
// Stretch all states to slightly larger temp buffer
blitBufferToBufferStretch(
src, srcW, srcH * numStates, srcPitch,
tempBuffer.data(), stretchW, stretchH * numStates, stretchPitch);
// Now copy only the top-left dstW x dstH portion per state into the final buffer
for (int state = 0; state < numStates; ++state) {
unsigned char* tempFrame = tempBuffer.data() + stretchW * stretchH * state;
unsigned char* finalFrame = dst + dstW * dstH * state;
for (int y = 0; y < dstH; ++y) {
memcpy(
finalFrame + y * dstW,
tempFrame + y * stretchPitch,
dstW);
}
}
}
void calculateScaledSize(int srcWidth, int srcHeight, int targetWidth, int targetHeight, int mode, int& outWidth, int& outHeight)
{
if (mode == 2) {
// Fullscreen stretch
outWidth = targetWidth;
outHeight = targetHeight;
} else {
// Maintain aspect ratio
if (targetHeight * srcWidth >= targetWidth * srcHeight) {
outWidth = targetWidth;
outHeight = (targetWidth * srcHeight) / srcWidth;
} else {
outWidth = (targetHeight * srcWidth) / srcHeight;
outHeight = targetHeight;
}
}
}
} // namespace fallout
+5
View File
@@ -17,6 +17,11 @@ void _swap_color_buf(unsigned char* buf, int width, int height, int pitch, int c
void bufferOutline(unsigned char* buf, int width, int height, int pitch, int a5);
void srcCopy(unsigned char* dest, int destPitch, unsigned char* src, int srcPitch, int width, int height);
void transSrcCopy(unsigned char* dest, int destPitch, unsigned char* src, int srcPitch, int width, int height);
void blitBufferToBufferStretchAndFixEdges(
unsigned char* src, int srcW, int srcH, int srcPitch,
unsigned char* dst, int dstW, int dstH, int dstPitch,
int numStates = 1);
void calculateScaledSize(int srcWidth, int srcHeight, int targetWidth, int targetHeight, int mode, int& outWidth, int& outHeight);
} // namespace fallout
+180 -99
View File
@@ -168,6 +168,14 @@ int gameInitWithOptions(const char* windowTitle, bool isMapper, int font, int a4
int skipOpeningMovies = 0;
configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_SKIP_OPENING_MOVIES_KEY, &skipOpeningMovies);
// load preferences before Splash screen to get proper brightness
if (_init_options_menu() != 0) {
debugPrint("Failed on init_options_menu\n");
return -1;
}
debugPrint(">init_options_menu\n");
if (!gIsMapper && skipOpeningMovies < 2) {
showSplash();
}
@@ -341,7 +349,7 @@ int gameInitWithOptions(const char* windowTitle, bool isMapper, int font, int a4
debugPrint(">scr_disable\t");
if (_init_options_menu() != 0) {
if (_init_options_menu() != 0) {
debugPrint("Failed on init_options_menu\n");
return -1;
}
@@ -1193,71 +1201,139 @@ static void gameFreeGlobalVars()
// 0x443F74
static void showHelp()
{
ScopedGameMode gm(GameMode::kHelp);
ScopedGameMode gm(GameMode::kHelp); // Switch to Help game mode.
bool isoWasEnabled = isoDisable();
gameMouseObjectsHide();
gameMouseSetCursor(MOUSE_CURSOR_NONE);
bool colorCycleWasEnabled = colorCycleEnabled();
colorCycleDisable();
// CE: Help screen uses separate color palette which is incompatible with
// colors in other windows. Setup overlay to hide everything.
int overlay = windowCreate(0, 0, screenGetWidth(), screenGetHeight(), 0, WINDOW_HIDDEN | WINDOW_MOVE_ON_TOP);
// load the optional stretch setting from f2_res.ini
int helpScreenStretchMode = 0;
int stretchGameMode = 0;
Config config;
if (configInit(&config)) {
if (configRead(&config, "f2_res.ini", false)) {
configGetInt(&config, "STATIC_SCREENS", "HELP_SCRN_SIZE", &helpScreenStretchMode);
int helpWindowX = (screenGetWidth() - HELP_SCREEN_WIDTH) / 2;
int helpWindowY = (screenGetHeight() - HELP_SCREEN_HEIGHT) / 2;
int win = windowCreate(helpWindowX, helpWindowY, HELP_SCREEN_WIDTH, HELP_SCREEN_HEIGHT, 0, WINDOW_HIDDEN | WINDOW_MOVE_ON_TOP);
if (configGetInt(&config, "STATIC_SCREENS", "STRETCH_GAME", &stretchGameMode)) {
helpScreenStretchMode = stretchGameMode; // always override if key exists
}
}
configFree(&config);
}
int screenWidth = screenGetWidth();
int screenHeight = screenGetHeight();
// Create an invisible fullscreen overlay window to cover everything.
int overlay = windowCreate(0, 0, screenWidth, screenHeight, 0, WINDOW_HIDDEN | WINDOW_MOVE_ON_TOP);
FrmImage helpFrmImage;
int helpFid = buildFid(OBJ_TYPE_INTERFACE, 297, 0, 0, 0); // Load Help screen graphic (FID 297).
if (!helpFrmImage.lock(helpFid)) {
if (colorCycleWasEnabled)
colorCycleEnable();
gameMouseObjectsShow();
if (isoWasEnabled)
isoEnable();
return;
}
unsigned char* helpData = helpFrmImage.getData();
int helpWidth = HELP_SCREEN_WIDTH;
int helpHeight = HELP_SCREEN_HEIGHT;
unsigned char* bufferToBlit = helpData;
int blitWidth = helpWidth;
int blitHeight = helpHeight;
// Check if stretching is needed (based on config or screen too small).
if (helpScreenStretchMode != 0 || screenWidth < helpWidth || screenHeight < helpHeight) {
int scaledWidth, scaledHeight;
calculateScaledSize(
helpWidth,
helpHeight,
screenWidth,
screenHeight,
helpScreenStretchMode,
scaledWidth,
scaledHeight);
// Allocate memory for the scaled image.
unsigned char* scaled = reinterpret_cast<unsigned char*>(internal_malloc((scaledWidth + 1) * (scaledHeight + 1)));
if (scaled != nullptr) {
// Stretch and fix the original help image into the new buffer.
blitBufferToBufferStretchAndFixEdges(
helpData, helpWidth, helpHeight, helpWidth, // Source buffer and dimensions
scaled, scaledWidth, scaledHeight, scaledWidth, // Destination buffer and dimensions
1 // numStates = 1 for a single image
);
bufferToBlit = scaled;
blitWidth = scaledWidth;
blitHeight = scaledHeight;
}
}
// Center the window on the screen.
int x = (screenWidth - blitWidth) / 2;
int y = (screenHeight - blitHeight) / 2;
// Create window for the help screen.
int win = windowCreate(x, y, blitWidth, blitHeight, 0, WINDOW_HIDDEN | WINDOW_MOVE_ON_TOP);
if (win != -1) {
unsigned char* windowBuffer = windowGetBuffer(win);
if (windowBuffer != nullptr) {
FrmImage backgroundFrmImage;
int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, 297, 0, 0, 0);
if (backgroundFrmImage.lock(backgroundFid)) {
paletteSetEntries(gPaletteBlack);
blitBufferToBuffer(backgroundFrmImage.getData(), HELP_SCREEN_WIDTH, HELP_SCREEN_HEIGHT, HELP_SCREEN_WIDTH, windowBuffer, HELP_SCREEN_WIDTH);
// Clear palette to black before displaying.
paletteSetEntries(gPaletteBlack);
colorPaletteLoad("art\\intrface\\helpscrn.pal");
paletteSetEntries(_cmap);
// Copy the help image into the window.
blitBufferToBuffer(bufferToBlit, blitWidth, blitHeight, blitWidth, windowBuffer, blitWidth);
// CE: Fill overlay with darkest color in the palette. It might
// not be completely black, but at least it's uniform.
bufferFill(windowGetBuffer(overlay),
screenGetWidth(),
screenGetHeight(),
screenGetWidth(),
intensityColorTable[_colorTable[0]][0]);
// Load the help screen's specific palette.
colorPaletteLoad("art\\intrface\\helpscrn.pal");
paletteSetEntries(_cmap);
windowShow(overlay);
windowShow(win);
// Fill the overlay with the darkest palette color to hide everything behind.
bufferFill(windowGetBuffer(overlay), screenWidth, screenHeight, screenWidth, intensityColorTable[_colorTable[0]][0]);
while (inputGetInput() == -1 && _game_user_wants_to_quit == 0) {
sharedFpsLimiter.mark();
renderPresent();
sharedFpsLimiter.throttle();
}
windowShow(overlay);
windowShow(win);
while (mouseGetEvent() != 0) {
sharedFpsLimiter.mark();
inputGetInput();
renderPresent();
sharedFpsLimiter.throttle();
}
paletteSetEntries(gPaletteBlack);
while (inputGetInput() == -1 && _game_user_wants_to_quit == 0) {
sharedFpsLimiter.mark();
renderPresent();
sharedFpsLimiter.throttle();
}
while (mouseGetEvent() != 0) {
sharedFpsLimiter.mark();
inputGetInput();
renderPresent();
sharedFpsLimiter.throttle();
}
// Fade back to black.
paletteSetEntries(gPaletteBlack);
}
// Cleanup
windowDestroy(overlay);
windowDestroy(win);
colorPaletteLoad("color.pal");
paletteSetEntries(_cmap);
}
// Free temporary scaled buffer if it was allocated.
if (bufferToBlit != helpData) {
internal_free(bufferToBlit);
}
// Restore previous state.
if (colorCycleWasEnabled) {
colorCycleEnable();
}
@@ -1371,7 +1447,7 @@ static int gameDbInit()
char* path_file_name_template = nullptr;
configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_PATCH_FILE, &path_file_name_template);
if (path_file_name_template == nullptr || *path_file_name_template == '\0') {
path_file_name_template = (char*)"patch%03d.dat";
path_file_name_template = "patch%03d.dat";
}
for (patch_index = 0; patch_index < 1000; patch_index++) {
@@ -1394,8 +1470,9 @@ static int gameDbInit()
// 0x444384
static void showSplash()
{
int splash = settings.system.splash;
int splashIndex = settings.system.splash;
// path to local splash folder
char path[64];
const char* language = settings.system.language.c_str();
if (compat_stricmp(language, ENGLISH) != 0) {
@@ -1404,19 +1481,19 @@ static void showSplash()
snprintf(path, sizeof(path), "art\\splash\\");
}
File* stream;
for (int index = 0; index < SPLASH_COUNT; index++) {
// open a splash file, cycle through SPLASH_COUNT entries
File* stream = nullptr;
for (int attempt = 0; attempt < SPLASH_COUNT; attempt++) {
char filePath[64];
snprintf(filePath, sizeof(filePath), "%ssplash%d.rix", path, splash);
snprintf(filePath, sizeof(filePath), "%ssplash%d.rix", path, splashIndex);
stream = fileOpen(filePath, "rb");
if (stream != nullptr) {
break;
}
splash++;
if (splash >= SPLASH_COUNT) {
splash = 0;
splashIndex++;
if (splashIndex >= SPLASH_COUNT) {
splashIndex = 0;
}
}
@@ -1424,105 +1501,109 @@ static void showSplash()
return;
}
// Allocate memory for the palette
unsigned char* palette = reinterpret_cast<unsigned char*>(internal_malloc(768));
if (palette == nullptr) {
fileClose(stream);
return;
}
// Verify RIX image version (needed?)
int version;
fileReadInt32(stream, &version);
if (version != 'RIX3') {
fileClose(stream);
internal_free(palette);
return;
}
short width;
fileRead(&width, sizeof(width), 1, stream);
// Read image dimensions
short splashWidth, splashHeight;
fileRead(&splashWidth, sizeof(splashWidth), 1, stream);
fileRead(&splashHeight, sizeof(splashHeight), 1, stream);
short height;
fileRead(&height, sizeof(height), 1, stream);
unsigned char* data = reinterpret_cast<unsigned char*>(internal_malloc(width * height));
if (data == nullptr) {
// Allocate memory for the image pixel data
unsigned char* splashData = reinterpret_cast<unsigned char*>(internal_malloc(splashWidth * splashHeight));
if (splashData == nullptr) {
internal_free(palette);
fileClose(stream);
return;
}
// Load palette and image data
paletteSetEntries(gPaletteBlack);
fileSeek(stream, 10, SEEK_SET);
fileRead(palette, 1, 768, stream);
fileRead(data, 1, width * height, stream);
fileRead(splashData, 1, splashWidth * splashHeight, stream);
fileClose(stream);
// Fix of wrong Palette, without it this makes background bright
// Basically just swapping first and last colors, this problem presented ONLY in F2, F1 has right palette in every splash
memcpy(palette + (255 * 3), palette, 3);
memset(palette, 0, 3);
for (int i = 0; i < width * height; i++) {
if (data[i] == 0) {
data[i] = 255;
} else if (data[i] == 255) {
data[i] = 0;
}
}
int size = 0;
// TODO: Move to settings.
int splashScreenStretchMode = 0;
int stretchGameMode = 0;
Config config;
if (configInit(&config)) {
if (configRead(&config, "f2_res.ini", false)) {
configGetInt(&config, "STATIC_SCREENS", "SPLASH_SCRN_SIZE", &size);
}
configGetInt(&config, "STATIC_SCREENS", "SPLASH_SCRN_SIZE", &splashScreenStretchMode);
if (configGetInt(&config, "STATIC_SCREENS", "STRETCH_GAME", &stretchGameMode)) {
splashScreenStretchMode = stretchGameMode; // always override if key exists
}
}
configFree(&config);
}
int screenWidth = screenGetWidth();
int screenHeight = screenGetHeight();
if (size != 0 || screenWidth < width || screenHeight < height) {
int scaledWidth;
int scaledHeight;
// Stretch image if required or if it doesn't fit the screen
if (splashScreenStretchMode != 0 || screenWidth < splashWidth || screenHeight < splashHeight) {
int scaledWidth, scaledHeight;
if (size == 2) {
scaledWidth = screenWidth;
scaledHeight = screenHeight;
} else {
if (screenHeight * width >= screenWidth * height) {
scaledWidth = screenWidth;
scaledHeight = screenWidth * height / width;
} else {
scaledWidth = screenHeight * width / height;
scaledHeight = screenHeight;
}
}
calculateScaledSize(
splashWidth,
splashHeight,
screenWidth,
screenHeight,
splashScreenStretchMode,
scaledWidth,
scaledHeight);
unsigned char* scaled = reinterpret_cast<unsigned char*>(internal_malloc(scaledWidth * scaledHeight));
unsigned char* scaled = reinterpret_cast<unsigned char*>(internal_malloc((scaledWidth) * (scaledHeight)));
if (scaled != nullptr) {
blitBufferToBufferStretch(data, width, height, width, scaled, scaledWidth, scaledHeight, scaledWidth);
// Stretch the splash screen and fix edge artifacts
blitBufferToBufferStretchAndFixEdges(
splashData,
splashWidth,
splashHeight,
splashWidth,
scaled,
scaledWidth,
scaledHeight,
scaledWidth,
1 // numStates = 1 for a single splash image
);
int x = (screenWidth > scaledWidth) ? (screenWidth - scaledWidth) / 2 : 0;
int y = (screenHeight > scaledHeight) ? (screenHeight - scaledHeight) / 2 : 0;
int x = screenWidth > scaledWidth ? (screenWidth - scaledWidth) / 2 : 0;
int y = screenHeight > scaledHeight ? (screenHeight - scaledHeight) / 2 : 0;
_scr_blit(scaled, scaledWidth, scaledHeight, 0, 0, scaledWidth, scaledHeight, x, y);
paletteFadeTo(palette);
internal_free(scaled);
}
} else {
int x = (screenWidth - width) / 2;
int y = (screenHeight - height) / 2;
_scr_blit(data, width, height, 0, 0, width, height, x, y);
// Center image without scaling
int x = (screenWidth - splashWidth) / 2;
int y = (screenHeight - splashHeight) / 2;
_scr_blit(splashData, splashWidth, splashHeight, 0, 0, splashWidth, splashHeight, x, y);
paletteFadeTo(palette);
}
internal_free(data);
// Clean up
internal_free(splashData);
internal_free(palette);
settings.system.splash = splash + 1;
// Save index for next splash image
settings.system.splash = splashIndex + 1;
}
int gameShowDeathDialog(const char* message)
+74 -20
View File
@@ -553,6 +553,15 @@ static int inventoryMessageListFree()
return 0;
}
// Helper function to flip the button buffer horizontally
void flipBufferHorizontally(unsigned char* src, int width, int height, int pitch) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width / 2; x++) {
std::swap(src[y * pitch + x], src[y * pitch + (width - 1 - x)]);
}
}
}
// 0x46E7B0
void inventoryOpen()
{
@@ -1009,60 +1018,105 @@ static bool _setup_inventory(int inventoryWindowType)
int fid;
int btn;
fid = buildFid(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);
fid = buildFid(OBJ_TYPE_INTERFACE, 299, 0, 0, 0);
_inventoryFrmImages[0].lock(fid);
fid = buildFid(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);
fid = buildFid(OBJ_TYPE_INTERFACE, 300, 0, 0, 0);
_inventoryFrmImages[1].lock(fid);
// Inventory, Use On, and Loot screens have incorrect buttons
// Game currently doesn't have the correct button resource
// We are going to scale and flip the menu buttons as a temporary solution
// Base button dimensions
int buttonBaseWidth = 26;
int buttonBaseHeight = 26;
int buttonWidth = 21;
int buttonHeight = 21;
unsigned char* scaledNormal = reinterpret_cast<unsigned char*>(SDL_malloc(21 * 21));
unsigned char* scaledPressed = reinterpret_cast<unsigned char*>(SDL_malloc(21 * 21));
if (!scaledNormal || !scaledPressed) {
return -1;
}
// Create flipped copies (to align highlights to top right light source)
unsigned char* flippedNormal = reinterpret_cast<unsigned char*>(SDL_malloc(buttonBaseWidth * buttonBaseHeight));
unsigned char* flippedPressed = reinterpret_cast<unsigned char*>(SDL_malloc(buttonBaseWidth * buttonBaseHeight));
if (!flippedNormal || !flippedPressed) {
return -1;
}
// Copy and flip the original images
memcpy(flippedNormal, _inventoryFrmImages[0].getData(), buttonBaseWidth * buttonBaseHeight);
memcpy(flippedPressed, _inventoryFrmImages[1].getData(), buttonBaseWidth * buttonBaseHeight);
// Flip them with the helper
flipBufferHorizontally(flippedNormal, buttonBaseWidth, buttonBaseHeight, buttonBaseWidth);
flipBufferHorizontally(flippedPressed, buttonBaseWidth, buttonBaseHeight, buttonBaseWidth);
// Use the flipped buffers as source and scale them
blitBufferToBufferStretchAndFixEdges(
flippedNormal, buttonBaseWidth, buttonBaseHeight, buttonBaseWidth,
scaledNormal, buttonWidth, buttonHeight, buttonWidth);
blitBufferToBufferStretchAndFixEdges(
flippedPressed, buttonBaseWidth, buttonBaseHeight, buttonBaseWidth,
scaledPressed, buttonWidth, buttonHeight, buttonWidth);
// Free the temp buffers
SDL_free(flippedNormal);
SDL_free(flippedPressed);
if (_inventoryFrmImages[0].isLocked() && _inventoryFrmImages[1].isLocked()) {
btn = -1;
switch (inventoryWindowType) {
case INVENTORY_WINDOW_TYPE_NORMAL:
// Done button
btn = buttonCreate(gInventoryWindow,
437,
329,
15,
16,
434,
325,
21,
21,
-1,
-1,
-1,
KEY_ESCAPE,
_inventoryFrmImages[0].getData(),
_inventoryFrmImages[1].getData(),
scaledNormal, // scaled and flipped button
scaledPressed, // scaled and flipped button
nullptr,
BUTTON_FLAG_TRANSPARENT);
break;
case INVENTORY_WINDOW_TYPE_USE_ITEM_ON:
// Cancel button
btn = buttonCreate(gInventoryWindow,
233,
328,
15,
16,
230,
325,
21,
21,
-1,
-1,
-1,
KEY_ESCAPE,
_inventoryFrmImages[0].getData(),
_inventoryFrmImages[1].getData(),
scaledNormal, // scaled and flipped button
scaledPressed, // scaled and flipped button
nullptr,
BUTTON_FLAG_TRANSPARENT);
break;
case INVENTORY_WINDOW_TYPE_LOOT:
// Done button
btn = buttonCreate(gInventoryWindow,
476,
331,
15,
16,
473,
327,
21,
21,
-1,
-1,
-1,
KEY_ESCAPE,
_inventoryFrmImages[0].getData(),
_inventoryFrmImages[1].getData(),
scaledNormal, // scaled and flipped button
scaledPressed, // scaled and flipped button
nullptr,
BUTTON_FLAG_TRANSPARENT);
break;