#include "skilldex.h" #include #include #include "art.h" #include "color.h" #include "cycle.h" #include "debug.h" #include "draw.h" #include "game.h" #include "game_mouse.h" #include "game_sound.h" #include "geometry.h" #include "input.h" #include "interface.h" #include "kb.h" #include "map.h" #include "memory.h" #include "message.h" #include "object.h" #include "platform_compat.h" #include "skill.h" #include "svga.h" #include "text_font.h" #include "window_manager.h" namespace fallout { #define SKILLDEX_WINDOW_RIGHT_MARGIN 4 #define SKILLDEX_WINDOW_BOTTOM_MARGIN 6 #define SKILLDEX_SKILL_BUTTON_BUFFER_COUNT (SKILLDEX_SKILL_COUNT * 2) typedef enum SkilldexFrm { SKILLDEX_FRM_BACKGROUND, SKILLDEX_FRM_BUTTON_ON, SKILLDEX_FRM_BUTTON_OFF, SKILLDEX_FRM_LITTLE_RED_BUTTON_UP, SKILLDEX_FRM_LITTLE_RED_BUTTON_DOWN, SKILLDEX_FRM_BIG_NUMBERS, SKILLDEX_FRM_COUNT, } SkilldexFrm; typedef enum SkilldexSkill { SKILLDEX_SKILL_SNEAK, SKILLDEX_SKILL_LOCKPICK, SKILLDEX_SKILL_STEAL, SKILLDEX_SKILL_TRAPS, SKILLDEX_SKILL_FIRST_AID, SKILLDEX_SKILL_DOCTOR, SKILLDEX_SKILL_SCIENCE, SKILLDEX_SKILL_REPAIR, SKILLDEX_SKILL_COUNT, } SkilldexSkill; static int skilldexWindowInit(); static void skilldexWindowFree(); // 0x51D43C static bool gSkilldexWindowIsoWasEnabled = false; // 0x51D440 static const int gSkilldexFrmIds[SKILLDEX_FRM_COUNT] = { 121, 119, 120, 8, 9, 170, }; // Maps Skilldex options into skills. // // 0x51D458 static const int gSkilldexSkills[SKILLDEX_SKILL_COUNT] = { SKILL_SNEAK, SKILL_LOCKPICK, SKILL_STEAL, SKILL_TRAPS, SKILL_FIRST_AID, SKILL_DOCTOR, SKILL_SCIENCE, SKILL_REPAIR, }; // 0x6680B8 static unsigned char* gSkilldexButtonsData[SKILLDEX_SKILL_BUTTON_BUFFER_COUNT]; // skilldex.msg // 0x6680F8 static MessageList gSkilldexMessageList; // 0x668100 static MessageListItem gSkilldexMessageListItem; // 0x668140 static int gSkilldexWindow; // 0x668144 static unsigned char* gSkilldexWindowBuffer; // 0x668148 static int gSkilldexWindowOldFont; static FrmImage _skilldexFrmImages[SKILLDEX_FRM_COUNT]; // skilldex_select // 0x4ABFD0 int skilldexOpen() { ScopedGameMode gm(GameMode::kSkilldex); if (skilldexWindowInit() == -1) { debugPrint("\n ** Error loading skilldex dialog data! **\n"); return -1; } int rc = -1; while (rc == -1) { sharedFpsLimiter.mark(); int keyCode = inputGetInput(); // SFALL: Close with 'S'. if (keyCode == KEY_ESCAPE || keyCode == KEY_UPPERCASE_S || keyCode == KEY_LOWERCASE_S || keyCode == 500 || _game_user_wants_to_quit != 0) { rc = 0; } else if (keyCode == KEY_RETURN) { soundPlayFile("ib1p1xx1"); rc = 0; } else if (keyCode >= 501 && keyCode <= 509) { rc = keyCode - 500; } renderPresent(); sharedFpsLimiter.throttle(); } if (rc != 0) { inputBlockForTocks(1000 / 9); } skilldexWindowFree(); return rc; } // 0x4AC054 static int skilldexWindowInit() { gSkilldexWindowOldFont = fontGetCurrent(); gSkilldexWindowIsoWasEnabled = false; gameMouseObjectsHide(); gameMouseSetCursor(MOUSE_CURSOR_ARROW); if (!messageListInit(&gSkilldexMessageList)) { return -1; } char path[COMPAT_MAX_PATH]; sprintf(path, "%s%s", asc_5186C8, "skilldex.msg"); if (!messageListLoad(&gSkilldexMessageList, path)) { return -1; } int frmIndex; for (frmIndex = 0; frmIndex < SKILLDEX_FRM_COUNT; frmIndex++) { int fid = buildFid(OBJ_TYPE_INTERFACE, gSkilldexFrmIds[frmIndex], 0, 0, 0); if (!_skilldexFrmImages[frmIndex].lock(fid)) { break; } } if (frmIndex < SKILLDEX_FRM_COUNT) { while (--frmIndex >= 0) { _skilldexFrmImages[frmIndex].unlock(); } messageListFree(&gSkilldexMessageList); return -1; } bool cycle = false; int buttonDataIndex; for (buttonDataIndex = 0; buttonDataIndex < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT; buttonDataIndex++) { gSkilldexButtonsData[buttonDataIndex] = (unsigned char*)internal_malloc(_skilldexFrmImages[SKILLDEX_FRM_BUTTON_ON].getHeight() * _skilldexFrmImages[SKILLDEX_FRM_BUTTON_ON].getWidth() + 512); if (gSkilldexButtonsData[buttonDataIndex] == NULL) { break; } // NOTE: Original code uses bitwise XOR. cycle = !cycle; unsigned char* data; int size; if (cycle) { size = _skilldexFrmImages[SKILLDEX_FRM_BUTTON_OFF].getWidth() * _skilldexFrmImages[SKILLDEX_FRM_BUTTON_OFF].getHeight(); data = _skilldexFrmImages[SKILLDEX_FRM_BUTTON_OFF].getData(); } else { size = _skilldexFrmImages[SKILLDEX_FRM_BUTTON_ON].getWidth() * _skilldexFrmImages[SKILLDEX_FRM_BUTTON_ON].getHeight(); data = _skilldexFrmImages[SKILLDEX_FRM_BUTTON_ON].getData(); } memcpy(gSkilldexButtonsData[buttonDataIndex], data, size); } if (buttonDataIndex < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT) { while (--buttonDataIndex >= 0) { internal_free(gSkilldexButtonsData[buttonDataIndex]); } for (int index = 0; index < SKILLDEX_FRM_COUNT; index++) { _skilldexFrmImages[index].unlock(); } messageListFree(&gSkilldexMessageList); return -1; } // Maintain original position relative to centered interface bar. int skilldexWindowX = (screenGetWidth() - gInterfaceBarWidth) / 2 + gInterfaceBarWidth - _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getWidth() - SKILLDEX_WINDOW_RIGHT_MARGIN; int skilldexWindowY = screenGetHeight() - INTERFACE_BAR_HEIGHT - 1 - _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getHeight() - SKILLDEX_WINDOW_BOTTOM_MARGIN; gSkilldexWindow = windowCreate(skilldexWindowX, skilldexWindowY, _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getWidth(), _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getHeight(), 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); if (gSkilldexWindow == -1) { for (int index = 0; index < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT; index++) { internal_free(gSkilldexButtonsData[index]); } for (int index = 0; index < SKILLDEX_FRM_COUNT; index++) { _skilldexFrmImages[index].unlock(); } messageListFree(&gSkilldexMessageList); return -1; } gSkilldexWindowIsoWasEnabled = isoDisable(); colorCycleDisable(); gameMouseSetCursor(MOUSE_CURSOR_ARROW); gSkilldexWindowBuffer = windowGetBuffer(gSkilldexWindow); memcpy(gSkilldexWindowBuffer, _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getData(), _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getWidth() * _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getHeight()); fontSetCurrent(103); // Render "SKILLDEX" title. char* title = getmsg(&gSkilldexMessageList, &gSkilldexMessageListItem, 100); fontDrawText(gSkilldexWindowBuffer + 14 * _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getWidth() + 55, title, _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getWidth(), _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getWidth(), _colorTable[18979]); // Render skill values. int valueY = 48; for (int index = 0; index < SKILLDEX_SKILL_COUNT; index++) { int value = skillGetValue(gDude, gSkilldexSkills[index]); // SFALL: Fix for negative values. // // NOTE: Sfall's fix is different. It simply renders 0 even if // calculated skill level is negative (which can be the case when // playing on Hard difficulty + low Agility). For unknown reason -5 is // the error code from `skillGetValue`, which is not handled here // because -5 is also a legitimate skill value. // // TODO: Provide other error code in `skillGetValue`. unsigned char* numbersFrmData = _skilldexFrmImages[SKILLDEX_FRM_BIG_NUMBERS].getData(); if (value < 0) { // First half of the bignum.frm is white, second half is red. numbersFrmData += _skilldexFrmImages[SKILLDEX_FRM_BIG_NUMBERS].getWidth() / 2; value = -value; } int hundreds = value / 100; blitBufferToBuffer(numbersFrmData + 14 * hundreds, 14, 24, 336, gSkilldexWindowBuffer + _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getWidth() * valueY + 110, _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getWidth()); int tens = (value % 100) / 10; blitBufferToBuffer(numbersFrmData + 14 * tens, 14, 24, 336, gSkilldexWindowBuffer + _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getWidth() * valueY + 124, _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getWidth()); int ones = (value % 100) % 10; blitBufferToBuffer(numbersFrmData + 14 * ones, 14, 24, 336, gSkilldexWindowBuffer + _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getWidth() * valueY + 138, _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getWidth()); valueY += 36; } // Render skill buttons. int lineHeight = fontGetLineHeight(); int buttonY = 45; int nameY = ((_skilldexFrmImages[SKILLDEX_FRM_BUTTON_OFF].getHeight() - lineHeight) / 2) + 1; for (int index = 0; index < SKILLDEX_SKILL_COUNT; index++) { char name[MESSAGE_LIST_ITEM_FIELD_MAX_SIZE]; strcpy(name, getmsg(&gSkilldexMessageList, &gSkilldexMessageListItem, 102 + index)); int nameX = ((_skilldexFrmImages[SKILLDEX_FRM_BUTTON_OFF].getWidth() - fontGetStringWidth(name)) / 2) + 1; if (nameX < 0) { nameX = 0; } fontDrawText(gSkilldexButtonsData[index * 2] + _skilldexFrmImages[SKILLDEX_FRM_BUTTON_ON].getWidth() * nameY + nameX, name, _skilldexFrmImages[SKILLDEX_FRM_BUTTON_ON].getWidth(), _skilldexFrmImages[SKILLDEX_FRM_BUTTON_ON].getWidth(), _colorTable[18979]); fontDrawText(gSkilldexButtonsData[index * 2 + 1] + _skilldexFrmImages[SKILLDEX_FRM_BUTTON_OFF].getWidth() * nameY + nameX, name, _skilldexFrmImages[SKILLDEX_FRM_BUTTON_OFF].getWidth(), _skilldexFrmImages[SKILLDEX_FRM_BUTTON_OFF].getWidth(), _colorTable[14723]); int btn = buttonCreate(gSkilldexWindow, 15, buttonY, _skilldexFrmImages[SKILLDEX_FRM_BUTTON_OFF].getWidth(), _skilldexFrmImages[SKILLDEX_FRM_BUTTON_OFF].getHeight(), -1, -1, -1, 501 + index, gSkilldexButtonsData[index * 2], gSkilldexButtonsData[index * 2 + 1], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { buttonSetCallbacks(btn, _gsound_lrg_butt_press, _gsound_lrg_butt_release); } buttonY += 36; } // Render "CANCEL" button. char* cancel = getmsg(&gSkilldexMessageList, &gSkilldexMessageListItem, 101); fontDrawText(gSkilldexWindowBuffer + _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getWidth() * 337 + 72, cancel, _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getWidth(), _skilldexFrmImages[SKILLDEX_FRM_BACKGROUND].getWidth(), _colorTable[18979]); int cancelBtn = buttonCreate(gSkilldexWindow, 48, 338, _skilldexFrmImages[SKILLDEX_FRM_LITTLE_RED_BUTTON_UP].getWidth(), _skilldexFrmImages[SKILLDEX_FRM_LITTLE_RED_BUTTON_UP].getHeight(), -1, -1, -1, 500, _skilldexFrmImages[SKILLDEX_FRM_LITTLE_RED_BUTTON_UP].getData(), _skilldexFrmImages[SKILLDEX_FRM_LITTLE_RED_BUTTON_DOWN].getData(), NULL, BUTTON_FLAG_TRANSPARENT); if (cancelBtn != -1) { buttonSetCallbacks(cancelBtn, _gsound_red_butt_press, _gsound_red_butt_release); } windowRefresh(gSkilldexWindow); return 0; } // 0x4AC67C static void skilldexWindowFree() { windowDestroy(gSkilldexWindow); for (int index = 0; index < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT; index++) { internal_free(gSkilldexButtonsData[index]); } for (int index = 0; index < SKILLDEX_FRM_COUNT; index++) { _skilldexFrmImages[index].unlock(); } messageListFree(&gSkilldexMessageList); fontSetCurrent(gSkilldexWindowOldFont); if (gSkilldexWindowIsoWasEnabled) { isoEnable(); } colorCycleEnable(); gameMouseSetCursor(MOUSE_CURSOR_ARROW); } } // namespace fallout