/
erlang.spawn
/
fallout2-ce
Обзор
Документация
Войти
/
erlang.spawn
/
fallout2-ce
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
continious
src/inventory.cc
6 480 строк
248 KB
Mike Klaas
Implement `unwield_slot` (#488)
09 июн 2026, 08:25
Не верифицирован
09 июн 2026, 08:25
261b54c
Код
Авторство
О чём код?
#include "inventory.h" #include <assert.h> #include <stdio.h> #include <string.h> #include <algorithm> #include <string> #include <utility> #include "actions.h" #include "animation.h" #include "art.h" #include "color.h" #include "combat.h" #include "combat_ai.h" #include "critter.h" #include "dbox.h" #include "debug.h" #include "dialog.h" #include "display_monitor.h" #include "draw.h" #include "game.h" #include "game_dialog.h" #include "game_mouse.h" #include "game_sound.h" #include "input.h" #include "interface.h" #include "interpreter_extra.h" #include "item.h" #include "kb.h" #include "light.h" #include "map.h" #include "message.h" #include "mouse.h" #include "object.h" #include "party_member.h" #include "perk.h" #include "platform_compat.h" #include "proto.h" #include "proto_instance.h" #include "random.h" #include "reaction.h" #include "scripts.h" #include "settings.h" #include "sfall_script_hooks.h" #include "skill.h" #include "stat.h" #include "svga.h" #include "text_font.h" #include "tile.h" #include "window_manager.h" namespace fallout { #define INVENTORY_WINDOW_X 80 #define INVENTORY_WINDOW_Y 0 #define INVENTORY_TRADE_WINDOW_X 80 #define INVENTORY_TRADE_WINDOW_Y 0 #define INVENTORY_TRADE_WINDOW_WIDTH 480 #define INVENTORY_TRADE_WINDOW_HEIGHT 180 constexpr int kTradeSlotCount = 3; #define INVENTORY_LARGE_SLOT_WIDTH 90 #define INVENTORY_LARGE_SLOT_HEIGHT 61 #define INVENTORY_LEFT_HAND_SLOT_X 154 #define INVENTORY_LEFT_HAND_SLOT_Y 286 #define INVENTORY_LEFT_HAND_SLOT_MAX_X (INVENTORY_LEFT_HAND_SLOT_X + INVENTORY_LARGE_SLOT_WIDTH) #define INVENTORY_LEFT_HAND_SLOT_MAX_Y (INVENTORY_LEFT_HAND_SLOT_Y + INVENTORY_LARGE_SLOT_HEIGHT) #define INVENTORY_RIGHT_HAND_SLOT_X 245 #define INVENTORY_RIGHT_HAND_SLOT_Y 286 #define INVENTORY_RIGHT_HAND_SLOT_MAX_X (INVENTORY_RIGHT_HAND_SLOT_X + INVENTORY_LARGE_SLOT_WIDTH) #define INVENTORY_RIGHT_HAND_SLOT_MAX_Y (INVENTORY_RIGHT_HAND_SLOT_Y + INVENTORY_LARGE_SLOT_HEIGHT) #define INVENTORY_ARMOR_SLOT_X 154 #define INVENTORY_ARMOR_SLOT_Y 183 #define INVENTORY_ARMOR_SLOT_MAX_X (INVENTORY_ARMOR_SLOT_X + INVENTORY_LARGE_SLOT_WIDTH) #define INVENTORY_ARMOR_SLOT_MAX_Y (INVENTORY_ARMOR_SLOT_Y + INVENTORY_LARGE_SLOT_HEIGHT) #define INVENTORY_TRADE_SCROLLER_Y 30 #define INVENTORY_TRADE_INNER_SCROLLER_Y 20 #define INVENTORY_TRADE_LEFT_SCROLLER_X 29 #define INVENTORY_TRADE_LEFT_SCROLLER_Y INVENTORY_TRADE_SCROLLER_Y #define INVENTORY_TRADE_RIGHT_SCROLLER_X 388 #define INVENTORY_TRADE_RIGHT_SCROLLER_Y INVENTORY_TRADE_SCROLLER_Y #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_X 165 #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y INVENTORY_TRADE_INNER_SCROLLER_Y #define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X 250 #define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y INVENTORY_TRADE_INNER_SCROLLER_Y #define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X 0 #define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y 10 #define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_MAX_X (INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X 165 #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y 10 #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_MAX_X (INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_X 250 #define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_Y 10 #define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_MAX_X (INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_X 395 #define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_Y 10 #define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_MAX_X (INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_LOOT_LEFT_SCROLLER_X 180 #define INVENTORY_LOOT_LEFT_SCROLLER_Y 37 #define INVENTORY_LOOT_LEFT_SCROLLER_MAX_X (INVENTORY_LOOT_LEFT_SCROLLER_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_LOOT_RIGHT_SCROLLER_X 297 #define INVENTORY_LOOT_RIGHT_SCROLLER_Y 37 #define INVENTORY_LOOT_RIGHT_SCROLLER_MAX_X (INVENTORY_LOOT_RIGHT_SCROLLER_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_SCROLLER_X 46 #define INVENTORY_SCROLLER_Y 35 #define INVENTORY_SCROLLER_MAX_X (INVENTORY_SCROLLER_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_BODY_VIEW_WIDTH 60 #define INVENTORY_BODY_VIEW_HEIGHT 100 #define INVENTORY_PC_BODY_VIEW_X 176 #define INVENTORY_PC_BODY_VIEW_Y 37 #define INVENTORY_PC_BODY_VIEW_MAX_X (INVENTORY_PC_BODY_VIEW_X + INVENTORY_BODY_VIEW_WIDTH) #define INVENTORY_PC_BODY_VIEW_MAX_Y (INVENTORY_PC_BODY_VIEW_Y + INVENTORY_BODY_VIEW_HEIGHT) #define INVENTORY_BARTER_LEFT_BODY_VIEW_X 18 #define INVENTORY_BARTER_LEFT_BODY_VIEW_Y 25 #define INVENTORY_LOOT_RIGHT_BODY_VIEW_X 422 #define INVENTORY_LOOT_RIGHT_BODY_VIEW_Y 35 #define INVENTORY_LOOT_LEFT_BODY_VIEW_X 44 #define INVENTORY_LOOT_LEFT_BODY_VIEW_Y 35 #define INVENTORY_SUMMARY_X 297 #define INVENTORY_SUMMARY_Y 44 #define INVENTORY_SUMMARY_MAX_X 440 #define INVENTORY_WINDOW_WIDTH 499 #define INVENTORY_USE_ON_WINDOW_WIDTH 292 #define INVENTORY_LOOT_WINDOW_WIDTH 537 #define INVENTORY_LOOT_WINDOW_WIDTH_EXPANDED 673 #define INVENTORY_NORMAL_BACKGROUND_FRM_ID 48 #define INVENTORY_LOOT_BACKGROUND_FRM_ID 114 #define INVENTORY_TRADE_WINDOW_WIDTH 480 #define INVENTORY_TIMER_WINDOW_WIDTH 259 #define INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH 640 #define INVENTORY_TRADE_BACKGROUND_WINDOW_HEIGHT 480 #define INVENTORY_TRADE_WINDOW_OFFSET ((INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH - INVENTORY_TRADE_WINDOW_WIDTH) / 2) #define INVENTORY_SLOT_PADDING 4 #define INVENTORY_SCROLLER_X_PAD (INVENTORY_SCROLLER_X + INVENTORY_SLOT_PADDING) #define INVENTORY_SCROLLER_Y_PAD (INVENTORY_SCROLLER_Y + INVENTORY_SLOT_PADDING) #define INVENTORY_LOOT_LEFT_SCROLLER_X_PAD (INVENTORY_LOOT_LEFT_SCROLLER_X + INVENTORY_SLOT_PADDING) #define INVENTORY_LOOT_LEFT_SCROLLER_Y_PAD (INVENTORY_LOOT_LEFT_SCROLLER_Y + INVENTORY_SLOT_PADDING) #define INVENTORY_LOOT_RIGHT_SCROLLER_X_PAD (INVENTORY_LOOT_RIGHT_SCROLLER_X + INVENTORY_SLOT_PADDING) #define INVENTORY_LOOT_RIGHT_SCROLLER_Y_PAD (INVENTORY_LOOT_RIGHT_SCROLLER_Y + INVENTORY_SLOT_PADDING) #define INVENTORY_LOOT_CRITTER_TOGGLE_Y (INVENTORY_LOOT_LEFT_BODY_VIEW_Y + INVENTORY_BODY_VIEW_HEIGHT + 24) #define INVENTORY_TRADE_LEFT_SCROLLER_X_PAD (INVENTORY_TRADE_LEFT_SCROLLER_Y + INVENTORY_SLOT_PADDING) #define INVENTORY_TRADE_LEFT_SCROLLER_Y_PAD (INVENTORY_TRADE_LEFT_SCROLLER_Y + INVENTORY_SLOT_PADDING) #define INVENTORY_TRADE_RIGHT_SCROLLER_X_PAD (INVENTORY_TRADE_RIGHT_SCROLLER_X + INVENTORY_SLOT_PADDING) #define INVENTORY_TRADE_RIGHT_SCROLLER_Y_PAD (INVENTORY_TRADE_RIGHT_SCROLLER_Y + INVENTORY_SLOT_PADDING) #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD (INVENTORY_TRADE_INNER_LEFT_SCROLLER_X + INVENTORY_SLOT_PADDING) #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y_PAD (INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y + INVENTORY_SLOT_PADDING) #define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD (INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X + INVENTORY_SLOT_PADDING) #define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y_PAD (INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y + INVENTORY_SLOT_PADDING) #define INVENTORY_SLOT_WIDTH_PAD (INVENTORY_SLOT_WIDTH - INVENTORY_SLOT_PADDING * 2) #define INVENTORY_SLOT_HEIGHT_PAD (INVENTORY_SLOT_HEIGHT - INVENTORY_SLOT_PADDING * 2) #define INVENTORY_NORMAL_WINDOW_PC_ROTATION_DELAY (1000U / ROTATION_COUNT) #define INVENTORY_FRM_COUNT 12 #define INVENTORY_ROWS 6 #define INVENTORY_HAND_RIGHT_KEY 2600 #define INVENTORY_HAND_LEFT_KEY 2601 #define INVENTORY_ARMOR_KEY 2602 typedef enum InventoryArrowFrm { INVENTORY_ARROW_FRM_LEFT_ARROW_UP, INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN, INVENTORY_ARROW_FRM_RIGHT_ARROW_UP, INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN, INVENTORY_ARROW_FRM_COUNT, } InventoryArrowFrm; typedef enum InventoryWindowCursor { INVENTORY_WINDOW_CURSOR_HAND, INVENTORY_WINDOW_CURSOR_ARROW, INVENTORY_WINDOW_CURSOR_PICK, INVENTORY_WINDOW_CURSOR_MENU, INVENTORY_WINDOW_CURSOR_BLANK, INVENTORY_WINDOW_CURSOR_COUNT, } InventoryWindowCursor; typedef enum InventoryWindowType { // Normal inventory window with quick character sheet. INVENTORY_WINDOW_TYPE_NORMAL, // Narrow inventory window with just an item scroller that's shown when // a "Use item on" is selected from context menu. INVENTORY_WINDOW_TYPE_USE_ITEM_ON, // Looting/strealing interface. INVENTORY_WINDOW_TYPE_LOOT, // Barter interface. INVENTORY_WINDOW_TYPE_TRADE, // Supplementary "Move items" window. Used to set quantity of items when // moving items between inventories. INVENTORY_WINDOW_TYPE_MOVE_ITEMS, // Supplementary "Set timer" window. Internally it's implemented as "Move // items" window but with timer overlay and slightly different adjustment // mechanics. INVENTORY_WINDOW_TYPE_SET_TIMER, INVENTORY_WINDOW_TYPE_COUNT, } InventoryWindowType; typedef struct InventoryWindowConfiguration { int frmId; // artId int width; int height; int x; int y; } InventoryWindowDescription; typedef struct InventoryCursorData { Art* frm; unsigned char* frmData; int width; int height; int offsetX; int offsetY; CacheEntry* frmHandle; } InventoryCursorData; typedef enum InventoryMoveResult { INVENTORY_MOVE_RESULT_FAILED, INVENTORY_MOVE_RESULT_CAUGHT_STEALING, INVENTORY_MOVE_RESULT_SUCCESS, } InventoryMoveResult; typedef enum InventoryAmmoMoveResult { INVENTORY_AMMO_MOVE_RESULT_FAILED = -1, INVENTORY_AMMO_MOVE_RESULT_SUCCESS = 0, INVENTORY_AMMO_MOVE_RESULT_BLOCKED = 1, } InventoryAmmoMoveResult; // offsets for normal inventory window (not steal, loot, &c.) typedef struct InventoryNormalLayout { int columns; int visibleSlots; int windowWidth; int windowHeight; int scrollerX; int scrollerY; int scrollerWidth; int scrollerHeight; int leftHandSlotX; int rightHandSlotX; int armorSlotX; int bodyViewX; int summaryX; int scrollButtonX; int doneButtonX; } InventoryNormalLayout; typedef struct InventoryLootLayout { int columns; int visibleSlots; int windowWidth; int windowHeight; int leftScrollerX; int leftScrollerY; int rightScrollerX; int rightScrollerY; int scrollerWidth; int scrollerHeight; int leftBodyViewX; int rightBodyViewX; int leftScrollButtonX; int rightScrollButtonX; int takeAllButtonX; int doneButtonX; int prevCritterButtonX; int nextCritterButtonX; } InventoryLootLayout; static int inventoryMessageListInit(); static int inventoryMessageListFree(); static bool _setup_inventory(int inventoryWindowType); static void _exit_inventory(bool shouldEnableIso); static void _display_inventory(int stackOffset, int draggedSlotIndex, int inventoryWindowType); static void _display_target_inventory(int stackOffset, int dragSlotIndex, Inventory* inventory, int inventoryWindowType); static void _display_inventory_info(Object* item, int quantity, unsigned char* dest, int pitch, bool isDragged); static void _display_body(int fid, int inventoryWindowType); static int inventoryCommonInit(); static void inventoryCommonFree(); static void inventorySetCursor(int cursor); static void inventoryItemSlotOnMouseEnter(int btn, int keyCode); static void inventoryItemSlotOnMouseExit(int btn, int keyCode); static void _inven_update_lighting(Object* activeItem); static void _inven_pickup(int keyCode, int indexOffset); static void _switch_hand(Object* sourceItem, Object** targetSlot, Object** sourceSlot, int itemIndex); static void _adjust_fid(); static void inventoryRenderSummary(); static int _inven_from_button(int keyCode, Object** outItem, Object*** outItemSlot, Object** outOwner); static void inventoryRenderItemDescription(const char* string); static void inventoryDrawCenteredText(unsigned char* buffer, int pitch, int width, int x, int y, const char* text, int color); static void inventoryExamineItem(Object* critter, Object* item); static void inventorySetLeftPaneCritter(Object* critter, Object* target, int inventoryWindowType); static void inventoryWindowOpenContextMenu(int eventCode, int inventoryWindowType); static InventoryMoveResult _move_inventory(Object* item, int slotIndex, Object* targetObj, bool isPlanting, int* stealXpOverridePtr); static std::pair<int, int> barterComputeTablesValue(Object* dude, Object* npc, bool offerButton = false); static std::pair<int, int> barterComputeTablesWeight(Object* dude, Object* npc); static int barterAttemptTransaction(Object* dude, Object* offerTable, Object* npc, Object* barterTable); static int barterGetMovedQuantity(Object* item, int maxQuantity, bool fromPlayer, bool fromInventory, bool immediate); static void barterMoveToTable(Object* item, int quantity, int slotIndex, int indexOffset, Object* npc, Object* sourceTable, bool fromDude); static void barterMoveFromTable(Object* item, int quantity, int slotIndex, Object* npc, Object* sourceTable, bool fromDude); static void barterDisplayTables(int win, Object* leftTable, Object* rightTable, int draggedSlotIndex); static void _container_enter(int keyCode, int inventoryWindowType); static void _container_exit(int keyCode, int inventoryWindowType); static int _drop_into_container(Object* container, Object* item, int sourceIndex, Object** itemSlot, int quantity); static InventoryAmmoMoveResult _drop_ammo_into_weapon(Object* weapon, Object* ammo, Object** ammoItemSlot, int quantity, int keyCode); static void _draw_amount(int value, int inventoryWindowType); static int inventoryQuantitySelect(int inventoryWindowType, Object* item, int maximum, int defaultValue = 1); static int inventoryQuantityWindowInit(int inventoryWindowType, Object* item); static int inventoryQuantityWindowFree(int inventoryWindowType); static bool _ctrl_pressed(); static void _drag_item_loop(Object* item, bool immediate); static void inventoryNormalLayoutUpdate(); static bool inventoryBackgroundLoad(FrmImage& image, int col1FrmId, const char* col2Name, int columns); static int inventoryChooseColumns(FrmImage& image, int expandedWidth, int col1FrmId, const char* col2Name); static void inventoryCreateSlotButtons(int baseKeyCode, int scrollerX, int scrollerY, int columns); static int inventoryGetWindowWidth(int inventoryWindowType); static int inventoryGetWindowHeight(int inventoryWindowType); static void inventoryNormalClampStackOffset(); static void inventoryLootLayoutUpdate(); static int inventoryLootGetSlotX(bool targetInventory, int slotIndex); static int inventoryLootGetSlotY(int slotIndex); static bool inventoryLootMouseHitTestScroller(bool targetInventory); static int inventoryComputeAlignedMaxOffset(int length, int visibleSlots, int scrollStep); static int inventoryGetCenteredWindowY(int windowHeight); static void inventoryDisplayLeftPaneCompanionName(unsigned char* windowBuffer, int windowPitch, const Rect& rect, int index); // 0x46E6D0 stats_array0 static const int gSummaryStats[7] = { STAT_CURRENT_HIT_POINTS, STAT_ARMOR_CLASS, STAT_DAMAGE_THRESHOLD, STAT_DAMAGE_THRESHOLD_LASER, STAT_DAMAGE_THRESHOLD_FIRE, STAT_DAMAGE_THRESHOLD_PLASMA, STAT_DAMAGE_THRESHOLD_EXPLOSION, }; // 0x46E6EC stats_array1 static const int gSummaryStats2[7] = { STAT_MAXIMUM_HIT_POINTS, -1, STAT_DAMAGE_RESISTANCE, STAT_DAMAGE_RESISTANCE_LASER, STAT_DAMAGE_RESISTANCE_FIRE, STAT_DAMAGE_RESISTANCE_PLASMA, STAT_DAMAGE_RESISTANCE_EXPLOSION, }; // 0x46E708 static const int gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_COUNT] = { 122, // left arrow up 123, // left arrow down 124, // right arrow up 125, // right arrow down }; // The number of items to show in scroller. // // 0x519054 inven_cur_disp static int gInventorySlotsCount = 6; static InventoryNormalLayout inventoryLayout; static InventoryLootLayout inventoryLootLayout; static FrmImage inventoryFrmImage; static FrmImage inventoryLootFrmImage; // 0x519058 inven_dude static Object* _inven_dude = nullptr; // Probably fid of armor to display in inventory dialog. // // 0x51905C inven_pid static int _inven_pid = -1; // 0x519060 inven_is_initialized static bool _inven_is_initialized = false; // 0x519064 inven_display_msg_line static int _inven_display_msg_line = 1; // 0x519068 iscr_data static const InventoryWindowDescription gInventoryWindowDescriptions[INVENTORY_WINDOW_TYPE_COUNT] = { { INVENTORY_NORMAL_BACKGROUND_FRM_ID, INVENTORY_WINDOW_WIDTH, 377, 80, 0 }, { 113, INVENTORY_USE_ON_WINDOW_WIDTH, 376, 80, 0 }, { INVENTORY_LOOT_BACKGROUND_FRM_ID, INVENTORY_LOOT_WINDOW_WIDTH, 376, 80, 0 }, { 111, INVENTORY_TRADE_WINDOW_WIDTH, 180, 80, 290 }, { 305, INVENTORY_TIMER_WINDOW_WIDTH, 162, 140, 80 }, { 305, INVENTORY_TIMER_WINDOW_WIDTH, 162, 140, 80 }, }; // 0x5190E0 dropped_explosive static bool _dropped_explosive = false; // 0x5190E4 inven_scroll_up_bid static int gInventoryScrollUpButton = -1; // 0x5190E8 inven_scroll_dn_bid static int gInventoryScrollDownButton = -1; // 0x5190EC loot_scroll_up_bid static int gSecondaryInventoryScrollUpButton = -1; // 0x5190F0 loot_scroll_dn_bid static int gSecondaryInventoryScrollDownButton = -1; // 0x5190F4 ticker static unsigned int gInventoryWindowDudeRotationTimestamp = 0; // 0x5190F8 curr_rot static int gInventoryWindowDudeRotation = 0; // 0x5190FC num static const int gInventoryWindowCursorFrmIds[INVENTORY_WINDOW_CURSOR_COUNT] = { 286, // pointing hand 250, // action arrow 282, // action pick 283, // action menu 266, // blank }; // 0x519110 last_target static Object* _last_target = nullptr; // 0x519114 act_use static const int _act_use[4] = { GAME_MOUSE_ACTION_MENU_ITEM_LOOK, GAME_MOUSE_ACTION_MENU_ITEM_USE, GAME_MOUSE_ACTION_MENU_ITEM_DROP, GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, }; // 0x519124 act_no_use static const int _act_no_use[3] = { GAME_MOUSE_ACTION_MENU_ITEM_LOOK, GAME_MOUSE_ACTION_MENU_ITEM_DROP, GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, }; // 0x519130 act_just_use static const int _act_just_use[3] = { GAME_MOUSE_ACTION_MENU_ITEM_LOOK, GAME_MOUSE_ACTION_MENU_ITEM_USE, GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, }; // 0x51913C act_nothing static const int _act_nothing[2] = { GAME_MOUSE_ACTION_MENU_ITEM_LOOK, GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, }; // 0x519144 act_weap static const int _act_weap[4] = { GAME_MOUSE_ACTION_MENU_ITEM_LOOK, GAME_MOUSE_ACTION_MENU_ITEM_UNLOAD, GAME_MOUSE_ACTION_MENU_ITEM_DROP, GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, }; // 0x519154 act_weap2 static const int _act_weap2[3] = { GAME_MOUSE_ACTION_MENU_ITEM_LOOK, GAME_MOUSE_ACTION_MENU_ITEM_UNLOAD, GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, }; // Scroll offsets to target inventory for every container nesting level (stack). // 0x59E7EC target_stack_offset static int _target_stack_offset[10]; // inventory.msg // // 0x59E814 inventry_message_file static MessageList gInventoryMessageList; // Current target critter or container for every nesting level (stack). // 0x59E81C target_stack static Object* _target_stack[10]; // Scroll offsets to main inventory for every container nesting level (stack). // 0x59E844 stack_offset static int _stack_offset[10]; // Current critter or container for every nesting level (stack). // 0x59E86C stack static Object* _stack[10]; // 0x59E894 mt_wid static int _mt_wid; // Note: Sfall also has InventoryApCost and QuickPocketsApCostReduction settings which we don't look at static constexpr int kDefaultInventoryApCost = 4; static constexpr int kQuickPocketsApCostReduction = 2; // Current inventory AP cost static int gInventoryApCost = kDefaultInventoryApCost; // Current barter price modifier, based on gGameDialogBarterModifier, with the reaction-based modifier added (in percent). // 0x59E898 barter_mod static int gBarterFinalModifier; // 0x59E89C btable_offset static int gBartererTableOffset; // 0x59E8A0 ptable_offset static int gPlayerTableOffset; // An inventory containing a subset of Player's items offered to the NPC during barter. // 0x59E8A4 ptable_pud static Inventory* gPlayerTableInventory; // 0x59E8A8 imdata static InventoryCursorData gInventoryCursorData[INVENTORY_WINDOW_CURSOR_COUNT]; // An object (PID -1) with an inventory containing a subset of Player's items offered to the NPC during barter. // 0x59E934 ptable static Object* gPlayerTableObj; // 0x59E938 display_msg static InventoryPrintItemDescriptionHandler* gInventoryPrintItemDescriptionHandler; // 0x59E93C im_value static int _im_value; // "keyCode" corresponding to an inventory item "button", or -1 if nothing // 0x59E940 immode static int gInventoryCursor; // An object (PID -1) with an inventory containing a subset of NPC's items asked for by the player during barter. // 0x59E944 btable static Object* gBartererTableObj; // Current nesting level for viewing target's bag/backpack contents. // 0x59E948 target_curr_stack static int _target_curr_stack; // 0x59E94C btable_pud static Inventory* gBartererTableInventory; // 0x59E950 inven_ui_was_disabled static bool _inven_ui_was_disabled; // 0x59E954 i_worn static Object* gInventoryArmor; // 0x59E958 i_lhand static Object* gInventoryLeftHandItem; // Rotating character's fid. // // 0x59E95C i_fid static int gInventoryWindowDudeFid; // 0x59E960 pud static Inventory* _pud; // 0x59E964 i_wid static int gInventoryWindow; // item2 // 0x59E968 i_rhand static Object* gInventoryRightHandItem; // Current nesting level for viewing bag/backpack contents. // 0x59E96C curr_stack static int _curr_stack; // 0x59E970 i_wid_max_y static int gInventoryWindowMaxY; // 0x59E974 i_wid_max_x static int gInventoryWindowMaxX; // 0x59E978 target_pud static Inventory* _target_pud; // 0x59E97C barter_back_win static int gInventoryBarterBackgroundWindow; // Weight of equipped items stripped from the left/right loot-pane critter. // Non-zero only while inventoryOpenLooting is active. static int inventoryLootRightEquippedWeight = 0; static FrmImage _inventoryFrmImages[INVENTORY_FRM_COUNT]; static FrmImage _moveFrmImages[8]; static bool inventoryBackgroundLoad(FrmImage& image, int col1FrmId, const char* col2Name, int columns) { image.unlock(); if (columns == 1) { return image.lock(buildFid(OBJ_TYPE_INTERFACE, col1FrmId, 0, 0, 0)); } if (columns == 2) { return image.lock(OBJ_TYPE_INTERFACE, col2Name); } return false; } static int buttonCreateSlot(int win, int x, int y, int width, int height, int keyCode, ButtonCallback* onEnter = nullptr, ButtonCallback* onExit = nullptr) { int btn = buttonCreate(win, x, y, width, height, keyCode, -1, keyCode); if (btn != -1) { buttonSetMouseCallbacks(btn, onEnter, onExit, nullptr, nullptr); } return btn; } static int buttonCreateAction(int win, int x, int y, int width, int height, int keyCode) { return buttonCreate(win, x, y, width, height, -1, -1, keyCode); } static ConstBuffer2D inventoryGetBackgroundBuffer(int inventoryWindowType, int fallbackFrmId, FrmImage& fallbackImage, int& sourceXOffset) { sourceXOffset = 0; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { return inventoryFrmImage.getBuffer(); } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { return inventoryLootFrmImage.getBuffer(); } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { sourceXOffset = INVENTORY_TRADE_WINDOW_OFFSET; return { windowGetBuffer(gInventoryBarterBackgroundWindow), INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH, windowGetHeight(gInventoryBarterBackgroundWindow) }; } if (fallbackImage.lock(buildFid(OBJ_TYPE_INTERFACE, fallbackFrmId, 0, 0, 0))) { return fallbackImage.getBuffer(); } return {}; } static void inventoryCreateSlotButtons(int baseKeyCode, int scrollerX, int scrollerY, int columns) { for (int row = 0; row < INVENTORY_ROWS; row++) { for (int column = 0; column < columns; column++) { int keyCode = baseKeyCode + row * columns + column; buttonCreateSlot(gInventoryWindow, scrollerX + column * INVENTORY_SLOT_WIDTH, scrollerY + row * INVENTORY_SLOT_HEIGHT, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, keyCode, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit); } } } static int inventoryGetWindowWidth(int inventoryWindowType) { if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) return inventoryLayout.windowWidth; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) return inventoryLootLayout.windowWidth; return gInventoryWindowDescriptions[inventoryWindowType].width; } static int inventoryGetWindowHeight(int inventoryWindowType) { if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) return inventoryLayout.windowHeight; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) return inventoryLootLayout.windowHeight; return gInventoryWindowDescriptions[inventoryWindowType].height; } static int inventoryGetCenteredWindowY(int windowHeight) { return (screenGetVisibleHeight() - windowHeight) / 2; } static int inventoryChooseColumns(FrmImage& image, int expandedWidth, int col1FrmId, const char* col2Name) { if (settings.ui.inventory_columns <= 1) { return 1; } if (expandedWidth <= screenGetWidth() && inventoryBackgroundLoad(image, col1FrmId, col2Name, 2)) { return 2; } return 1; } static void inventoryNormalApplyLayout(int columns) { int shift = (columns - 1) * INVENTORY_SLOT_WIDTH; inventoryLayout.columns = columns; inventoryLayout.visibleSlots = INVENTORY_ROWS * columns; inventoryLayout.windowWidth = INVENTORY_WINDOW_WIDTH + shift; inventoryLayout.windowHeight = gInventoryWindowDescriptions[INVENTORY_WINDOW_TYPE_NORMAL].height; inventoryLayout.scrollerX = INVENTORY_SCROLLER_X; inventoryLayout.scrollerY = INVENTORY_SCROLLER_Y; inventoryLayout.scrollerWidth = columns * INVENTORY_SLOT_WIDTH; inventoryLayout.scrollerHeight = INVENTORY_ROWS * INVENTORY_SLOT_HEIGHT; inventoryLayout.leftHandSlotX = INVENTORY_LEFT_HAND_SLOT_X + shift; inventoryLayout.rightHandSlotX = INVENTORY_RIGHT_HAND_SLOT_X + shift; inventoryLayout.armorSlotX = INVENTORY_ARMOR_SLOT_X + shift; inventoryLayout.bodyViewX = INVENTORY_PC_BODY_VIEW_X + shift; inventoryLayout.summaryX = INVENTORY_SUMMARY_X + shift; inventoryLayout.scrollButtonX = 128 + shift; inventoryLayout.doneButtonX = 437 + shift; } static void inventoryLootApplyLayout(int columns) { int extraColumns = columns - 1; int scrollerShift = extraColumns * (367 - INVENTORY_LOOT_RIGHT_SCROLLER_X); int leftBodyViewShift = extraColumns * (47 - INVENTORY_LOOT_LEFT_BODY_VIEW_X); int rightBodyViewShift = extraColumns * (563 - INVENTORY_LOOT_RIGHT_BODY_VIEW_X); int rightPaneShift = extraColumns * (612 - 476); inventoryLootLayout.columns = columns; inventoryLootLayout.visibleSlots = INVENTORY_ROWS * columns; inventoryLootLayout.windowWidth = INVENTORY_LOOT_WINDOW_WIDTH + extraColumns * (INVENTORY_LOOT_WINDOW_WIDTH_EXPANDED - INVENTORY_LOOT_WINDOW_WIDTH); inventoryLootLayout.windowHeight = gInventoryWindowDescriptions[INVENTORY_WINDOW_TYPE_LOOT].height; inventoryLootLayout.leftScrollerX = INVENTORY_LOOT_LEFT_SCROLLER_X; inventoryLootLayout.leftScrollerY = INVENTORY_LOOT_LEFT_SCROLLER_Y; inventoryLootLayout.rightScrollerX = INVENTORY_LOOT_RIGHT_SCROLLER_X + scrollerShift; inventoryLootLayout.rightScrollerY = INVENTORY_LOOT_RIGHT_SCROLLER_Y; inventoryLootLayout.scrollerWidth = columns * INVENTORY_SLOT_WIDTH; inventoryLootLayout.scrollerHeight = INVENTORY_ROWS * INVENTORY_SLOT_HEIGHT; inventoryLootLayout.leftBodyViewX = INVENTORY_LOOT_LEFT_BODY_VIEW_X + leftBodyViewShift; inventoryLootLayout.rightBodyViewX = INVENTORY_LOOT_RIGHT_BODY_VIEW_X + rightBodyViewShift; inventoryLootLayout.leftScrollButtonX = 128; inventoryLootLayout.rightScrollButtonX = 379 + rightPaneShift; inventoryLootLayout.takeAllButtonX = 432 + rightPaneShift; inventoryLootLayout.doneButtonX = 476 + rightPaneShift; // Center the two 20px-wide critter-nav arrows below the right avatar int critterBtnCenterX = inventoryLootLayout.rightBodyViewX + INVENTORY_BODY_VIEW_WIDTH / 2; inventoryLootLayout.prevCritterButtonX = critterBtnCenterX - 20; inventoryLootLayout.nextCritterButtonX = critterBtnCenterX; } static void inventoryNormalLayoutUpdate() { int columns = inventoryChooseColumns(inventoryFrmImage, INVENTORY_WINDOW_WIDTH + INVENTORY_SLOT_WIDTH, INVENTORY_NORMAL_BACKGROUND_FRM_ID, "invbox2.png"); if (columns == 1) { inventoryBackgroundLoad(inventoryFrmImage, INVENTORY_NORMAL_BACKGROUND_FRM_ID, "invbox2.png", 1); } inventoryNormalApplyLayout(columns); } static void inventoryLootLayoutUpdate() { int columns = inventoryChooseColumns(inventoryLootFrmImage, INVENTORY_LOOT_WINDOW_WIDTH_EXPANDED, INVENTORY_LOOT_BACKGROUND_FRM_ID, "loot2.png"); if (columns == 1) { inventoryBackgroundLoad(inventoryLootFrmImage, INVENTORY_LOOT_BACKGROUND_FRM_ID, "loot2.png", 1); } inventoryLootApplyLayout(columns); } static void inventoryNormalClampStackOffset() { int maxOffset = inventoryComputeAlignedMaxOffset(_pud->length, inventoryLayout.visibleSlots, inventoryLayout.columns); if (_stack_offset[_curr_stack] > maxOffset) { _stack_offset[_curr_stack] = maxOffset; } if (_stack_offset[_curr_stack] < 0) { _stack_offset[_curr_stack] = 0; } } static void inventoryLootScrollBy(int& offset, int delta, int totalItems) { int scrollStep = inventoryLootLayout.columns; offset += delta * scrollStep; if (offset < 0) offset = 0; int maxOffset = inventoryComputeAlignedMaxOffset(totalItems, gInventorySlotsCount, scrollStep); if (offset > maxOffset) offset = maxOffset; } static int inventoryLootGetSlotX(bool targetInventory, int slotIndex) { int scrollerX = targetInventory ? inventoryLootLayout.rightScrollerX : inventoryLootLayout.leftScrollerX; return scrollerX + slotIndex % inventoryLootLayout.columns * INVENTORY_SLOT_WIDTH; } static int inventoryLootGetSlotY(int slotIndex) { return inventoryLootLayout.leftScrollerY + slotIndex / inventoryLootLayout.columns * INVENTORY_SLOT_HEIGHT; } static bool inventoryLootMouseHitTestScroller(bool targetInventory) { int scrollerX = targetInventory ? inventoryLootLayout.rightScrollerX : inventoryLootLayout.leftScrollerX; int scrollerY = targetInventory ? inventoryLootLayout.rightScrollerY : inventoryLootLayout.leftScrollerY; return mouseHitTestInWindow(gInventoryWindow, scrollerX, scrollerY, scrollerX + inventoryLootLayout.scrollerWidth, scrollerY + inventoryLootLayout.scrollerHeight); } static int inventoryComputeAlignedMaxOffset(int length, int visibleSlots, int scrollStep) { int maxOffset = length - visibleSlots; if (maxOffset <= 0) { return 0; } return (maxOffset + scrollStep - 1) / scrollStep * scrollStep; } // 0x46E724 void inventoryResetDude() { _inven_dude = gDude; _inven_pid = 0x1000000; } int inventoryGetInvenApCost() { int quickPockets = 0; if (gDude != nullptr) { quickPockets = perkGetRank(gDude, PERK_QUICK_POCKETS); } return std::max(gInventoryApCost - kQuickPocketsApCostReduction * quickPockets, 0); } void inventorySetInvenApCost(int cost) { gInventoryApCost = cost; } void inventoryResetInvenApCost() { gInventoryApCost = kDefaultInventoryApCost; } // inven_set_dude void inventorySetDude(Object* obj, int pid) { _inven_dude = obj; _inven_pid = pid; } // TODO(CE): move to more generic location int inventoryComputeCritterFid(Object* critter, int basePid, Object* rightHandItem, Object* leftHandItem, Object* armor, int activeHand, int anim, int rotation) { if (FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER) { return critter->fid; } Proto* proto = nullptr; int inventoryFid = _art_vault_guy_num; if (protoGetProto(basePid, &proto) != -1) { inventoryFid = proto->fid & 0xFFF; } if (armor != nullptr) { if (protoGetProto(armor->pid, &proto) != -1 && proto != nullptr) { if (critterGetStat(critter, STAT_GENDER) == GENDER_FEMALE) { inventoryFid = proto->item.data.armor.femaleFid; } else { inventoryFid = proto->item.data.armor.maleFid; } if (inventoryFid == -1) { inventoryFid = _art_vault_guy_num; } } } int animationCode = 0; Object* itemInHand = activeHand == HAND_RIGHT ? rightHandItem : leftHandItem; if (itemInHand != nullptr) { if (protoGetProto(itemInHand->pid, &proto) != -1 && proto != nullptr && proto->item.type == ITEM_TYPE_WEAPON) { animationCode = proto->item.data.weapon.animationCode; } } return buildFid(OBJ_TYPE_CRITTER, inventoryFid, anim, animationCode, rotation); } // inventory_msg_init // 0x46E73C static int inventoryMessageListInit() { char path[COMPAT_MAX_PATH]; if (!messageListInit(&gInventoryMessageList)) return -1; snprintf(path, sizeof(path), "%s%s", asc_5186C8, "inventry.msg"); if (!messageListLoad(&gInventoryMessageList, path)) return -1; return 0; } // inventory_msg_free // 0x46E7A0 static int inventoryMessageListFree() { messageListFree(&gInventoryMessageList); return 0; } // 0x46E7B0 void inventoryOpen() { if (isInCombat()) { if (_combat_whose_turn() != _inven_dude) { return; } } ScopedGameMode gm(GameMode::kInventory); if (inventoryCommonInit() == -1) { return; } if (isInCombat()) { if (_inven_dude == gDude) { int actionPointsRequired = inventoryGetInvenApCost(); if (actionPointsRequired > 0 && actionPointsRequired > gDude->data.critter.combat.ap) { // You don't have enough action points to use inventory MessageListItem messageListItem; messageListItem.num = 19; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { displayMonitorAddMessage(messageListItem.text); } // NOTE: Uninline. inventoryCommonFree(); return; } if (actionPointsRequired > 0) { if (actionPointsRequired > gDude->data.critter.combat.ap) { gDude->data.critter.combat.ap = 0; } else { gDude->data.critter.combat.ap -= actionPointsRequired; } interfaceRenderActionPoints(gDude->data.critter.combat.ap, _combat_free_move); } } } Object* oldArmor = critterGetArmor(_inven_dude); bool isoWasEnabled = _setup_inventory(INVENTORY_WINDOW_TYPE_NORMAL); reg_anim_clear(_inven_dude); inventoryRenderSummary(); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL); inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); for (;;) { sharedFpsLimiter.mark(); int keyCode = inputGetInput(); // SFALL: Close with 'I'. if (keyCode == KEY_ESCAPE || keyCode == KEY_UPPERCASE_I || keyCode == KEY_LOWERCASE_I) { break; } if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) { break; } _display_body(-1, INVENTORY_WINDOW_TYPE_NORMAL); if (gameGetState() == GAME_STATE_5) { break; } if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X) { showQuitConfirmationDialog(); } else if (keyCode == KEY_HOME) { _stack_offset[_curr_stack] = 0; _display_inventory(0, -1, INVENTORY_WINDOW_TYPE_NORMAL); } else if (keyCode == KEY_ARROW_UP) { if (_stack_offset[_curr_stack] > 0) { _stack_offset[_curr_stack] -= inventoryLayout.columns; inventoryNormalClampStackOffset(); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL); } } else if (keyCode == KEY_PAGE_UP) { _stack_offset[_curr_stack] -= inventoryLayout.visibleSlots; if (_stack_offset[_curr_stack] < 0) { _stack_offset[_curr_stack] = 0; } _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL); } else if (keyCode == KEY_END) { _stack_offset[_curr_stack] = _pud->length - inventoryLayout.visibleSlots; inventoryNormalClampStackOffset(); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL); } else if (keyCode == KEY_ARROW_DOWN) { if (inventoryLayout.visibleSlots + _stack_offset[_curr_stack] < _pud->length) { _stack_offset[_curr_stack] += inventoryLayout.columns; inventoryNormalClampStackOffset(); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL); } } else if (keyCode == KEY_PAGE_DOWN) { int nextPageOffset = inventoryLayout.visibleSlots + _stack_offset[_curr_stack]; int nextPageEnd = nextPageOffset + inventoryLayout.visibleSlots; _stack_offset[_curr_stack] = nextPageOffset; int inventoryLength = _pud->length; if (nextPageEnd >= _pud->length) { _stack_offset[_curr_stack] = inventoryLength - inventoryLayout.visibleSlots; inventoryNormalClampStackOffset(); } _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL); } else if (keyCode == 2500) { _container_exit(keyCode, INVENTORY_WINDOW_TYPE_NORMAL); } else { if ((mouseGetEvent() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_HAND) { inventorySetCursor(INVENTORY_WINDOW_CURSOR_ARROW); } else if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); inventoryRenderSummary(); windowRefresh(gInventoryWindow); } } else if ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { if ((keyCode >= 1000 && keyCode < 1000 + inventoryLayout.visibleSlots) || keyCode == INVENTORY_HAND_RIGHT_KEY || keyCode == INVENTORY_HAND_LEFT_KEY || keyCode == INVENTORY_ARMOR_KEY) { if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_NORMAL); } else { _inven_pickup(keyCode, _stack_offset[_curr_stack]); } } } else if ((mouseGetEvent() & MOUSE_EVENT_WHEEL) != 0) { if (mouseHitTestInWindow(gInventoryWindow, inventoryLayout.scrollerX, inventoryLayout.scrollerY, inventoryLayout.scrollerX + inventoryLayout.scrollerWidth, inventoryLayout.scrollerY + inventoryLayout.scrollerHeight)) { int wheelX; int wheelY; mouseGetWheel(&wheelX, &wheelY); if (wheelY > 0) { if (_stack_offset[_curr_stack] > 0) { _stack_offset[_curr_stack] -= inventoryLayout.columns; inventoryNormalClampStackOffset(); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL); } } else if (wheelY < 0) { if (inventoryLayout.visibleSlots + _stack_offset[_curr_stack] < _pud->length) { _stack_offset[_curr_stack] += inventoryLayout.columns; inventoryNormalClampStackOffset(); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL); } } } } } renderPresent(); sharedFpsLimiter.throttle(); } _inven_dude = _stack[0]; _adjust_fid(); if (_inven_dude == gDude) { Rect rect; objectSetFid(_inven_dude, gInventoryWindowDudeFid, &rect); tileWindowRefreshRect(&rect, _inven_dude->elevation); } Object* newArmor = critterGetArmor(_inven_dude); if (_inven_dude == gDude) { if (oldArmor != newArmor) { interfaceRenderArmorClass(true); } } _exit_inventory(isoWasEnabled); // NOTE: Uninline. inventoryCommonFree(); if (_inven_dude == gDude) { interfaceUpdateItems(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } } // 0x46EC90 static bool _setup_inventory(int inventoryWindowType) { _dropped_explosive = 0; _curr_stack = 0; _stack_offset[0] = 0; gInventorySlotsCount = 6; _pud = &(_inven_dude->data.inventory); _stack[0] = _inven_dude; bool isNormalWindow = inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL; if (inventoryWindowType <= INVENTORY_WINDOW_TYPE_LOOT) { if (isNormalWindow) { inventoryNormalLayoutUpdate(); } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { inventoryLootLayoutUpdate(); gInventorySlotsCount = inventoryLootLayout.visibleSlots; } const InventoryWindowDescription* windowDescription = &(gInventoryWindowDescriptions[inventoryWindowType]); int windowWidth = inventoryGetWindowWidth(inventoryWindowType); int windowHeight = inventoryGetWindowHeight(inventoryWindowType); // Maintain original position in original resolution, otherwise center it. bool preserveVanillaX = screenGetWidth() == 640 && windowWidth == windowDescription->width && inventoryWindowType != INVENTORY_WINDOW_TYPE_LOOT; bool preserveVanillaY = screenGetHeight() == 480; int inventoryWindowX = preserveVanillaX ? INVENTORY_WINDOW_X : (screenGetWidth() - windowWidth) / 2; int inventoryWindowY = preserveVanillaY ? INVENTORY_WINDOW_Y : inventoryGetCenteredWindowY(windowHeight); gInventoryWindow = windowCreate(inventoryWindowX, inventoryWindowY, windowWidth, windowHeight, 257, WINDOW_MODAL | WINDOW_MOVE_ON_TOP); gInventoryWindowMaxX = windowWidth + inventoryWindowX; gInventoryWindowMaxY = windowHeight + inventoryWindowY; Buffer2D destBuf { windowGetBuffer(gInventoryWindow), windowWidth, windowHeight }; if (isNormalWindow) { assert(inventoryFrmImage.isLocked()); blitBuffer2D(inventoryFrmImage.getBuffer(), destBuf); } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { assert(inventoryLootFrmImage.isLocked()); blitBuffer2D(inventoryLootFrmImage.getBuffer(), destBuf); } else { FrmImage backgroundFrmImage; if (backgroundFrmImage.lock(buildFid(OBJ_TYPE_INTERFACE, windowDescription->frmId, 0, 0, 0))) { blitBuffer2D(backgroundFrmImage.getBuffer(), destBuf); } } gInventoryPrintItemDescriptionHandler = displayMonitorAddMessage; } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { if (gInventoryBarterBackgroundWindow == -1) { exit(1); } int extraSlots = gameDialogIsBarterWindowExpanded() ? kExpandedBarterExtraSlots : 0; gInventorySlotsCount = kTradeSlotCount + extraSlots; int tradeWindowHeight = INVENTORY_TRADE_WINDOW_HEIGHT + extraSlots * INVENTORY_SLOT_HEIGHT; // Trade inventory window is a part of game dialog, which is 640x480. Rect bgWindowRect; windowGetRect(gInventoryBarterBackgroundWindow, &bgWindowRect); int tradeWindowX = bgWindowRect.left + INVENTORY_TRADE_WINDOW_X; int tradeWindowY = bgWindowRect.top + INVENTORY_TRADE_WINDOW_Y; gInventoryWindow = windowCreate(tradeWindowX, tradeWindowY, INVENTORY_TRADE_WINDOW_WIDTH, tradeWindowHeight, 257, 0); gInventoryWindowMaxX = tradeWindowX + INVENTORY_TRADE_WINDOW_WIDTH; gInventoryWindowMaxY = tradeWindowY + tradeWindowHeight; Buffer2D dest { windowGetBuffer(gInventoryWindow), INVENTORY_TRADE_WINDOW_WIDTH, tradeWindowHeight }; ConstBuffer2D src { windowGetBuffer(gInventoryBarterBackgroundWindow), INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH, windowGetHeight(gInventoryBarterBackgroundWindow) }; blitBuffer2D(src, INVENTORY_TRADE_WINDOW_X, 0, INVENTORY_TRADE_WINDOW_WIDTH, tradeWindowHeight, dest); gInventoryPrintItemDescriptionHandler = gameDialogRenderSupplementaryMessage; } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { inventoryCreateSlotButtons(1000, inventoryLootLayout.leftScrollerX, inventoryLootLayout.leftScrollerY, inventoryLootLayout.columns); inventoryCreateSlotButtons(2000, inventoryLootLayout.rightScrollerX, inventoryLootLayout.rightScrollerY, inventoryLootLayout.columns); } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { int y1 = INVENTORY_TRADE_SCROLLER_Y; int y2 = INVENTORY_TRADE_INNER_SCROLLER_Y; for (int index = 0; index < gInventorySlotsCount; index++) { buttonCreateSlot(gInventoryWindow, INVENTORY_TRADE_LEFT_SCROLLER_X, y1, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 1000 + index, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit); buttonCreateSlot(gInventoryWindow, INVENTORY_TRADE_RIGHT_SCROLLER_X, y1, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 2000 + index, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit); buttonCreateSlot(gInventoryWindow, INVENTORY_TRADE_INNER_LEFT_SCROLLER_X, y2, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 2300 + index, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit); buttonCreateSlot(gInventoryWindow, INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X, y2, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 2400 + index, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit); y1 += INVENTORY_SLOT_HEIGHT; y2 += INVENTORY_SLOT_HEIGHT; } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { inventoryCreateSlotButtons(1000, inventoryLayout.scrollerX, inventoryLayout.scrollerY, inventoryLayout.columns); } else { // Create invisible buttons representing item slots. for (int index = 0; index < gInventorySlotsCount; index++) { buttonCreateSlot(gInventoryWindow, INVENTORY_SCROLLER_X, INVENTORY_SLOT_HEIGHT * (gInventorySlotsCount - index - 1) + INVENTORY_SCROLLER_Y, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 999 + gInventorySlotsCount - index, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit); } } if (isNormalWindow) { // Item2 slot buttonCreateSlot(gInventoryWindow, inventoryLayout.rightHandSlotX, INVENTORY_RIGHT_HAND_SLOT_Y, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, INVENTORY_HAND_RIGHT_KEY, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit); // Item1 slot buttonCreateSlot(gInventoryWindow, inventoryLayout.leftHandSlotX, INVENTORY_LEFT_HAND_SLOT_Y, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, INVENTORY_HAND_LEFT_KEY, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit); // Armor slot buttonCreateSlot(gInventoryWindow, inventoryLayout.armorSlotX, INVENTORY_ARMOR_SLOT_Y, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, INVENTORY_ARMOR_KEY, inventoryItemSlotOnMouseEnter, inventoryItemSlotOnMouseExit); } int fid; int btn; fid = buildFid(OBJ_TYPE_INTERFACE, 8, 0, 0, 0); _inventoryFrmImages[0].lock(fid); fid = buildFid(OBJ_TYPE_INTERFACE, 9, 0, 0, 0); _inventoryFrmImages[1].lock(fid); if (_inventoryFrmImages[0].isLocked() && _inventoryFrmImages[1].isLocked()) { btn = -1; switch (inventoryWindowType) { case INVENTORY_WINDOW_TYPE_NORMAL: // Done button btn = buttonCreate(gInventoryWindow, inventoryLayout.doneButtonX, 329, 15, 16, -1, -1, -1, KEY_ESCAPE, _inventoryFrmImages[0].getData(), _inventoryFrmImages[1].getData(), nullptr, BUTTON_FLAG_TRANSPARENT); break; case INVENTORY_WINDOW_TYPE_USE_ITEM_ON: // Cancel button btn = buttonCreate(gInventoryWindow, 233, 328, 15, 16, -1, -1, -1, KEY_ESCAPE, _inventoryFrmImages[0].getData(), _inventoryFrmImages[1].getData(), nullptr, BUTTON_FLAG_TRANSPARENT); break; case INVENTORY_WINDOW_TYPE_LOOT: // Done button btn = buttonCreate(gInventoryWindow, inventoryLootLayout.doneButtonX, 331, 15, 16, -1, -1, -1, KEY_ESCAPE, _inventoryFrmImages[0].getData(), _inventoryFrmImages[1].getData(), nullptr, BUTTON_FLAG_TRANSPARENT); break; } if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { // Large arrow up (normal). fid = buildFid(OBJ_TYPE_INTERFACE, 100, 0, 0, 0); _inventoryFrmImages[2].lock(fid); // Large arrow up (pressed). fid = buildFid(OBJ_TYPE_INTERFACE, 101, 0, 0, 0); _inventoryFrmImages[3].lock(fid); if (_inventoryFrmImages[2].isLocked() && _inventoryFrmImages[3].isLocked()) { // Left inventory up button. btn = buttonCreate(gInventoryWindow, 109, 56, 23, 24, -1, -1, KEY_ARROW_UP, -1, _inventoryFrmImages[2].getData(), _inventoryFrmImages[3].getData()); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } // Right inventory up button. btn = buttonCreate(gInventoryWindow, 342, 56, 23, 24, -1, -1, KEY_CTRL_ARROW_UP, -1, _inventoryFrmImages[2].getData(), _inventoryFrmImages[3].getData()); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } } } else { // Large up arrow (normal). fid = buildFid(OBJ_TYPE_INTERFACE, 49, 0, 0, 0); _inventoryFrmImages[2].lock(fid); // Large up arrow (pressed). fid = buildFid(OBJ_TYPE_INTERFACE, 50, 0, 0, 0); _inventoryFrmImages[3].lock(fid); // Large up arrow (disabled). fid = buildFid(OBJ_TYPE_INTERFACE, 53, 0, 0, 0); _inventoryFrmImages[4].lock(fid); if (_inventoryFrmImages[2].isLocked() && _inventoryFrmImages[3].isLocked() && _inventoryFrmImages[4].isLocked()) { if (inventoryWindowType != INVENTORY_WINDOW_TYPE_TRADE) { int scrollUpX = isNormalWindow ? inventoryLayout.scrollButtonX : inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT ? inventoryLootLayout.leftScrollButtonX : 128; // Left inventory up button. gInventoryScrollUpButton = buttonCreate(gInventoryWindow, scrollUpX, 39, 22, 23, -1, -1, KEY_ARROW_UP, -1, _inventoryFrmImages[2].getData(), _inventoryFrmImages[3].getData()); if (gInventoryScrollUpButton != -1) { _win_register_button_disable(gInventoryScrollUpButton, _inventoryFrmImages[4].getData(), _inventoryFrmImages[4].getData(), _inventoryFrmImages[4].getData()); buttonSetCallbacks(gInventoryScrollUpButton, _gsound_red_butt_press, _gsound_red_butt_release); buttonDisable(gInventoryScrollUpButton); } } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { // Right inventory up button. gSecondaryInventoryScrollUpButton = buttonCreate(gInventoryWindow, inventoryLootLayout.rightScrollButtonX, 39, 22, 23, -1, -1, KEY_CTRL_ARROW_UP, -1, _inventoryFrmImages[2].getData(), _inventoryFrmImages[3].getData()); if (gSecondaryInventoryScrollUpButton != -1) { _win_register_button_disable(gSecondaryInventoryScrollUpButton, _inventoryFrmImages[4].getData(), _inventoryFrmImages[4].getData(), _inventoryFrmImages[4].getData()); buttonSetCallbacks(gSecondaryInventoryScrollUpButton, _gsound_red_butt_press, _gsound_red_butt_release); buttonDisable(gSecondaryInventoryScrollUpButton); } } } } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { // Large dialog down button (normal) fid = buildFid(OBJ_TYPE_INTERFACE, 93, 0, 0, 0); _inventoryFrmImages[5].lock(fid); // Dialog down button (pressed) fid = buildFid(OBJ_TYPE_INTERFACE, 94, 0, 0, 0); _inventoryFrmImages[6].lock(fid); if (_inventoryFrmImages[5].isLocked() && _inventoryFrmImages[6].isLocked()) { // Left inventory down button. btn = buttonCreate(gInventoryWindow, 109, 82, 24, 25, -1, -1, KEY_ARROW_DOWN, -1, _inventoryFrmImages[5].getData(), _inventoryFrmImages[6].getData()); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } // Right inventory down button btn = buttonCreate(gInventoryWindow, 342, 82, 24, 25, -1, -1, KEY_CTRL_ARROW_DOWN, -1, _inventoryFrmImages[5].getData(), _inventoryFrmImages[6].getData()); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } // Invisible button representing left character. buttonCreateAction(gInventoryBarterBackgroundWindow, INVENTORY_BARTER_LEFT_BODY_VIEW_X, INVENTORY_BARTER_LEFT_BODY_VIEW_Y, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, 2500); // Invisible button representing right character. buttonCreateAction(gInventoryBarterBackgroundWindow, 560, 25, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, 2501); } } else { // Large arrow down (normal). fid = buildFid(OBJ_TYPE_INTERFACE, 51, 0, 0, 0); _inventoryFrmImages[5].lock(fid); // Large arrow down (pressed). fid = buildFid(OBJ_TYPE_INTERFACE, 52, 0, 0, 0); _inventoryFrmImages[6].lock(fid); // Large arrow down (disabled). fid = buildFid(OBJ_TYPE_INTERFACE, 54, 0, 0, 0); _inventoryFrmImages[7].lock(fid); if (_inventoryFrmImages[5].isLocked() && _inventoryFrmImages[6].isLocked() && _inventoryFrmImages[7].isLocked()) { int scrollDownX = isNormalWindow ? inventoryLayout.scrollButtonX : inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT ? inventoryLootLayout.leftScrollButtonX : 128; // Left inventory down button. gInventoryScrollDownButton = buttonCreate(gInventoryWindow, scrollDownX, 62, 22, 23, -1, -1, KEY_ARROW_DOWN, -1, _inventoryFrmImages[5].getData(), _inventoryFrmImages[6].getData()); buttonSetCallbacks(gInventoryScrollDownButton, _gsound_red_butt_press, _gsound_red_butt_release); _win_register_button_disable(gInventoryScrollDownButton, _inventoryFrmImages[7].getData(), _inventoryFrmImages[7].getData(), _inventoryFrmImages[7].getData()); buttonDisable(gInventoryScrollDownButton); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { // Invisible button representing left character. buttonCreateAction(gInventoryWindow, inventoryLootLayout.leftBodyViewX, INVENTORY_LOOT_LEFT_BODY_VIEW_Y, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, 2500); // Right inventory down button. gSecondaryInventoryScrollDownButton = buttonCreate(gInventoryWindow, inventoryLootLayout.rightScrollButtonX, 62, 22, 23, -1, -1, KEY_CTRL_ARROW_DOWN, -1, _inventoryFrmImages[5].getData(), _inventoryFrmImages[6].getData()); if (gSecondaryInventoryScrollDownButton != -1) { buttonSetCallbacks(gSecondaryInventoryScrollDownButton, _gsound_red_butt_press, _gsound_red_butt_release); _win_register_button_disable(gSecondaryInventoryScrollDownButton, _inventoryFrmImages[7].getData(), _inventoryFrmImages[7].getData(), _inventoryFrmImages[7].getData()); buttonDisable(gSecondaryInventoryScrollDownButton); } // Invisible button representing right character. buttonCreateAction(gInventoryWindow, inventoryLootLayout.rightBodyViewX, INVENTORY_LOOT_RIGHT_BODY_VIEW_Y, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, 2501); } else { // Invisible button representing character (in inventory and use on dialogs). buttonCreateAction(gInventoryWindow, isNormalWindow ? inventoryLayout.bodyViewX : INVENTORY_PC_BODY_VIEW_X, INVENTORY_PC_BODY_VIEW_Y, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, 2500); } } } if (inventoryWindowType != INVENTORY_WINDOW_TYPE_TRADE) { if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { if (!_gIsSteal) { // Take all button (normal) fid = buildFid(OBJ_TYPE_INTERFACE, 436, 0, 0, 0); _inventoryFrmImages[8].lock(fid); // Take all button (pressed) fid = buildFid(OBJ_TYPE_INTERFACE, 437, 0, 0, 0); _inventoryFrmImages[9].lock(fid); if (_inventoryFrmImages[8].isLocked() && _inventoryFrmImages[9].isLocked()) { // Take all button. btn = buttonCreate(gInventoryWindow, inventoryLootLayout.takeAllButtonX, 204, 39, 41, -1, -1, 2502, -1, _inventoryFrmImages[8].getData(), _inventoryFrmImages[9].getData()); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } } } } } else { // Inventory button up (normal) fid = buildFid(OBJ_TYPE_INTERFACE, 49, 0, 0, 0); _inventoryFrmImages[8].lock(fid); // Inventory button up (pressed) fid = buildFid(OBJ_TYPE_INTERFACE, 50, 0, 0, 0); _inventoryFrmImages[9].lock(fid); if (_inventoryFrmImages[8].isLocked() && _inventoryFrmImages[9].isLocked()) { // Left offered inventory up button. btn = buttonCreate(gInventoryWindow, 128, 113, 22, 23, -1, -1, KEY_PAGE_UP, -1, _inventoryFrmImages[8].getData(), _inventoryFrmImages[9].getData()); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } // Right offered inventory up button. btn = buttonCreate(gInventoryWindow, 333, 113, 22, 23, -1, -1, KEY_CTRL_PAGE_UP, -1, _inventoryFrmImages[8].getData(), _inventoryFrmImages[9].getData()); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } } // Inventory button down (normal) fid = buildFid(OBJ_TYPE_INTERFACE, 51, 0, 0, 0); _inventoryFrmImages[10].lock(fid); // Inventory button down (pressed). fid = buildFid(OBJ_TYPE_INTERFACE, 52, 0, 0, 0); _inventoryFrmImages[11].lock(fid); if (_inventoryFrmImages[10].isLocked() && _inventoryFrmImages[11].isLocked()) { // Left offered inventory down button. btn = buttonCreate(gInventoryWindow, 128, 136, 22, 23, -1, -1, KEY_PAGE_DOWN, -1, _inventoryFrmImages[10].getData(), _inventoryFrmImages[11].getData()); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } // Right offered inventory down button. btn = buttonCreate(gInventoryWindow, 333, 136, 22, 23, -1, -1, KEY_CTRL_PAGE_DOWN, -1, _inventoryFrmImages[10].getData(), _inventoryFrmImages[11].getData()); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } } } gInventoryRightHandItem = nullptr; gInventoryArmor = nullptr; gInventoryLeftHandItem = nullptr; for (int index = 0; index < _pud->length; index++) { InventoryItem* inventoryItem = &(_pud->items[index]); Object* item = inventoryItem->item; if ((item->flags & OBJECT_IN_LEFT_HAND) != 0) { if ((item->flags & OBJECT_IN_RIGHT_HAND) != 0) { gInventoryRightHandItem = item; } gInventoryLeftHandItem = item; } else if ((item->flags & OBJECT_IN_RIGHT_HAND) != 0) { gInventoryRightHandItem = item; } else if ((item->flags & OBJECT_WORN) != 0) { gInventoryArmor = item; } } if (gInventoryLeftHandItem != nullptr) { itemRemove(_inven_dude, gInventoryLeftHandItem, 1); } if (gInventoryRightHandItem != nullptr && gInventoryRightHandItem != gInventoryLeftHandItem) { itemRemove(_inven_dude, gInventoryRightHandItem, 1); } if (gInventoryArmor != nullptr) { itemRemove(_inven_dude, gInventoryArmor, 1); } _adjust_fid(); bool isoWasEnabled = isoDisable(); _gmouse_disable(0); touch_set_touchscreen_mode(true); touch_set_pan_mode(true); return isoWasEnabled; } // 0x46FBD8 static void _exit_inventory(bool shouldEnableIso) { _inven_dude = _stack[0]; if (gInventoryLeftHandItem != nullptr) { gInventoryLeftHandItem->flags |= OBJECT_IN_LEFT_HAND; if (gInventoryLeftHandItem == gInventoryRightHandItem) { gInventoryLeftHandItem->flags |= OBJECT_IN_RIGHT_HAND; } itemAdd(_inven_dude, gInventoryLeftHandItem, 1); } if (gInventoryRightHandItem != nullptr && gInventoryRightHandItem != gInventoryLeftHandItem) { gInventoryRightHandItem->flags |= OBJECT_IN_RIGHT_HAND; itemAdd(_inven_dude, gInventoryRightHandItem, 1); } if (gInventoryArmor != nullptr) { gInventoryArmor->flags |= OBJECT_WORN; itemAdd(_inven_dude, gInventoryArmor, 1); } gInventoryRightHandItem = nullptr; gInventoryArmor = nullptr; gInventoryLeftHandItem = nullptr; for (int index = 0; index < INVENTORY_FRM_COUNT; index++) { _inventoryFrmImages[index].unlock(); } if (shouldEnableIso) { isoEnable(); } windowDestroy(gInventoryWindow); inventoryFrmImage.unlock(); inventoryLootFrmImage.unlock(); _gmouse_enable(); touch_set_touchscreen_mode(false); touch_set_pan_mode(false); if (_dropped_explosive) { Attack attack; attackInit(&attack, gDude, nullptr, HIT_MODE_PUNCH, HIT_LOCATION_TORSO); attack.attackerFlags = DAM_HIT; attack.tile = gDude->tile; _compute_explosion_on_extras(&attack, 0, 0, 1); Object* watcher = nullptr; for (int index = 0; index < attack.extrasLength; index++) { Object* critter = attack.extras[index]; if (critter != gDude && critter->data.critter.combat.team != gDude->data.critter.combat.team && statRoll(critter, STAT_PERCEPTION, 0, nullptr) >= ROLL_SUCCESS) { critterSetWhoHitMe(critter, gDude); if (watcher == nullptr) { watcher = critter; } } } if (watcher != nullptr) { if (!isInCombat()) { CombatStartData combat; combat.attacker = watcher; combat.defender = gDude; combat.actionPointsBonus = 0; combat.accuracyBonus = 0; combat.damageBonus = 0; combat.minDamage = 0; combat.maxDamage = INT_MAX; combat.overrideAttackResults = 0; scriptsRequestCombat(&combat); } } _dropped_explosive = false; } } // 0x46FDF4 static void _display_inventory(int stackOffset, int dragSlotIndex, int inventoryWindowType) { unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); int pitch; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { pitch = inventoryLayout.windowWidth; unsigned char* backgroundData = inventoryFrmImage.getData(); int backgroundWidth = inventoryFrmImage.getWidth(); if (backgroundData != nullptr) { // Clear scroll view background. blitBufferToBuffer(backgroundData + backgroundWidth * inventoryLayout.scrollerY + inventoryLayout.scrollerX, inventoryLayout.scrollerWidth, inventoryLayout.scrollerHeight, backgroundWidth, windowBuffer + pitch * inventoryLayout.scrollerY + inventoryLayout.scrollerX, pitch); // Clear armor button background. blitBufferToBuffer(backgroundData + backgroundWidth * INVENTORY_ARMOR_SLOT_Y + inventoryLayout.armorSlotX, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, backgroundWidth, windowBuffer + pitch * INVENTORY_ARMOR_SLOT_Y + inventoryLayout.armorSlotX, pitch); if (gInventoryLeftHandItem != nullptr && gInventoryLeftHandItem == gInventoryRightHandItem) { blitBufferToBuffer(backgroundData + backgroundWidth * INVENTORY_LEFT_HAND_SLOT_Y + inventoryLayout.leftHandSlotX, INVENTORY_LARGE_SLOT_WIDTH * 2, INVENTORY_LARGE_SLOT_HEIGHT, backgroundWidth, windowBuffer + pitch * INVENTORY_LEFT_HAND_SLOT_Y + inventoryLayout.leftHandSlotX, pitch); } else { // Clear both items in one go. blitBufferToBuffer(backgroundData + backgroundWidth * INVENTORY_LEFT_HAND_SLOT_Y + inventoryLayout.leftHandSlotX, INVENTORY_LARGE_SLOT_WIDTH * 2, INVENTORY_LARGE_SLOT_HEIGHT, backgroundWidth, windowBuffer + pitch * INVENTORY_LEFT_HAND_SLOT_Y + inventoryLayout.leftHandSlotX, pitch); } } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_USE_ITEM_ON) { pitch = INVENTORY_USE_ON_WINDOW_WIDTH; FrmImage backgroundFrmImage; int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, 113, 0, 0, 0); if (backgroundFrmImage.lock(backgroundFid)) { // Clear scroll view background. blitBufferToBuffer(backgroundFrmImage.getData() + pitch * INVENTORY_SCROLLER_Y + INVENTORY_SCROLLER_X, INVENTORY_SLOT_WIDTH, gInventorySlotsCount * INVENTORY_SLOT_HEIGHT, pitch, windowBuffer + pitch * INVENTORY_SCROLLER_Y + INVENTORY_SCROLLER_X, pitch); } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { pitch = inventoryLootLayout.windowWidth; unsigned char* backgroundData = inventoryLootFrmImage.getData(); int backgroundWidth = inventoryLootFrmImage.getWidth(); if (backgroundData != nullptr) { // Clear scroll view background. blitBufferToBuffer(backgroundData + backgroundWidth * inventoryLootLayout.leftScrollerY + inventoryLootLayout.leftScrollerX, inventoryLootLayout.scrollerWidth, inventoryLootLayout.scrollerHeight, backgroundWidth, windowBuffer + pitch * inventoryLootLayout.leftScrollerY + inventoryLootLayout.leftScrollerX, pitch); } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { pitch = INVENTORY_TRADE_WINDOW_WIDTH; windowBuffer = windowGetBuffer(gInventoryWindow); blitBufferToBuffer(windowGetBuffer(gInventoryBarterBackgroundWindow) + INVENTORY_TRADE_LEFT_SCROLLER_Y * INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH + INVENTORY_TRADE_LEFT_SCROLLER_X + INVENTORY_TRADE_WINDOW_OFFSET, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount, INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH, windowBuffer + pitch * INVENTORY_TRADE_LEFT_SCROLLER_Y + INVENTORY_TRADE_LEFT_SCROLLER_X, pitch); } else { assert(false && "Should be unreachable"); } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL || inventoryWindowType == INVENTORY_WINDOW_TYPE_USE_ITEM_ON || inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { if (gInventoryScrollUpButton != -1) { if (stackOffset <= 0) { buttonDisable(gInventoryScrollUpButton); } else { buttonEnable(gInventoryScrollUpButton); } } if (gInventoryScrollDownButton != -1) { int visibleSlots = inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL ? inventoryLayout.visibleSlots : gInventorySlotsCount; if (_pud->length - stackOffset <= visibleSlots) { buttonDisable(gInventoryScrollDownButton); } else { buttonEnable(gInventoryScrollDownButton); } } } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { for (int slotIndex = 0; slotIndex + stackOffset < _pud->length && slotIndex < inventoryLayout.visibleSlots; slotIndex += 1) { int itemIndex = slotIndex + stackOffset + 1; int row = slotIndex / inventoryLayout.columns; int column = slotIndex % inventoryLayout.columns; int itemPaddingY = INVENTORY_SLOT_PADDING + (inventoryLayout.columns > 1 ? INVENTORY_SLOT_PADDING : 0); int offset = pitch * (inventoryLayout.scrollerY + row * INVENTORY_SLOT_HEIGHT + itemPaddingY) + inventoryLayout.scrollerX + column * INVENTORY_SLOT_WIDTH + INVENTORY_SLOT_PADDING; InventoryItem* inventoryItem = &(_pud->items[_pud->length - itemIndex]); int inventoryFid = itemGetInventoryFid(inventoryItem->item); artRender(inventoryFid, windowBuffer + offset, INVENTORY_SLOT_WIDTH_PAD, INVENTORY_SLOT_HEIGHT_PAD, pitch); _display_inventory_info(inventoryItem->item, inventoryItem->quantity, windowBuffer + offset, pitch, slotIndex == dragSlotIndex); } } else { int y = 0; for (int slotIndex = 0; slotIndex + stackOffset < _pud->length && slotIndex < gInventorySlotsCount; slotIndex += 1) { int itemIndex = slotIndex + stackOffset + 1; int offset; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { offset = pitch * (y + INVENTORY_TRADE_LEFT_SCROLLER_Y_PAD) + INVENTORY_TRADE_LEFT_SCROLLER_X_PAD; } else { if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { offset = pitch * (inventoryLootGetSlotY(slotIndex) + INVENTORY_SLOT_PADDING) + inventoryLootGetSlotX(false, slotIndex) + INVENTORY_SLOT_PADDING; } else { offset = pitch * (y + INVENTORY_SCROLLER_Y_PAD) + INVENTORY_SCROLLER_X_PAD; } } InventoryItem* inventoryItem = &(_pud->items[_pud->length - itemIndex]); int inventoryFid = itemGetInventoryFid(inventoryItem->item); artRender(inventoryFid, windowBuffer + offset, INVENTORY_SLOT_WIDTH_PAD, INVENTORY_SLOT_HEIGHT_PAD, pitch); _display_inventory_info(inventoryItem->item, inventoryItem->quantity, windowBuffer + offset, pitch, slotIndex == dragSlotIndex); if (inventoryWindowType != INVENTORY_WINDOW_TYPE_LOOT) { y += INVENTORY_SLOT_HEIGHT; } } } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { if (gInventoryRightHandItem != nullptr) { int width = gInventoryRightHandItem == gInventoryLeftHandItem ? INVENTORY_LARGE_SLOT_WIDTH * 2 : INVENTORY_LARGE_SLOT_WIDTH; int inventoryFid = itemGetInventoryFid(gInventoryRightHandItem); artRender(inventoryFid, windowBuffer + pitch * INVENTORY_RIGHT_HAND_SLOT_Y + inventoryLayout.rightHandSlotX, width, INVENTORY_LARGE_SLOT_HEIGHT, pitch); } if (gInventoryLeftHandItem != nullptr && gInventoryLeftHandItem != gInventoryRightHandItem) { int inventoryFid = itemGetInventoryFid(gInventoryLeftHandItem); artRender(inventoryFid, windowBuffer + pitch * INVENTORY_LEFT_HAND_SLOT_Y + inventoryLayout.leftHandSlotX, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, pitch); } if (gInventoryArmor != nullptr) { int inventoryFid = itemGetInventoryFid(gInventoryArmor); artRender(inventoryFid, windowBuffer + pitch * INVENTORY_ARMOR_SLOT_Y + inventoryLayout.armorSlotX, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, pitch); } } // CE: Show items weight. if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { char formattedText[20]; int oldFont = fontGetCurrent(); fontSetCurrent(101); FrmImage backgroundFrm; if (inventoryLootFrmImage.isLocked()) { int x = inventoryLootLayout.leftScrollerX; int y = inventoryLootLayout.leftScrollerY + inventoryLootLayout.scrollerHeight + 2; blitBufferToBuffer(inventoryLootFrmImage.getData() + inventoryLootFrmImage.getWidth() * y + x, inventoryLootLayout.scrollerWidth, fontGetLineHeight(), inventoryLootFrmImage.getWidth(), windowBuffer + pitch * y + x, pitch); } Object* object = _stack[0]; int color = _colorTable[992]; if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { int carryWeight = critterGetStat(object, STAT_CARRY_WEIGHT); int inventoryWeight = objectGetInventoryWeight(object); snprintf(formattedText, sizeof(formattedText), "%d/%d", inventoryWeight, carryWeight); if (inventoryWeight > carryWeight) { color = _colorTable[31744]; } } else { int inventoryWeight = objectGetInventoryWeight(object); snprintf(formattedText, sizeof(formattedText), "%d", inventoryWeight); } int y = inventoryLootLayout.leftScrollerY + inventoryLootLayout.scrollerHeight + 2; inventoryDrawCenteredText(windowBuffer, pitch, inventoryLootLayout.scrollerWidth, inventoryLootLayout.leftScrollerX, y, formattedText, color); fontSetCurrent(oldFont); } windowRefresh(gInventoryWindow); } // Render inventory item. // // [stackOffset] is an index of the first visible item in the scrolling view. // [dragSlotIndex] is an index of item being dragged (it decreases displayed number of items in inner functions). // // 0x47036C static void _display_target_inventory(int stackOffset, int dragSlotIndex, Inventory* inventory, int inventoryWindowType) { unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); int pitch; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { pitch = inventoryLootLayout.windowWidth; unsigned char* backgroundData = inventoryLootFrmImage.getData(); int backgroundWidth = inventoryLootFrmImage.getWidth(); if (backgroundData != nullptr) { blitBufferToBuffer(backgroundData + backgroundWidth * inventoryLootLayout.rightScrollerY + inventoryLootLayout.rightScrollerX, inventoryLootLayout.scrollerWidth, inventoryLootLayout.scrollerHeight, backgroundWidth, windowBuffer + pitch * inventoryLootLayout.rightScrollerY + inventoryLootLayout.rightScrollerX, pitch); } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { pitch = INVENTORY_TRADE_WINDOW_WIDTH; unsigned char* src = windowGetBuffer(gInventoryBarterBackgroundWindow); blitBufferToBuffer(src + INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH * INVENTORY_TRADE_RIGHT_SCROLLER_Y + INVENTORY_TRADE_RIGHT_SCROLLER_X + INVENTORY_TRADE_WINDOW_OFFSET, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount, INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH, windowBuffer + INVENTORY_TRADE_WINDOW_WIDTH * INVENTORY_TRADE_RIGHT_SCROLLER_Y + INVENTORY_TRADE_RIGHT_SCROLLER_X, INVENTORY_TRADE_WINDOW_WIDTH); } else { assert(false && "Should be unreachable"); } int y = 0; for (int slotIndex = 0; slotIndex < gInventorySlotsCount; slotIndex++) { int itemIndex = stackOffset + slotIndex; if (itemIndex >= inventory->length) { break; } int offset; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { offset = pitch * (inventoryLootGetSlotY(slotIndex) + INVENTORY_SLOT_PADDING) + inventoryLootGetSlotX(true, slotIndex) + INVENTORY_SLOT_PADDING; } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { offset = pitch * (y + INVENTORY_TRADE_RIGHT_SCROLLER_Y_PAD) + INVENTORY_TRADE_RIGHT_SCROLLER_X_PAD; } else { assert(false && "Should be unreachable"); } InventoryItem* inventoryItem = &(inventory->items[inventory->length - (itemIndex + 1)]); int inventoryFid = itemGetInventoryFid(inventoryItem->item); artRender(inventoryFid, windowBuffer + offset, INVENTORY_SLOT_WIDTH_PAD, INVENTORY_SLOT_HEIGHT_PAD, pitch); _display_inventory_info(inventoryItem->item, inventoryItem->quantity, windowBuffer + offset, pitch, slotIndex == dragSlotIndex); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { y += INVENTORY_SLOT_HEIGHT; } } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { if (gSecondaryInventoryScrollUpButton != -1) { if (stackOffset <= 0) { buttonDisable(gSecondaryInventoryScrollUpButton); } else { buttonEnable(gSecondaryInventoryScrollUpButton); } } if (gSecondaryInventoryScrollDownButton != -1) { if (inventory->length - stackOffset <= gInventorySlotsCount) { buttonDisable(gSecondaryInventoryScrollDownButton); } else { buttonEnable(gSecondaryInventoryScrollDownButton); } } } // CE: Show items weight. if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { char formattedText[20]; formattedText[0] = '\0'; int oldFont = fontGetCurrent(); fontSetCurrent(101); if (inventoryLootFrmImage.isLocked()) { int x = inventoryLootLayout.rightScrollerX; int y = inventoryLootLayout.rightScrollerY + inventoryLootLayout.scrollerHeight + 2; blitBufferToBuffer(inventoryLootFrmImage.getData() + inventoryLootFrmImage.getWidth() * y + x, inventoryLootLayout.scrollerWidth, fontGetLineHeight(), inventoryLootFrmImage.getWidth(), windowBuffer + pitch * y + x, pitch); } Object* object = _target_stack[_target_curr_stack]; int color = _colorTable[992]; if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { int currentWeight = objectGetInventoryWeight(object) + inventoryLootRightEquippedWeight; int maxWeight = critterGetStat(object, STAT_CARRY_WEIGHT); snprintf(formattedText, sizeof(formattedText), "%d/%d", currentWeight, maxWeight); if (currentWeight > maxWeight) { color = _colorTable[31744]; } } else if (PID_TYPE(object->pid) == OBJ_TYPE_ITEM) { if (itemGetType(object) == ITEM_TYPE_CONTAINER) { int currentSize = containerGetTotalSize(object); int maxSize = containerGetMaxSize(object); snprintf(formattedText, sizeof(formattedText), "%d/%d", currentSize, maxSize); } } else { int inventoryWeight = objectGetInventoryWeight(object); snprintf(formattedText, sizeof(formattedText), "%d", inventoryWeight); } int y = inventoryLootLayout.rightScrollerY + inventoryLootLayout.scrollerHeight + 2; inventoryDrawCenteredText(windowBuffer, pitch, inventoryLootLayout.scrollerWidth, inventoryLootLayout.rightScrollerX, y, formattedText, color); fontSetCurrent(oldFont); } } // Renders inventory item quantity. // // 0x4705A0 static void _display_inventory_info(Object* item, int quantity, unsigned char* dest, int pitch, bool isDragged) { int oldFont = fontGetCurrent(); fontSetCurrent(101); char formattedText[12]; // NOTE: Original code is slightly different and probably used goto. bool draw = false; if (itemGetType(item) == ITEM_TYPE_AMMO) { int ammoQuantity = ammoGetCapacity(item) * (quantity - 1); if (!isDragged) { ammoQuantity += ammoGetQuantity(item); } if (ammoQuantity > 99999) { ammoQuantity = 99999; } snprintf(formattedText, sizeof(formattedText), "x%d", ammoQuantity); draw = true; } else { if (quantity > 1) { int displayedQuantity = quantity; if (isDragged) { displayedQuantity -= 1; } if (quantity > 1) { if (displayedQuantity > 99999) { displayedQuantity = 99999; } snprintf(formattedText, sizeof(formattedText), "x%d", displayedQuantity); draw = true; } } } if (draw) { fontDrawText(dest, formattedText, 80, pitch, _colorTable[32767]); } fontSetCurrent(oldFont); } // 0x470650 static void _display_body(int fid, int inventoryWindowType) { if (getTicksSince(gInventoryWindowDudeRotationTimestamp) < INVENTORY_NORMAL_WINDOW_PC_ROTATION_DELAY) { return; } gInventoryWindowDudeRotation += 1; if (gInventoryWindowDudeRotation == ROTATION_COUNT) { gInventoryWindowDudeRotation = 0; } int rotations[2]; if (fid == -1) { rotations[0] = gInventoryWindowDudeRotation; rotations[1] = ROTATION_SE; } else { rotations[0] = ROTATION_SW; rotations[1] = _target_stack[_target_curr_stack]->rotation; } int fids[2] = { gInventoryWindowDudeFid, fid, }; for (int index = 0; index < 2; index += 1) { int fid = fids[index]; if (fid == -1) { continue; } CacheEntry* handle; Art* art = artLock(fid, &handle); if (art == nullptr) { continue; } int frame = 0; if (index == 1) { frame = artGetFrameCount(art) - 1; } int rotation = rotations[index]; unsigned char* frameData = artGetFrameData(art, frame, rotation); int framePitch = artGetWidth(art, frame, rotation); int frameWidth = std::min(framePitch, INVENTORY_BODY_VIEW_WIDTH); int frameHeight = artGetHeight(art, frame, rotation); if (frameHeight > INVENTORY_BODY_VIEW_HEIGHT) { frameHeight = INVENTORY_BODY_VIEW_HEIGHT; } int win; Rect rect; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { unsigned char* windowBuffer = windowGetBuffer(gInventoryBarterBackgroundWindow); int windowPitch = windowGetWidth(gInventoryBarterBackgroundWindow); if (index == 1) { rect.left = 560; rect.top = 25; } else { rect.left = INVENTORY_BARTER_LEFT_BODY_VIEW_X; rect.top = INVENTORY_BARTER_LEFT_BODY_VIEW_Y; } rect.right = rect.left + INVENTORY_BODY_VIEW_WIDTH - 1; rect.bottom = rect.top + INVENTORY_BODY_VIEW_HEIGHT - 1; FrmImage backgroundFrmImage; int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, gGameDialogSpeakerIsPartyMember ? 420 : 111, 0, 0, 0); if (backgroundFrmImage.lock(backgroundFid)) { blitBufferToBuffer(backgroundFrmImage.getData() + rect.top * 640 + rect.left, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, 640, windowBuffer + windowPitch * rect.top + rect.left, windowPitch); } blitBufferToBufferTrans(frameData, frameWidth, frameHeight, framePitch, windowBuffer + windowPitch * (rect.top + (INVENTORY_BODY_VIEW_HEIGHT - frameHeight) / 2) + (INVENTORY_BODY_VIEW_WIDTH - frameWidth) / 2 + rect.left, windowPitch); inventoryDisplayLeftPaneCompanionName(windowBuffer, windowPitch, rect, index); win = gInventoryBarterBackgroundWindow; } else { unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); int windowPitch = windowGetWidth(gInventoryWindow); FrmImage backgroundFrmImage; int Fid = INVENTORY_LOOT_BACKGROUND_FRM_ID; int sourceXOffset = 0; if (index == 1) { if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { rect.left = inventoryLootLayout.rightBodyViewX; rect.top = INVENTORY_LOOT_RIGHT_BODY_VIEW_Y; } else { rect.left = 297; // inventory data window? ?not used? rect.top = 37; Fid = INVENTORY_NORMAL_BACKGROUND_FRM_ID; sourceXOffset = 229; } } else { if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { rect.left = inventoryLootLayout.leftBodyViewX; rect.top = INVENTORY_LOOT_LEFT_BODY_VIEW_Y; } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_USE_ITEM_ON) { rect.left = 176; // Use item cha window rect.top = 37; Fid = 113; sourceXOffset = 292; } else { rect.left = inventoryLayout.bodyViewX; // inventory cha window rect.top = 37; } } rect.right = rect.left + INVENTORY_BODY_VIEW_WIDTH - 1; rect.bottom = rect.top + INVENTORY_BODY_VIEW_HEIGHT - 1; Buffer2D dst { windowBuffer, windowPitch, windowGetHeight(gInventoryWindow) }; ConstBuffer2D background = inventoryGetBackgroundBuffer(inventoryWindowType, Fid, backgroundFrmImage, sourceXOffset); if (background) { blitBuffer2D(background, rect.left + sourceXOffset, rect.top, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, dst, rect.left, rect.top); } blitBufferToBufferTrans(frameData, frameWidth, frameHeight, framePitch, windowBuffer + windowPitch * (rect.top + (INVENTORY_BODY_VIEW_HEIGHT - frameHeight) / 2) + (INVENTORY_BODY_VIEW_WIDTH - frameWidth) / 2 + rect.left, windowPitch); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { inventoryDisplayLeftPaneCompanionName(windowBuffer, windowPitch, rect, index); } win = gInventoryWindow; } windowRefreshRect(win, &rect); artUnlock(handle); } gInventoryWindowDudeRotationTimestamp = getTicks(); } // 0x470A2C static int inventoryCommonInit() { if (inventoryMessageListInit() == -1) { return -1; } _inven_ui_was_disabled = gameUiIsDisabled(); if (_inven_ui_was_disabled) { gameUiEnable(); } gameMouseObjectsHide(); gameMouseSetCursor(MOUSE_CURSOR_ARROW); int index; for (index = 0; index < INVENTORY_WINDOW_CURSOR_COUNT; index++) { InventoryCursorData* cursorData = &(gInventoryCursorData[index]); int fid = buildFid(OBJ_TYPE_INTERFACE, gInventoryWindowCursorFrmIds[index], 0, 0, 0); Art* frm = artLock(fid, &(cursorData->frmHandle)); if (frm == nullptr) { break; } cursorData->frm = frm; cursorData->frmData = artGetFrameData(frm, 0, 0); cursorData->width = artGetWidth(frm, 0, 0); cursorData->height = artGetHeight(frm, 0, 0); artGetFrameOffsets(frm, 0, 0, &(cursorData->offsetX), &(cursorData->offsetY)); } if (index != INVENTORY_WINDOW_CURSOR_COUNT) { for (; index >= 0; index--) { artUnlock(gInventoryCursorData[index].frmHandle); } if (_inven_ui_was_disabled) { gameUiDisable(0); } messageListFree(&gInventoryMessageList); return -1; } _inven_is_initialized = true; _im_value = -1; return 0; } // NOTE: Inlined. // // 0x470B8C static void inventoryCommonFree() { for (int index = 0; index < INVENTORY_WINDOW_CURSOR_COUNT; index++) { artUnlock(gInventoryCursorData[index].frmHandle); } if (_inven_ui_was_disabled) { gameUiDisable(0); } // NOTE: Uninline. inventoryMessageListFree(); _inven_is_initialized = 0; } // 0x470BCC static void inventorySetCursor(int cursor) { gInventoryCursor = cursor; if (cursor != INVENTORY_WINDOW_CURSOR_ARROW || _im_value == -1) { InventoryCursorData* cursorData = &(gInventoryCursorData[cursor]); mouseSetFrame(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, cursorData->offsetX, cursorData->offsetY, 0); } else { inventoryItemSlotOnMouseEnter(-1, _im_value); } } // 0x470C2C static void inventoryItemSlotOnMouseEnter(int btn, int keyCode) { if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { int x; int y; mouseGetPositionInWindow(gInventoryWindow, &x, &y); Object* item = nullptr; if (_inven_from_button(keyCode, &item, nullptr, nullptr) != 0) { gameMouseRenderPrimaryAction(x, y, 3, gInventoryWindowMaxX, gInventoryWindowMaxY); int cursorHotspotX = 0; int cursorHotspotY = 0; _gmouse_3d_pick_frame_hot(&cursorHotspotX, &cursorHotspotY); InventoryCursorData* cursorData = &(gInventoryCursorData[INVENTORY_WINDOW_CURSOR_PICK]); mouseSetFrame(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, cursorHotspotX, cursorHotspotY, 0); if (item != _last_target) { objectLookAtFunc(_stack[0], item, gInventoryPrintItemDescriptionHandler); } } else { InventoryCursorData* cursorData = &(gInventoryCursorData[INVENTORY_WINDOW_CURSOR_ARROW]); mouseSetFrame(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, cursorData->offsetX, cursorData->offsetY, 0); } _last_target = item; } _im_value = keyCode; } // 0x470D1C static void inventoryItemSlotOnMouseExit(int btn, int keyCode) { if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { InventoryCursorData* cursorData = &(gInventoryCursorData[INVENTORY_WINDOW_CURSOR_ARROW]); mouseSetFrame(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, cursorData->offsetX, cursorData->offsetY, 0); } _im_value = -1; } // 0x470D5C static void _inven_update_lighting(Object* activeItem) { if (gDude == _inven_dude) { int lightDistance; if (activeItem != nullptr && activeItem->lightDistance > 4) { lightDistance = activeItem->lightDistance; } else { lightDistance = 4; } Rect rect; objectSetLight(_inven_dude, lightDistance, 0x10000, &rect); tileWindowRefreshRect(&rect, gElevation); } } // 0x470DB8 static void _inven_pickup(int buttonCode, int indexOffset) { Object* item; Object** itemSlot = nullptr; int count = _inven_from_button(buttonCode, &item, &itemSlot, nullptr); if (count == 0) { return; } int itemIndex = -1; Object* itemInHand = nullptr; Rect rect; switch (buttonCode) { case INVENTORY_HAND_RIGHT_KEY: rect.left = inventoryLayout.rightHandSlotX; rect.top = INVENTORY_RIGHT_HAND_SLOT_Y; if (_inven_dude == gDude && interfaceGetCurrentHand() != HAND_LEFT) { itemInHand = item; } break; case INVENTORY_HAND_LEFT_KEY: rect.left = inventoryLayout.leftHandSlotX; rect.top = INVENTORY_LEFT_HAND_SLOT_Y; if (_inven_dude == gDude && interfaceGetCurrentHand() == HAND_LEFT) { itemInHand = item; } break; case INVENTORY_ARMOR_KEY: rect.left = inventoryLayout.armorSlotX; rect.top = INVENTORY_ARMOR_SLOT_Y; break; default: // Normal inventory slot buttons use 1000-based key codes; convert // the button code to a zero-based slot index for grid positioning. itemIndex = buttonCode - 1000; rect.left = inventoryLayout.scrollerX + itemIndex % inventoryLayout.columns * INVENTORY_SLOT_WIDTH; rect.top = INVENTORY_SLOT_HEIGHT * (itemIndex / inventoryLayout.columns) + inventoryLayout.scrollerY; break; } bool pickUpFromSlot = itemIndex == -1; // true if item was picked up from armor or weapon slots if (pickUpFromSlot || _pud->items[_pud->length - (itemIndex + indexOffset + 1)].quantity <= 1) { // erase background unless item is part of a 2+ quantity stack unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); int width, height; if (gInventoryRightHandItem != gInventoryLeftHandItem || item != gInventoryLeftHandItem) { if (pickUpFromSlot) { height = INVENTORY_LARGE_SLOT_HEIGHT; width = INVENTORY_LARGE_SLOT_WIDTH; } else { height = INVENTORY_SLOT_HEIGHT; width = INVENTORY_SLOT_WIDTH; } } else { // seems to wipe both hand slots at once, but I don't know how to trigger this in game height = INVENTORY_LARGE_SLOT_HEIGHT; width = 180; rect.left = inventoryLayout.leftHandSlotX; rect.top = INVENTORY_LEFT_HAND_SLOT_Y; } rect.right = rect.left + width - 1; rect.bottom = rect.top + height - 1; unsigned char* backgroundData = inventoryFrmImage.getData(); int backgroundWidth = inventoryFrmImage.getWidth(); if (backgroundData != nullptr) { blitBufferToBuffer(backgroundData + backgroundWidth * rect.top + rect.left, width, height, backgroundWidth, windowBuffer + inventoryLayout.windowWidth * rect.top + rect.left, inventoryLayout.windowWidth); } windowRefreshRect(gInventoryWindow, &rect); } else { _display_inventory(indexOffset, itemIndex, INVENTORY_WINDOW_TYPE_NORMAL); } if (itemInHand != nullptr) { _inven_update_lighting(nullptr); } // allow ctrl-click to quick unequip or equip item bool immediate = _ctrl_pressed(); _drag_item_loop(item, immediate); // drag into inventory list, or ctrl-click from slot if (pickUpFromSlot && (immediate || mouseHitTestInWindow(gInventoryWindow, inventoryLayout.scrollerX, inventoryLayout.scrollerY, inventoryLayout.scrollerX + inventoryLayout.scrollerWidth, inventoryLayout.scrollerY + inventoryLayout.scrollerHeight))) { int x; int y; mouseGetPositionInWindow(gInventoryWindow, &x, &y); int row = (y - inventoryLayout.scrollerY) / INVENTORY_SLOT_HEIGHT; int column = (x - inventoryLayout.scrollerX) / INVENTORY_SLOT_WIDTH; if (row < 0) { row = 0; } else if (row >= INVENTORY_ROWS) { row = INVENTORY_ROWS - 1; } if (column < 0) { column = 0; } else if (column >= inventoryLayout.columns) { column = inventoryLayout.columns - 1; } int targetIndex = row * inventoryLayout.columns + column + indexOffset; if (!immediate && targetIndex < _pud->length) { Object* targetItem = _pud->items[_pud->length - (targetIndex + 1)].item; if (targetItem != item) { // Dropping item on top of another item. if (itemGetType(targetItem) == ITEM_TYPE_CONTAINER) { if (_drop_into_container(targetItem, item, itemIndex, itemSlot, count) == 0) { itemIndex = 0; } } else { if (_drop_ammo_into_weapon(targetItem, item, itemSlot, count, buttonCode) == INVENTORY_AMMO_MOVE_RESULT_SUCCESS) { itemIndex = 0; } } } } if (immediate || itemIndex == -1) { if (!scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_MAIN_BACKPACK, item, nullptr)) { goto inventory_move_done; } // TODO: Holy shit, needs refactoring. *itemSlot = nullptr; if (itemAdd(_inven_dude, item, 1)) { *itemSlot = item; } else if (itemSlot == &gInventoryArmor) { adjustCritterStatsOnArmorChange(_stack[0], item, nullptr); } else if (gInventoryRightHandItem == gInventoryLeftHandItem) { gInventoryLeftHandItem = nullptr; gInventoryRightHandItem = nullptr; } } } else if (!pickUpFromSlot && immediate && itemGetType(item) != ITEM_TYPE_ARMOR) { // ctrl-click non-armor to quick-equip: // default to first empty hand, or left hand if both are full bool left = gInventoryLeftHandItem == nullptr || gInventoryRightHandItem != nullptr; if (left) { _switch_hand(item, &gInventoryLeftHandItem, itemSlot, itemIndex); } else { _switch_hand(item, &gInventoryRightHandItem, itemSlot, itemIndex); } // drop in left hand slot } else if (mouseHitTestInWindow(gInventoryWindow, inventoryLayout.leftHandSlotX, INVENTORY_LEFT_HAND_SLOT_Y, inventoryLayout.leftHandSlotX + INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LEFT_HAND_SLOT_MAX_Y)) { if (gInventoryLeftHandItem != nullptr && itemGetType(gInventoryLeftHandItem) == ITEM_TYPE_CONTAINER && gInventoryLeftHandItem != item) { _drop_into_container(gInventoryLeftHandItem, item, itemIndex, itemSlot, count); } else if (gInventoryLeftHandItem == nullptr) { _switch_hand(item, &gInventoryLeftHandItem, itemSlot, itemIndex); } else if (_drop_ammo_into_weapon(gInventoryLeftHandItem, item, itemSlot, count, buttonCode) == INVENTORY_AMMO_MOVE_RESULT_FAILED) { _switch_hand(item, &gInventoryLeftHandItem, itemSlot, itemIndex); } // drop in right hand slot } else if (mouseHitTestInWindow(gInventoryWindow, inventoryLayout.rightHandSlotX, INVENTORY_RIGHT_HAND_SLOT_Y, inventoryLayout.rightHandSlotX + INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_RIGHT_HAND_SLOT_MAX_Y)) { if (gInventoryRightHandItem != nullptr && itemGetType(gInventoryRightHandItem) == ITEM_TYPE_CONTAINER && gInventoryRightHandItem != item) { _drop_into_container(gInventoryRightHandItem, item, itemIndex, itemSlot, count); } else if (gInventoryRightHandItem == nullptr) { _switch_hand(item, &gInventoryRightHandItem, itemSlot, itemIndex); } else if (_drop_ammo_into_weapon(gInventoryRightHandItem, item, itemSlot, count, buttonCode) == INVENTORY_AMMO_MOVE_RESULT_FAILED) { _switch_hand(item, &gInventoryRightHandItem, itemSlot, itemIndex); } } else if ((immediate && itemGetType(item) == ITEM_TYPE_ARMOR) || mouseHitTestInWindow(gInventoryWindow, inventoryLayout.armorSlotX, INVENTORY_ARMOR_SLOT_Y, inventoryLayout.armorSlotX + INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_ARMOR_SLOT_MAX_Y)) { if (itemGetType(item) == ITEM_TYPE_ARMOR) { Object* currentArmor = gInventoryArmor; if (!scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_ARMOR_SLOT, item, currentArmor)) { goto inventory_move_done; } int itemAddResult = 0; if (itemIndex != -1) { itemRemove(_inven_dude, item, 1); } if (gInventoryArmor != nullptr) { if (itemSlot != nullptr) { *itemSlot = gInventoryArmor; } else { gInventoryArmor = nullptr; itemAddResult = itemAdd(_inven_dude, currentArmor, 1); } } else { if (itemSlot != nullptr) { *itemSlot = gInventoryArmor; } } if (itemAddResult != 0) { gInventoryArmor = currentArmor; if (itemIndex != -1) { itemAdd(_inven_dude, item, 1); } } else { adjustCritterStatsOnArmorChange(_stack[0], currentArmor, item); gInventoryArmor = item; } } } else if (mouseHitTestInWindow(gInventoryWindow, inventoryLayout.bodyViewX, INVENTORY_PC_BODY_VIEW_Y, inventoryLayout.bodyViewX + INVENTORY_BODY_VIEW_WIDTH, INVENTORY_PC_BODY_VIEW_MAX_Y)) { if (_curr_stack == 0) { // Call the hook when dropping item on the PC portrait when not in a container. Return value is irrelevant. if (!scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_CHARACTER_PORTRAIT, item, nullptr)) { goto inventory_move_done; } } else { // If we are looking inside nested inventory (such as backpack item), we see this item in the PC Body View instead of the player. // So we drop item into it. _drop_into_container(_stack[_curr_stack - 1], item, itemIndex, itemSlot, count); } } inventory_move_done: _adjust_fid(); inventoryRenderSummary(); _display_inventory(indexOffset, -1, INVENTORY_WINDOW_TYPE_NORMAL); inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); if (_inven_dude == gDude) { Object* item; if (interfaceGetCurrentHand() == HAND_LEFT) { item = critterGetItem1(_inven_dude); } else { item = critterGetItem2(_inven_dude); } if (item != nullptr) { _inven_update_lighting(item); } } } // 0x4714E0 static void _switch_hand(Object* sourceItem, Object** targetSlot, Object** sourceSlot, int itemIndex) { if (*targetSlot != nullptr) { if (itemGetType(*targetSlot) == ITEM_TYPE_WEAPON && itemGetType(sourceItem) == ITEM_TYPE_AMMO) { return; } } HookInventoryMoveType targetSlotType = targetSlot == &gInventoryLeftHandItem ? HOOK_INVENTORYMOVE_LEFT_HAND : HOOK_INVENTORYMOVE_RIGHT_HAND; if (!scriptHooks_InventoryMove(targetSlotType, sourceItem, *targetSlot)) { return; } if (*targetSlot != nullptr) { if (sourceSlot != nullptr && (sourceSlot != &gInventoryArmor || itemGetType(*targetSlot) == ITEM_TYPE_ARMOR)) { if (sourceSlot == &gInventoryArmor) { adjustCritterStatsOnArmorChange(_stack[0], gInventoryArmor, *targetSlot); } *sourceSlot = *targetSlot; } else { if (itemIndex != -1) { itemRemove(_inven_dude, sourceItem, 1); } Object* existingItem = *targetSlot; *targetSlot = nullptr; if (itemAdd(_inven_dude, existingItem, 1) != 0) { itemAdd(_inven_dude, sourceItem, 1); return; } itemIndex = -1; if (sourceSlot != nullptr) { if (sourceSlot == &gInventoryArmor) { adjustCritterStatsOnArmorChange(_stack[0], gInventoryArmor, nullptr); } *sourceSlot = nullptr; } } } else { if (sourceSlot != nullptr) { if (sourceSlot == &gInventoryArmor) { adjustCritterStatsOnArmorChange(_stack[0], gInventoryArmor, nullptr); } *sourceSlot = nullptr; } } *targetSlot = sourceItem; if (itemIndex != -1) { itemRemove(_inven_dude, sourceItem, 1); } } // This function removes armor bonuses and effects granted by [oldArmor] and // adds appropriate bonuses and effects granted by [newArmor]. Both [oldArmor] // and [newArmor] can be NULL. // // 0x4715F8 void adjustCritterStatsOnArmorChange(Object* critter, Object* oldArmor, Object* newArmor) { int armorClassBonus = critterGetBonusStat(critter, STAT_ARMOR_CLASS); int oldArmorClass = armorGetArmorClass(oldArmor); int newArmorClass = armorGetArmorClass(newArmor); critterSetBonusStat(critter, STAT_ARMOR_CLASS, armorClassBonus - oldArmorClass + newArmorClass); int damageResistanceStat = STAT_DAMAGE_RESISTANCE; int damageThresholdStat = STAT_DAMAGE_THRESHOLD; for (int damageType = 0; damageType < DAMAGE_TYPE_COUNT; damageType += 1) { int damageResistanceBonus = critterGetBonusStat(critter, damageResistanceStat); int oldArmorDamageResistance = armorGetDamageResistance(oldArmor, damageType); int newArmorDamageResistance = armorGetDamageResistance(newArmor, damageType); critterSetBonusStat(critter, damageResistanceStat, damageResistanceBonus - oldArmorDamageResistance + newArmorDamageResistance); int damageThresholdBonus = critterGetBonusStat(critter, damageThresholdStat); int oldArmorDamageThreshold = armorGetDamageThreshold(oldArmor, damageType); int newArmorDamageThreshold = armorGetDamageThreshold(newArmor, damageType); critterSetBonusStat(critter, damageThresholdStat, damageThresholdBonus - oldArmorDamageThreshold + newArmorDamageThreshold); damageResistanceStat += 1; damageThresholdStat += 1; } if (objectIsPartyMember(critter)) { if (oldArmor != nullptr) { int perk = armorGetPerk(oldArmor); perkRemoveEffect(critter, perk); } if (newArmor != nullptr) { int perk = armorGetPerk(newArmor); perkAddEffect(critter, perk); } } } // 0x4716E8 static void _adjust_fid() { int fid = inventoryComputeCritterFid(_inven_dude, _inven_pid, gInventoryRightHandItem, gInventoryLeftHandItem, gInventoryArmor, interfaceGetCurrentHand(), 0, 0); gInventoryWindowDudeFid = scriptHooks_AdjustFid(fid, fid); } // 0x4717E4 void inventoryOpenUseItemOn(Object* targetObj) { ScopedGameMode gm(GameMode::kUseOn); if (inventoryCommonInit() == -1) { return; } bool isoWasEnabled = _setup_inventory(INVENTORY_WINDOW_TYPE_USE_ITEM_ON); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); for (;;) { sharedFpsLimiter.mark(); if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) { break; } _display_body(-1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); int keyCode = inputGetInput(); switch (keyCode) { case KEY_HOME: _stack_offset[_curr_stack] = 0; _display_inventory(0, -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); break; case KEY_ARROW_UP: if (_stack_offset[_curr_stack] > 0) { _stack_offset[_curr_stack] -= 1; _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); } break; case KEY_PAGE_UP: _stack_offset[_curr_stack] -= gInventorySlotsCount; if (_stack_offset[_curr_stack] < 0) { _stack_offset[_curr_stack] = 0; _display_inventory(_stack_offset[_curr_stack], -1, 1); } break; case KEY_END: _stack_offset[_curr_stack] = _pud->length - gInventorySlotsCount; if (_stack_offset[_curr_stack] < 0) { _stack_offset[_curr_stack] = 0; } _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); break; case KEY_ARROW_DOWN: if (_stack_offset[_curr_stack] + gInventorySlotsCount < _pud->length) { _stack_offset[_curr_stack] += 1; _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); } break; case KEY_PAGE_DOWN: _stack_offset[_curr_stack] += gInventorySlotsCount; if (_stack_offset[_curr_stack] + gInventorySlotsCount >= _pud->length) { _stack_offset[_curr_stack] = _pud->length - gInventorySlotsCount; if (_stack_offset[_curr_stack] < 0) { _stack_offset[_curr_stack] = 0; } } _display_inventory(_stack_offset[_curr_stack], -1, 1); break; case 2500: _container_exit(keyCode, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); break; default: if ((mouseGetEvent() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_HAND) { inventorySetCursor(INVENTORY_WINDOW_CURSOR_ARROW); } else { inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); } } else if ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { if (keyCode >= 1000 && keyCode < 1000 + gInventorySlotsCount) { if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); } else { int inventoryItemIndex = _pud->length - (_stack_offset[_curr_stack] + keyCode - 1000 + 1); // SFALL: Fix crash when clicking on empty space in the inventory list // opened by "Use Inventory Item On" (backpack) action icon if (inventoryItemIndex < _pud->length && inventoryItemIndex >= 0) { InventoryItem* inventoryItem = &(_pud->items[inventoryItemIndex]); if (isInCombat()) { if (gDude->data.critter.combat.ap >= 2) { if (_action_use_an_item_on_object(gDude, targetObj, inventoryItem->item) != -1) { int actionPoints = gDude->data.critter.combat.ap; if (actionPoints < 2) { gDude->data.critter.combat.ap = 0; } else { gDude->data.critter.combat.ap = actionPoints - 2; } interfaceRenderActionPoints(gDude->data.critter.combat.ap, _combat_free_move); } } } else { _action_use_an_item_on_object(gDude, targetObj, inventoryItem->item); } // fix for click through bug gBlockMouseUpEvent = true; keyCode = KEY_ESCAPE; } else { keyCode = -1; } } } } else if ((mouseGetEvent() & MOUSE_EVENT_WHEEL) != 0) { if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_SCROLLER_X, INVENTORY_SCROLLER_Y, INVENTORY_SCROLLER_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_SCROLLER_Y)) { int wheelX; int wheelY; mouseGetWheel(&wheelX, &wheelY); if (wheelY > 0) { if (_stack_offset[_curr_stack] > 0) { _stack_offset[_curr_stack] -= 1; _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); } } else if (wheelY < 0) { if (_stack_offset[_curr_stack] + gInventorySlotsCount < _pud->length) { _stack_offset[_curr_stack] += 1; _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); } } } } } if (keyCode == KEY_ESCAPE) { break; } renderPresent(); sharedFpsLimiter.throttle(); } _exit_inventory(isoWasEnabled); // NOTE: Uninline. inventoryCommonFree(); } // 0x471B70 Object* critterGetItem2(Object* critter) { int i; Inventory* inventory; Object* item; if (gInventoryRightHandItem != nullptr && critter == _inven_dude) { return gInventoryRightHandItem; } inventory = &(critter->data.inventory); for (i = 0; i < inventory->length; i++) { item = inventory->items[i].item; if (item->flags & OBJECT_IN_RIGHT_HAND) { return item; } } return nullptr; } // 0x471BBC Object* critterGetItem1(Object* critter) { int i; Inventory* inventory; Object* item; if (gInventoryLeftHandItem != nullptr && critter == _inven_dude) { return gInventoryLeftHandItem; } inventory = &(critter->data.inventory); for (i = 0; i < inventory->length; i++) { item = inventory->items[i].item; if (item->flags & OBJECT_IN_LEFT_HAND) { return item; } } return nullptr; } // 0x471C08 Object* critterGetArmor(Object* critter) { int i; Inventory* inventory; Object* item; if (gInventoryArmor != nullptr && critter == _inven_dude) { return gInventoryArmor; } inventory = &(critter->data.inventory); for (i = 0; i < inventory->length; i++) { item = inventory->items[i].item; if (item->flags & OBJECT_WORN) { return item; } } return nullptr; } // Critter inventories are displayed without equipped items in almost all cases. This is accomplished by // temporarily removing them when inventory is viewed. CritterEquipped critterStripEquipped(Object* critter) { CritterEquipped equipped; Inventory* inv = &critter->data.inventory; for (int i = 0; i < inv->length; i++) { Object* item = inv->items[i].item; if ((item->flags & OBJECT_IN_LEFT_HAND) != 0) { if ((item->flags & OBJECT_IN_RIGHT_HAND) != 0) { equipped.rightHand = item; } equipped.leftHand = item; } else if ((item->flags & OBJECT_IN_RIGHT_HAND) != 0) { equipped.rightHand = item; } else if ((item->flags & OBJECT_WORN) != 0) { equipped.armor = item; } } if (equipped.leftHand != nullptr) { equipped.weight += itemGetWeight(equipped.leftHand); itemRemove(critter, equipped.leftHand, 1); } if (equipped.rightHand != nullptr && equipped.rightHand != equipped.leftHand) { equipped.weight += itemGetWeight(equipped.rightHand); itemRemove(critter, equipped.rightHand, 1); } if (equipped.armor != nullptr) { equipped.weight += itemGetWeight(equipped.armor); itemRemove(critter, equipped.armor, 1); } return equipped; } void critterRestoreEquipped(Object* critter, CritterEquipped& equipped) { if (equipped.leftHand != nullptr) { equipped.leftHand->flags |= OBJECT_IN_LEFT_HAND; if (equipped.leftHand == equipped.rightHand) equipped.leftHand->flags |= OBJECT_IN_RIGHT_HAND; itemAdd(critter, equipped.leftHand, 1); } if (equipped.rightHand != nullptr && equipped.rightHand != equipped.leftHand) { equipped.rightHand->flags |= OBJECT_IN_RIGHT_HAND; itemAdd(critter, equipped.rightHand, 1); } if (equipped.armor != nullptr) { equipped.armor->flags |= OBJECT_WORN; itemAdd(critter, equipped.armor, 1); } equipped = {}; } static void inventoryStripEquippedToGlobals(Object* critter) { CritterEquipped equipped = critterStripEquipped(critter); gInventoryLeftHandItem = equipped.leftHand; gInventoryRightHandItem = equipped.rightHand; gInventoryArmor = equipped.armor; } static void inventoryRestoreEquippedFromGlobals(Object* critter) { CritterEquipped equipped { gInventoryLeftHandItem, gInventoryRightHandItem, gInventoryArmor }; critterRestoreEquipped(critter, equipped); gInventoryLeftHandItem = nullptr; gInventoryRightHandItem = nullptr; gInventoryArmor = nullptr; } static void inventorySetLeftPaneCritter(Object* critter, Object* target, int inventoryWindowType) { assert(critter != nullptr); assert(target != nullptr); // _inven_dude can point at a nested container while browsing bags, but // equipped items always need to be restored onto the owning critter. inventoryRestoreEquippedFromGlobals(_stack[0]); inventoryStripEquippedToGlobals(critter); _inven_dude = critter; _curr_stack = 0; _pud = &(critter->data.inventory); _stack[0] = critter; _stack_offset[0] = 0; int animationCode = 0; Object* itemInHand = interfaceGetCurrentHand() == HAND_RIGHT ? gInventoryRightHandItem : gInventoryLeftHandItem; if (itemInHand != nullptr) { Proto* proto = nullptr; if (protoGetProto(itemInHand->pid, &proto) != -1 && proto != nullptr && proto->item.type == ITEM_TYPE_WEAPON) { animationCode = proto->item.data.weapon.animationCode; } } gInventoryWindowDudeFid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, 0, animationCode, 0); gInventoryWindowDudeRotationTimestamp = 0; _display_inventory(0, -1, inventoryWindowType); _display_body(target->fid, inventoryWindowType); } // 0x471CA0 Object* objectGetCarriedObjectByPid(Object* obj, int pid) { Inventory* inventory = &(obj->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); if (inventoryItem->item->pid == pid) { return inventoryItem->item; } Object* found = objectGetCarriedObjectByPid(inventoryItem->item, pid); if (found != nullptr) { return found; } } return nullptr; } // 0x471CDC int objectGetCarriedQuantityByPid(Object* object, int pid) { int quantity = 0; Inventory* inventory = &(object->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); if (inventoryItem->item->pid == pid) { quantity += inventoryItem->quantity; } quantity += objectGetCarriedQuantityByPid(inventoryItem->item, pid); } return quantity; } // Renders character's summary of SPECIAL stats, equipped armor bonuses, // and weapon's damage/range. // // 0x471D5C static void inventoryRenderSummary() { int summaryStats[7]; memcpy(summaryStats, gSummaryStats, sizeof(summaryStats)); int summaryStats2[7]; memcpy(summaryStats2, gSummaryStats2, sizeof(summaryStats2)); char formattedText[80]; int oldFont = fontGetCurrent(); fontSetCurrent(101); unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); int pitch = inventoryLayout.windowWidth; int summaryX = inventoryLayout.summaryX; int summaryMaxX = summaryX + (INVENTORY_SUMMARY_MAX_X - INVENTORY_SUMMARY_X); unsigned char* backgroundData = inventoryFrmImage.getData(); int backgroundWidth = inventoryFrmImage.getWidth(); if (backgroundData != nullptr) { blitBufferToBuffer(backgroundData + backgroundWidth * INVENTORY_SUMMARY_Y + summaryX, 152, 188, backgroundWidth, windowBuffer + pitch * INVENTORY_SUMMARY_Y + summaryX, pitch); } // Render character name. const char* critterName = critterGetName(_stack[0]); fontDrawText(windowBuffer + pitch * INVENTORY_SUMMARY_Y + summaryX, critterName, 80, pitch, _colorTable[992]); bufferDrawLine(windowBuffer, pitch, summaryX, 3 * fontGetLineHeight() / 2 + INVENTORY_SUMMARY_Y, summaryMaxX, 3 * fontGetLineHeight() / 2 + INVENTORY_SUMMARY_Y, _colorTable[992]); MessageListItem messageListItem; int offset = pitch * 2 * fontGetLineHeight() + pitch * INVENTORY_SUMMARY_Y + summaryX; for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) { messageListItem.num = stat; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { fontDrawText(windowBuffer + offset, messageListItem.text, 80, pitch, _colorTable[992]); } int value = critterGetStat(_stack[0], stat); snprintf(formattedText, sizeof(formattedText), "%d", value); fontDrawText(windowBuffer + offset + 24, formattedText, 80, pitch, _colorTable[992]); offset += pitch * fontGetLineHeight(); } offset -= pitch * 7 * fontGetLineHeight(); for (int index = 0; index < 7; index += 1) { messageListItem.num = 7 + index; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { fontDrawText(windowBuffer + offset + 40, messageListItem.text, 80, pitch, _colorTable[992]); } if (summaryStats2[index] == -1) { int value = critterGetStat(_stack[0], summaryStats[index]); snprintf(formattedText, sizeof(formattedText), " %d", value); } else { int value1 = critterGetStat(_stack[0], summaryStats[index]); int value2 = critterGetStat(_stack[0], summaryStats2[index]); const char* format = index != 0 ? "%d/%d%%" : "%d/%d"; snprintf(formattedText, sizeof(formattedText), format, value1, value2); } fontDrawText(windowBuffer + offset + 104, formattedText, 80, pitch, _colorTable[992]); offset += pitch * fontGetLineHeight(); } bufferDrawLine(windowBuffer, pitch, summaryX, 18 * fontGetLineHeight() / 2 + 48, summaryMaxX, 18 * fontGetLineHeight() / 2 + 48, _colorTable[992]); bufferDrawLine(windowBuffer, pitch, summaryX, 26 * fontGetLineHeight() / 2 + 48, summaryMaxX, 26 * fontGetLineHeight() / 2 + 48, _colorTable[992]); Object* itemsInHands[2] = { gInventoryLeftHandItem, gInventoryRightHandItem, }; const int hitModes[2] = { HIT_MODE_LEFT_WEAPON_PRIMARY, HIT_MODE_RIGHT_WEAPON_PRIMARY, }; const int secondaryHitModes[2] = { HIT_MODE_LEFT_WEAPON_SECONDARY, HIT_MODE_RIGHT_WEAPON_SECONDARY, }; const int unarmedHitModes[2] = { HIT_MODE_PUNCH, HIT_MODE_KICK, }; offset += pitch * fontGetLineHeight(); for (int index = 0; index < 2; index += 1) { Object* item = itemsInHands[index]; if (item == nullptr) { formattedText[0] = '\0'; // No item messageListItem.num = 14; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { fontDrawText(windowBuffer + offset, messageListItem.text, 120, pitch, _colorTable[992]); } offset += pitch * fontGetLineHeight(); // Unarmed dmg: messageListItem.num = 24; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { // SFALL: Display the actual damage values of unarmed attacks. // CE: Implementation is different. int hitMode = unarmedHitModes[index]; if (_stack[0] == gDude) { int actions[2]; interfaceGetItemActions(&(actions[0]), &(actions[1])); bool isSecondary = actions[index] == INTERFACE_ITEM_ACTION_SECONDARY || actions[index] == INTERFACE_ITEM_ACTION_SECONDARY_AIMING; if (index == HAND_LEFT) { hitMode = unarmedGetPunchHitMode(isSecondary); } else { hitMode = unarmedGetKickHitMode(isSecondary); } } // Formula is the same as in `weaponGetDamage`. int minDamage; int maxDamage; int bonusDamage = unarmedGetDamage(hitMode, &minDamage, &maxDamage); int meleeDamage = critterGetStat(_stack[0], STAT_MELEE_DAMAGE); // TODO: Localize unarmed attack names. snprintf(formattedText, sizeof(formattedText), "%s %d-%d", messageListItem.text, bonusDamage + minDamage, bonusDamage + meleeDamage + maxDamage); } fontDrawText(windowBuffer + offset, formattedText, 120, pitch, _colorTable[992]); offset += 3 * pitch * fontGetLineHeight(); continue; } const char* itemName = itemGetName(item); fontDrawText(windowBuffer + offset, itemName, 140, pitch, _colorTable[992]); offset += pitch * fontGetLineHeight(); int itemType = itemGetType(item); if (itemType != ITEM_TYPE_WEAPON) { if (itemType == ITEM_TYPE_ARMOR) { // (Not worn) messageListItem.num = 18; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { fontDrawText(windowBuffer + offset, messageListItem.text, 120, pitch, _colorTable[992]); } } offset += 3 * pitch * fontGetLineHeight(); continue; } // SFALL: Fix displaying secondary mode weapon range. int hitMode = hitModes[index]; if (_stack[0] == gDude) { int actions[2]; interfaceGetItemActions(&(actions[0]), &(actions[1])); bool isSecondary = actions[index] == INTERFACE_ITEM_ACTION_SECONDARY || actions[index] == INTERFACE_ITEM_ACTION_SECONDARY_AIMING; if (isSecondary) { hitMode = secondaryHitModes[index]; } } int range = weaponGetRange(_stack[0], hitMode); int damageMin; int damageMax; weaponGetDamageMinMax(item, &damageMin, &damageMax); // CE: Fix displaying secondary mode weapon damage (affects throwable // melee weapons - knifes, spears, etc.). int attackType = weaponGetAttackTypeForHitMode(item, hitMode); formattedText[0] = '\0'; int meleeDamage; if (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) { meleeDamage = critterGetStat(_stack[0], STAT_MELEE_DAMAGE); // SFALL: Display melee damage without "Bonus HtH Damage" bonus. if (damageModGetBonusHthDamageFix() && !damageModGetDisplayBonusDamage()) { meleeDamage -= 2 * perkGetRank(gDude, PERK_BONUS_HTH_DAMAGE); } } else { meleeDamage = 0; } messageListItem.num = 15; // Dmg: if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { if (attackType != 4 && range <= 1) { // SFALL: Display bonus damage. if (damageModGetBonusHthDamageFix() && damageModGetDisplayBonusDamage()) { // CE: Just in case check for attack type, however it looks // like we cannot be here with anything besides melee or // unarmed. if (_stack[0] == gDude && (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED)) { // See explanation in `weaponGetDamage`. damageMin += 2 * perkGetRank(gDude, PERK_BONUS_HTH_DAMAGE); } } snprintf(formattedText, sizeof(formattedText), "%s %d-%d", messageListItem.text, damageMin, damageMax + meleeDamage); } else { MessageListItem rangeMessageListItem; rangeMessageListItem.num = 16; // Rng: if (messageListGetItem(&gInventoryMessageList, &rangeMessageListItem)) { // SFALL: Display bonus damage. if (damageModGetDisplayBonusDamage()) { // CE: There is a bug in Sfall diplaying wrong damage // bonus for melee weapons with range > 1 (spears, // sledgehammers) and throwables (secondary mode). if (_stack[0] == gDude && attackType == ATTACK_TYPE_RANGED) { int damageBonus = 2 * perkGetRank(gDude, PERK_BONUS_RANGED_DAMAGE); damageMin += damageBonus; damageMax += damageBonus; } } snprintf(formattedText, sizeof(formattedText), "%s %d-%d %s %d", messageListItem.text, damageMin, damageMax + meleeDamage, rangeMessageListItem.text, range); } } fontDrawText(windowBuffer + offset, formattedText, 140, pitch, _colorTable[992]); } offset += pitch * fontGetLineHeight(); if (ammoGetCapacity(item) > 0) { int ammoTypePid = weaponGetAmmoTypePid(item); formattedText[0] = '\0'; messageListItem.num = 17; // Ammo: if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { if (ammoTypePid != -1) { if (ammoGetQuantity(item) != 0) { const char* ammoName = protoGetName(ammoTypePid); int capacity = ammoGetCapacity(item); int quantity = ammoGetQuantity(item); snprintf(formattedText, sizeof(formattedText), "%s %d/%d %s", messageListItem.text, quantity, capacity, ammoName); } else { int capacity = ammoGetCapacity(item); int quantity = ammoGetQuantity(item); snprintf(formattedText, sizeof(formattedText), "%s %d/%d", messageListItem.text, quantity, capacity); } } } else { int capacity = ammoGetCapacity(item); int quantity = ammoGetQuantity(item); snprintf(formattedText, sizeof(formattedText), "%s %d/%d", messageListItem.text, quantity, capacity); } fontDrawText(windowBuffer + offset, formattedText, 140, pitch, _colorTable[992]); } offset += 2 * pitch * fontGetLineHeight(); } // Total wt: messageListItem.num = 20; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { if (PID_TYPE(_stack[0]->pid) == OBJ_TYPE_CRITTER) { int carryWeight = critterGetStat(_stack[0], STAT_CARRY_WEIGHT); int inventoryWeight = objectGetInventoryWeight(_stack[0]); snprintf(formattedText, sizeof(formattedText), "%s %d/%d", messageListItem.text, inventoryWeight, carryWeight); int color = _colorTable[992]; if (critterIsEncumbered(_stack[0])) { color = _colorTable[31744]; } fontDrawText(windowBuffer + offset + 15, formattedText, 120, pitch, color); } else { int inventoryWeight = objectGetInventoryWeight(_stack[0]); snprintf(formattedText, sizeof(formattedText), "%s %d", messageListItem.text, inventoryWeight); fontDrawText(windowBuffer + offset + 30, formattedText, 80, pitch, _colorTable[992]); } } fontSetCurrent(oldFont); } // Finds next item of given [itemType] (can be -1 which means any type of // item). // // The [index] is used to control where to continue the search from, -1 - from // the beginning. // // 0x472698 Object* inventoryFindByType(Object* obj, int itemType, int* indexPtr) { int dummy = -1; if (indexPtr == nullptr) { indexPtr = &dummy; } *indexPtr += 1; Inventory* inventory = &(obj->data.inventory); // TODO: Refactor with for loop. if (*indexPtr >= inventory->length) { return nullptr; } while (itemType != -1 && itemGetType(inventory->items[*indexPtr].item) != itemType) { *indexPtr += 1; if (*indexPtr >= inventory->length) { return nullptr; } } return inventory->items[*indexPtr].item; } // Searches for an item with a given id inside given obj's inventory. // // 0x4726EC Object* inventoryFindById(Object* obj, int id) { if (obj->id == id) { return obj; } Inventory* inventory = &(obj->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); Object* item = inventoryItem->item; if (item->id == id) { return item; } if (itemGetType(item) == ITEM_TYPE_CONTAINER) { item = inventoryFindById(item, id); if (item != nullptr) { return item; } } } return nullptr; } // Returns inventory item at a given index. // // 0x472740 Object* inventoryItemByIndex(Object* obj, int index) { Inventory* inventory; inventory = &(obj->data.inventory); if (index < 0 || index >= inventory->length) { return nullptr; } return inventory->items[index].item; } // inven_wield // 0x472758 int inventoryEquip(Object* critter, Object* item, int hand) { return inventoryEquipFunc(critter, item, hand, true); } // 0x472768 int inventoryEquipFunc(Object* critter, Object* item, int handIndex, bool animate) { int itemType = itemGetType(item); InvenSlot invenSlot = itemType == ITEM_TYPE_ARMOR ? InvenSlot::Armor : (handIndex == HAND_RIGHT ? InvenSlot::RightHand : InvenSlot::LeftHand); if (!scriptHooks_InvenWield(critter, item, invenSlot, 1, 0)) { return -1; } if (animate) { if (!isoIsDisabled()) { reg_anim_begin(ANIMATION_REQUEST_RESERVED); } } if (itemType == ITEM_TYPE_ARMOR) { Object* armor = critterGetArmor(critter); if (armor != nullptr) { armor->flags &= ~OBJECT_WORN; } item->flags |= OBJECT_WORN; int baseFrmId; if (critterGetStat(critter, STAT_GENDER) == GENDER_FEMALE) { baseFrmId = armorGetFemaleFid(item); } else { baseFrmId = armorGetMaleFid(item); } if (baseFrmId == -1) { baseFrmId = 1; } if (critter == gDude) { if (!isoIsDisabled()) { int fid = buildFid(OBJ_TYPE_CRITTER, baseFrmId, 0, (critter->fid & 0xF000) >> 12, critter->rotation + 1); animationRegisterSetFid(critter, fid, 0); } } else { adjustCritterStatsOnArmorChange(critter, armor, item); } } else { int hand; if (critter == gDude) { hand = interfaceGetCurrentHand(); } else { hand = HAND_RIGHT; } int weaponAnimationCode = weaponGetAnimationCode(item); int hitModeAnimationCode = weaponGetAnimationForHitMode(item, HIT_MODE_RIGHT_WEAPON_PRIMARY); int fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, hitModeAnimationCode, weaponAnimationCode, critter->rotation + 1); if (!artExists(fid)) { debugPrint("\ninven_wield failed! ERROR ERROR ERROR!"); return -1; } Object* equippedItem; if (handIndex == HAND_RIGHT) { equippedItem = critterGetItem2(critter); item->flags |= OBJECT_IN_RIGHT_HAND; } else { equippedItem = critterGetItem1(critter); item->flags |= OBJECT_IN_LEFT_HAND; } Rect rect; if (equippedItem != nullptr) { equippedItem->flags &= ~OBJECT_IN_ANY_HAND; if (equippedItem->pid == PROTO_ID_LIT_FLARE) { int lightIntensity; int lightDistance; if (critter == gDude) { lightIntensity = LIGHT_INTENSITY_MAX; lightDistance = 4; } else { Proto* proto; if (protoGetProto(critter->pid, &proto) == -1) { return -1; } lightDistance = proto->lightDistance; lightIntensity = proto->lightIntensity; } objectSetLight(critter, lightDistance, lightIntensity, &rect); } } if (item->pid == PROTO_ID_LIT_FLARE) { int lightDistance = item->lightDistance; if (lightDistance < critter->lightDistance) { lightDistance = critter->lightDistance; } int lightIntensity = item->lightIntensity; if (lightIntensity < critter->lightIntensity) { lightIntensity = critter->lightIntensity; } objectSetLight(critter, lightDistance, lightIntensity, &rect); tileWindowRefreshRect(&rect, gElevation); } if (itemGetType(item) == ITEM_TYPE_WEAPON) { weaponAnimationCode = weaponGetAnimationCode(item); } else { weaponAnimationCode = 0; } if (hand == handIndex) { if ((critter->fid & 0xF000) >> 12 != 0) { if (animate) { if (!isoIsDisabled()) { const char* soundEffectName = sfxBuildCharName(critter, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED); animationRegisterPlaySoundEffect(critter, soundEffectName, 0); animationRegisterAnimate(critter, ANIM_PUT_AWAY, 0); } } } if (animate && !isoIsDisabled()) { if (weaponAnimationCode != 0) { animationRegisterTakeOutWeapon(critter, weaponAnimationCode, -1); } else { int fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, 0, 0, critter->rotation + 1); animationRegisterSetFid(critter, fid, -1); } } else { int fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, 0, weaponAnimationCode, critter->rotation + 1); _dude_stand(critter, critter->rotation, fid); } } } if (animate) { if (!isoIsDisabled()) { return reg_anim_end(); } } return 0; } // inven_unwield // 0x472A54 int inventoryUnequip(Object* critter_obj, int hand) { return inventoryUnequipFunc(critter_obj, hand, true); } // 0x472A64 int inventoryUnequipFunc(Object* critter, int hand, bool animate) { int activeHand; Object* item; if (critter == gDude) { activeHand = interfaceGetCurrentHand(); } else { activeHand = HAND_RIGHT; // NPC's only ever use right slot } if (hand) { item = critterGetItem2(critter); } else { item = critterGetItem1(critter); } // Notify scripts before mutating the OBJECT_IN_ANY_HAND flag.. if (item != nullptr) { InvenSlot invenSlot = hand == HAND_RIGHT ? InvenSlot::RightHand : InvenSlot::LeftHand; if (!scriptHooks_InvenWield(critter, item, invenSlot, 0, 0)) { return -1; } } if (item) { item->flags &= ~OBJECT_IN_ANY_HAND; } if (activeHand == hand && ((critter->fid & 0xF000) >> 12) != 0) { if (animate && !isoIsDisabled()) { reg_anim_begin(ANIMATION_REQUEST_RESERVED); const char* sfx = sfxBuildCharName(critter, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED); animationRegisterPlaySoundEffect(critter, sfx, 0); animationRegisterAnimate(critter, ANIM_PUT_AWAY, 0); int fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, 0, 0, critter->rotation + 1); animationRegisterSetFid(critter, fid, -1); return reg_anim_end(); } int fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, 0, 0, critter->rotation + 1); _dude_stand(critter, critter->rotation, fid); } return 0; } // 0x472B54 static int _inven_from_button(int keyCode, Object** outItem, Object*** outItemSlot, Object** outOwner) { Object** itemSlot; Object* owner; Object* item; int quantity = 0; switch (keyCode) { case INVENTORY_HAND_RIGHT_KEY: itemSlot = &gInventoryRightHandItem; owner = _stack[0]; item = gInventoryRightHandItem; break; case INVENTORY_HAND_LEFT_KEY: itemSlot = &gInventoryLeftHandItem; owner = _stack[0]; item = gInventoryLeftHandItem; break; case INVENTORY_ARMOR_KEY: itemSlot = &gInventoryArmor; owner = _stack[0]; item = gInventoryArmor; break; default: itemSlot = nullptr; owner = nullptr; item = nullptr; InventoryItem* inventoryItem; if (keyCode < 2000) { int index = _stack_offset[_curr_stack] + keyCode - 1000; if (index >= _pud->length) { break; } inventoryItem = &(_pud->items[_pud->length - (index + 1)]); item = inventoryItem->item; owner = _stack[_curr_stack]; } else if (keyCode < 2300) { int index = _target_stack_offset[_target_curr_stack] + keyCode - 2000; if (index >= _target_pud->length) { break; } inventoryItem = &(_target_pud->items[_target_pud->length - (index + 1)]); item = inventoryItem->item; owner = _target_stack[_target_curr_stack]; } else if (keyCode < 2400) { int index = gPlayerTableOffset + keyCode - 2300; if (index >= gPlayerTableInventory->length) { break; } inventoryItem = &(gPlayerTableInventory->items[gPlayerTableInventory->length - (index + 1)]); item = inventoryItem->item; owner = gPlayerTableObj; } else { int index = gBartererTableOffset + keyCode - 2400; if (index >= gBartererTableInventory->length) { break; } inventoryItem = &(gBartererTableInventory->items[gBartererTableInventory->length - (index + 1)]); item = inventoryItem->item; owner = gBartererTableObj; } quantity = inventoryItem->quantity; } if (outItemSlot != nullptr) { *outItemSlot = itemSlot; } if (outItem != nullptr) { *outItem = item; } if (outOwner != nullptr) { *outOwner = owner; } if (quantity == 0 && item != nullptr) { quantity = 1; } return quantity; } static void inventoryDrawCenteredText(unsigned char* buffer, int pitch, int width, int x, int y, const char* text, int color) { if (text == nullptr) { return; } int oldFont = fontGetCurrent(); fontSetCurrent(101); int textWidth = std::min(fontGetStringWidth(text), width); int textX = x + width / 2 - textWidth / 2; fontDrawText(buffer + pitch * y + textX, text, width, pitch, color); fontSetCurrent(oldFont); } static void inventoryDisplayLeftPaneCompanionName(unsigned char* windowBuffer, int windowPitch, const Rect& rect, int index) { if (index != 0 || _stack[0] == gDude) { return; } const char* name = critterGetName(_stack[0]); if (name == nullptr) { return; } int oldFont = fontGetCurrent(); fontSetCurrent(101); int nameY = rect.bottom - fontGetLineHeight() - 2; fontSetCurrent(oldFont); inventoryDrawCenteredText(windowBuffer, windowPitch, INVENTORY_BODY_VIEW_WIDTH, rect.left, nameY, name, _colorTable[992]); } // Displays item description. // // inven_display_msg // 0x472D24 static void inventoryRenderItemDescription(const char* string) { int oldFont = fontGetCurrent(); fontSetCurrent(101); if (string == nullptr) { fontSetCurrent(oldFont); return; } unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); int pitch = inventoryLayout.windowWidth; windowBuffer += pitch * INVENTORY_SUMMARY_Y + inventoryLayout.summaryX; std::string mutableString(string); char* c = mutableString.data(); while (c != nullptr && *c != '\0') { _inven_display_msg_line += 1; if (_inven_display_msg_line > 17) { debugPrint("\nError: inven_display_msg: out of bounds!"); goto end; } char* space = nullptr; if (fontGetStringWidth(c) > 152) { // Look for next space. space = c + 1; while (*space != '\0' && *space != ' ') { space += 1; } if (*space == '\0') { // This was the last line containing very long word. Text // drawing routine will silently truncate it after reaching // desired length. fontDrawText(windowBuffer + pitch * _inven_display_msg_line * fontGetLineHeight(), c, 152, pitch, _colorTable[992]); goto end; } char* nextSpace = space + 1; while (true) { while (*nextSpace != '\0' && *nextSpace != ' ') { nextSpace += 1; } if (*nextSpace == '\0') { break; } // Break string and measure it. *nextSpace = '\0'; if (fontGetStringWidth(c) >= 152) { // Next space is too far to fit in one line. Restore next // space's character and stop. *nextSpace = ' '; break; } space = nextSpace; // Restore next space's character and continue looping from the // next character. *nextSpace = ' '; nextSpace += 1; } if (*space == ' ') { *space = '\0'; } } if (fontGetStringWidth(c) > 152) { debugPrint("\nError: inven_display_msg: word too long!"); goto end; } fontDrawText(windowBuffer + pitch * _inven_display_msg_line * fontGetLineHeight(), c, 152, pitch, _colorTable[992]); if (space != nullptr) { c = space + 1; if (*space == '\0') { *space = ' '; } } else { c = nullptr; } } end: fontSetCurrent(oldFont); } // Examines inventory item. // // 0x472EB8 static void inventoryExamineItem(Object* critter, Object* item) { int oldFont = fontGetCurrent(); fontSetCurrent(101); unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); int pitch = inventoryLayout.windowWidth; int summaryX = inventoryLayout.summaryX; int summaryMaxX = summaryX + (INVENTORY_SUMMARY_MAX_X - INVENTORY_SUMMARY_X); // Clear item description area. unsigned char* backgroundData = inventoryFrmImage.getData(); int backgroundWidth = inventoryFrmImage.getWidth(); if (backgroundData != nullptr) { blitBufferToBuffer(backgroundData + backgroundWidth * INVENTORY_SUMMARY_Y + summaryX, 152, 188, backgroundWidth, windowBuffer + pitch * INVENTORY_SUMMARY_Y + summaryX, pitch); } // Reset item description lines counter. _inven_display_msg_line = 0; // Render item's name. char* itemName = objectGetName(item); inventoryRenderItemDescription(itemName); // Increment line counter to accomodate separator below. _inven_display_msg_line += 1; int lineHeight = fontGetLineHeight(); // Draw separator. // SFALL: Fix separator position when item name is longer than one line. bufferDrawLine(windowBuffer, pitch, summaryX, (_inven_display_msg_line - 1) * lineHeight + lineHeight / 2 + 49, summaryMaxX, (_inven_display_msg_line - 1) * lineHeight + lineHeight / 2 + 49, _colorTable[992]); // Examine item. objectExamineFunc(critter, item, inventoryRenderItemDescription); // Add weight if neccessary. int weight = itemGetWeight(item); if (weight != 0) { MessageListItem messageListItem; messageListItem.num = 540; if (weight == 1) { messageListItem.num = 541; } if (!messageListGetItem(&gProtoMessageList, &messageListItem)) { debugPrint("\nError: Couldn't find message!"); } char formattedText[40]; snprintf(formattedText, sizeof(formattedText), messageListItem.text, weight); inventoryRenderItemDescription(formattedText); } fontSetCurrent(oldFont); } // 0x47304C static void inventoryWindowOpenContextMenu(int keyCode, int inventoryWindowType) { Object* item; Object** itemSlot; Object* owner; int quantity = _inven_from_button(keyCode, &item, &itemSlot, &owner); if (quantity == 0) { return; } int itemType = itemGetType(item); int mouseState; do { sharedFpsLimiter.mark(); inputGetInput(); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { _display_body(-1, INVENTORY_WINDOW_TYPE_NORMAL); } mouseState = mouseGetEvent(); if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) { if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL) { objectLookAtFunc(_stack[0], item, gInventoryPrintItemDescriptionHandler); } else { inventoryExamineItem(_stack[0], item); } windowRefresh(gInventoryWindow); return; } renderPresent(); sharedFpsLimiter.throttle(); } while ((mouseState & MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT) != MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT); inventorySetCursor(INVENTORY_WINDOW_CURSOR_BLANK); unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); int x; int y; mouseGetPosition(&x, &y); int actionMenuItemsLength; const int* actionMenuItems; if (itemType == ITEM_TYPE_WEAPON && weaponCanBeUnloaded(item)) { if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL && objectGetOwner(item) != gDude) { actionMenuItemsLength = 3; actionMenuItems = _act_weap2; } else { actionMenuItemsLength = 4; actionMenuItems = _act_weap; } } else { if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL) { // SFALL: Fix crash when trying to open bag/backpack on the table // in the bartering interface. Object* owner = objectGetOwner(item); if (owner != gDude) { if (itemType == ITEM_TYPE_CONTAINER && (owner == _stack[_curr_stack] || owner == _target_stack[_target_curr_stack])) { actionMenuItemsLength = 3; actionMenuItems = _act_just_use; } else { actionMenuItemsLength = 2; actionMenuItems = _act_nothing; } } else { if (itemType == ITEM_TYPE_CONTAINER) { actionMenuItemsLength = 4; actionMenuItems = _act_use; } else { actionMenuItemsLength = 3; actionMenuItems = _act_no_use; } } } else { if (itemType == ITEM_TYPE_CONTAINER && itemSlot != nullptr) { actionMenuItemsLength = 3; actionMenuItems = _act_no_use; } else { if (_obj_action_can_use(item) || _proto_action_can_use_on(item->pid)) { actionMenuItemsLength = 4; actionMenuItems = _act_use; } else { actionMenuItemsLength = 3; actionMenuItems = _act_no_use; } } } } const InventoryWindowDescription* windowDescription = &(gInventoryWindowDescriptions[inventoryWindowType]); int windowWidth = inventoryGetWindowWidth(inventoryWindowType); int windowHeight = inventoryGetWindowHeight(inventoryWindowType); Rect windowRect; windowGetRect(gInventoryWindow, &windowRect); int inventoryWindowX = windowRect.left; int inventoryWindowY = windowRect.top; gameMouseRenderActionMenuItems(x, y, actionMenuItems, actionMenuItemsLength, windowWidth + inventoryWindowX, windowHeight + inventoryWindowY); InventoryCursorData* cursorData = &(gInventoryCursorData[INVENTORY_WINDOW_CURSOR_MENU]); int offsetX; int offsetY; artGetRotationOffsets(cursorData->frm, 0, &offsetX, &offsetY); Rect rect; rect.left = x - inventoryWindowX - cursorData->width / 2 + offsetX; rect.top = y - inventoryWindowY - cursorData->height + 1 + offsetY; rect.right = rect.left + cursorData->width - 1; rect.bottom = rect.top + cursorData->height - 1; int menuButtonHeight = cursorData->height; if (rect.top + menuButtonHeight > windowHeight) { menuButtonHeight = windowHeight - rect.top; } int btn = buttonCreate(gInventoryWindow, rect.left, rect.top, cursorData->width, menuButtonHeight, -1, -1, -1, -1, cursorData->frmData, cursorData->frmData, nullptr, BUTTON_FLAG_TRANSPARENT); windowRefreshRect(gInventoryWindow, &rect); int menuItemIndex = 0; int previousMouseY = y; while ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_UP) == 0) { sharedFpsLimiter.mark(); inputGetInput(); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { _display_body(-1, INVENTORY_WINDOW_TYPE_NORMAL); } int x; int y; mouseGetPosition(&x, &y); if (y - previousMouseY > 10 || previousMouseY - y > 10) { if (y >= previousMouseY || menuItemIndex <= 0) { if (previousMouseY < y && menuItemIndex < actionMenuItemsLength - 1) { menuItemIndex++; } } else { menuItemIndex--; } gameMouseHighlightActionMenuItemAtIndex(menuItemIndex); windowRefreshRect(gInventoryWindow, &rect); previousMouseY = y; } renderPresent(); sharedFpsLimiter.throttle(); } buttonDestroy(btn); Buffer2D dst { windowBuffer, windowWidth, windowHeight }; FrmImage backgroundFrmImage; int sourceXOffset; ConstBuffer2D background = inventoryGetBackgroundBuffer(inventoryWindowType, windowDescription->frmId, backgroundFrmImage, sourceXOffset); if (background) { blitBuffer2D(background, rect.left + sourceXOffset, rect.top, cursorData->width, menuButtonHeight, dst, rect.left, rect.top); } _mouse_set_position(x, y); _display_inventory(_stack_offset[_curr_stack], -1, inventoryWindowType); int actionMenuItem = actionMenuItems[menuItemIndex]; switch (actionMenuItem) { case GAME_MOUSE_ACTION_MENU_ITEM_DROP: { bool inventoryMoveAlreadyChecked = false; if (itemSlot != nullptr) { if (!scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item, nullptr)) { break; } inventoryMoveAlreadyChecked = true; if (itemSlot == &gInventoryArmor) { adjustCritterStatsOnArmorChange(_stack[0], item, nullptr); } itemAdd(owner, item, 1); quantity = 1; *itemSlot = nullptr; } if (item->pid == PROTO_ID_MONEY) { if (quantity > 1) { quantity = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, item, quantity); } else { quantity = 1; } if (quantity > 0) { if (quantity == 1) { if (inventoryMoveAlreadyChecked || scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item, nullptr)) { itemSetMoney(item, 1); objectDrop(owner, item); } } else { if (itemRemove(owner, item, quantity - 1) == 0) { Object* item2; if (_inven_from_button(keyCode, &item2, &itemSlot, &owner) != 0) { if (scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item2, nullptr)) { itemSetMoney(item2, quantity); objectDrop(owner, item2); } else { itemAdd(owner, item, quantity - 1); } } else { itemAdd(owner, item, quantity - 1); } } } } } else if (explosiveIsActiveExplosive(item->pid)) { if (inventoryMoveAlreadyChecked || scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item, nullptr)) { _dropped_explosive = 1; objectDrop(owner, item); } } else { if (quantity > 1) { quantity = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, item, quantity); for (int index = 0; index < quantity; index++) { if (_inven_from_button(keyCode, &item, &itemSlot, &owner) != 0) { if (scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item, nullptr)) { objectDrop(owner, item); } } } } else { if (inventoryMoveAlreadyChecked || scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item, nullptr)) { objectDrop(owner, item); } } } break; } case GAME_MOUSE_ACTION_MENU_ITEM_LOOK: if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL) { objectExamineFunc(_stack[0], item, gInventoryPrintItemDescriptionHandler); } else { inventoryExamineItem(_stack[0], item); } break; case GAME_MOUSE_ACTION_MENU_ITEM_USE: switch (itemType) { case ITEM_TYPE_CONTAINER: _container_enter(keyCode, inventoryWindowType); break; case ITEM_TYPE_DRUG: if (drugItemTakeDrug(_stack[0], item)) { if (itemSlot != nullptr) { *itemSlot = nullptr; } else { itemRemove(owner, item, 1); } _obj_connect(item, gDude->tile, gDude->elevation, nullptr); objectDestroy(item); } interfaceRenderHitPoints(true); break; case ITEM_TYPE_WEAPON: case ITEM_TYPE_MISC: if (itemSlot == nullptr) { itemRemove(owner, item, 1); } UseItemResultCode useResult; if (_obj_action_can_use(item)) { useResult = objectUseItemInternal(_stack[0], item); } else { useResult = objectUseItemOnInternal(_stack[0], _stack[0], item); } if (useResult == USE_ITEM_RESULT_REMOVE) { if (itemSlot != nullptr) { *itemSlot = nullptr; } _obj_connect(item, gDude->tile, gDude->elevation, nullptr); objectDestroy(item); } else { if (itemSlot == nullptr) { itemAdd(owner, item, 1); } } } break; case GAME_MOUSE_ACTION_MENU_ITEM_UNLOAD: if (itemSlot == nullptr) { itemRemove(owner, item, 1); } for (;;) { Object* ammo = weaponUnload(item); if (ammo == nullptr) { break; } Rect rect; _obj_disconnect(ammo, &rect); itemAdd(owner, ammo, 1); } if (itemSlot == nullptr) { itemAdd(owner, item, 1); } break; default: break; } inventorySetCursor(INVENTORY_WINDOW_CURSOR_ARROW); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL && actionMenuItem != GAME_MOUSE_ACTION_MENU_ITEM_LOOK) { inventoryRenderSummary(); } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT || inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, inventoryWindowType); } _display_inventory(_stack_offset[_curr_stack], -1, inventoryWindowType); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { barterDisplayTables(gInventoryBarterBackgroundWindow, gPlayerTableObj, gBartererTableObj, -1); } _adjust_fid(); } // 0x473904 int inventoryOpenLooting(Object* looter, Object* target) { MessageListItem messageListItem; if (looter != _inven_dude) { return 0; } ScopedGameMode gm(GameMode::kLoot); if (FID_TYPE(target->fid) == OBJ_TYPE_CRITTER) { if (critterFlagCheck(target->pid, CRITTER_NO_STEAL)) { // You can't find anything to take from that. messageListItem.num = 50; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { displayMonitorAddMessage(messageListItem.text); } return 0; } } if (FID_TYPE(target->fid) == OBJ_TYPE_ITEM) { if (itemGetType(target) == ITEM_TYPE_CONTAINER) { if (target->frame == 0) { CacheEntry* handle; Art* frm = artLock(target->fid, &handle); if (frm != nullptr) { int frameCount = artGetFrameCount(frm); artUnlock(handle); if (frameCount > 1) { return 0; } } } } } int sid = -1; if (!_gIsSteal) { if (objectGetSid(target, &sid) != -1) { scriptSetObjects(sid, looter, nullptr); scriptExecProc(sid, SCRIPT_PROC_PICKUP); Script* script; if (scriptGetScript(sid, &script) != -1) { if (script->scriptOverrides) { return 0; } } } } if (inventoryCommonInit() == -1) { return 0; } _target_pud = &(target->data.inventory); _target_curr_stack = 0; _target_stack_offset[0] = 0; _target_stack[0] = target; Object* hiddenBox = nullptr; if (objectCreateWithFidPid(&hiddenBox, -1, PROTO_ID_JESSE_CONTAINER) == -1) { return 0; } itemMoveAllHidden(target, hiddenBox); CritterEquipped stealTargetEquipped; if (_gIsSteal) { stealTargetEquipped = critterStripEquipped(target); inventoryLootRightEquippedWeight = stealTargetEquipped.weight; } bool isoWasEnabled = _setup_inventory(INVENTORY_WINDOW_TYPE_LOOT); Object** critters = nullptr; int critterCount = 0; int critterIndex = 0; if (!_gIsSteal) { if (FID_TYPE(target->fid) == OBJ_TYPE_CRITTER) { critterCount = objectListCreate(target->tile, target->elevation, OBJ_TYPE_CRITTER, &critters); int endIndex = critterCount - 1; for (int index = 0; index < critterCount; index++) { Object* critter = critters[index]; if ((critter->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) == 0) { critters[index] = critters[endIndex]; critters[endIndex] = critter; critterCount--; index--; endIndex--; } else { critterIndex++; } } if (critterCount == 1) { objectListFree(critters); critterCount = 0; } if (critterCount > 1) { int btn = buttonCreateWithFrm(gInventoryWindow, inventoryLootLayout.prevCritterButtonX, INVENTORY_LOOT_CRITTER_TOGGLE_Y, -1, -1, KEY_PAGE_UP, -1, FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_UP]), FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN])); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } btn = buttonCreateWithFrm(gInventoryWindow, inventoryLootLayout.nextCritterButtonX, INVENTORY_LOOT_CRITTER_TOGGLE_Y, -1, -1, KEY_PAGE_DOWN, -1, FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_UP]), FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN])); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } for (int index = 0; index < critterCount; index++) { if (target == critters[index]) { critterIndex = index; } } } } } // Party member navigation: left/right arrows below the left (player) avatar // switch whose inventory is shown on the left pane. It works when looting, // or "stealing" from a companion (which is just quick looting) Object* const playerObj = _inven_dude; int savedDudeFid = gInventoryWindowDudeFid; std::vector<Object*> partyTargets = { _inven_dude }; if (settings.qol.party_loot_and_barter && (!_gIsSteal || objectIsPartyMember(target))) { for (Object* pm : get_all_party_members_objects(false)) { if (pm != gDude && pm != target) { partyTargets.push_back(pm); } } } int partyTargetIndex = 0; if (partyTargets.size() > 1) { const int btnCenterX = inventoryLootLayout.leftBodyViewX + INVENTORY_BODY_VIEW_WIDTH / 2; int btn = buttonCreateWithFrm(gInventoryWindow, btnCenterX - 20, INVENTORY_LOOT_CRITTER_TOGGLE_Y, -1, -1, KEY_ARROW_LEFT, -1, FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_UP]), FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN])); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } btn = buttonCreateWithFrm(gInventoryWindow, btnCenterX, INVENTORY_LOOT_CRITTER_TOGGLE_Y, -1, -1, KEY_ARROW_RIGHT, -1, FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_UP]), FrmId(OBJ_TYPE_INTERFACE, gInventoryArrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN])); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } } _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_LOOT); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); gInventoryWindowDudeRotationTimestamp = 0; _display_body(target->fid, INVENTORY_WINDOW_TYPE_LOOT); inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); bool isCaughtStealing = false; int stealingXp = 0; int stealingXpBonus = 10; for (;;) { sharedFpsLimiter.mark(); if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) { break; } if (isCaughtStealing) { break; } int keyCode = inputGetInput(); if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { showQuitConfirmationDialog(); } if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) { break; } if (keyCode == 2502 || keyCode == KEY_LOWERCASE_A) { if (!_gIsSteal) { if (keyCode == KEY_LOWERCASE_A) { soundPlayFile("ib1p1xx1"); } int maxCarryWeight = critterGetStat(_inven_dude, STAT_CARRY_WEIGHT); int currentWeight = objectGetInventoryWeight(_inven_dude); int newInventoryWeight = objectGetInventoryWeight(target); if (newInventoryWeight <= maxCarryWeight - currentWeight) { itemMoveAll(target, _inven_dude); _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_LOOT); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); } else { // Sorry, you cannot carry that much. messageListItem.num = 31; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { showDialogBox(messageListItem.text, nullptr, 0, 169, 117, _colorTable[32328], nullptr, _colorTable[32328], 0); } } } // change selected companion } else if (keyCode == KEY_ARROW_LEFT) { if (partyTargets.size() > 1) { partyTargetIndex = (partyTargetIndex > 0) ? partyTargetIndex - 1 : (int)partyTargets.size() - 1; inventorySetLeftPaneCritter(partyTargets[partyTargetIndex], target, INVENTORY_WINDOW_TYPE_LOOT); } } else if (keyCode == KEY_ARROW_RIGHT) { if (partyTargets.size() > 1) { partyTargetIndex = (partyTargetIndex < (int)partyTargets.size() - 1) ? partyTargetIndex + 1 : 0; inventorySetLeftPaneCritter(partyTargets[partyTargetIndex], target, INVENTORY_WINDOW_TYPE_LOOT); } } else if (keyCode == KEY_ARROW_UP) { if (_stack_offset[_curr_stack] > 0) { inventoryLootScrollBy(_stack_offset[_curr_stack], -1, _pud->length); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); } } else if (keyCode == KEY_PAGE_UP) { if (critterCount != 0) { if (critterIndex > 0) { critterIndex -= 1; } else { critterIndex = critterCount - 1; } target = critters[critterIndex]; _target_pud = &(target->data.inventory); _target_stack[0] = target; _target_curr_stack = 0; _target_stack_offset[0] = 0; _display_target_inventory(0, -1, _target_pud, INVENTORY_WINDOW_TYPE_LOOT); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); _display_body(target->fid, INVENTORY_WINDOW_TYPE_LOOT); } } else if (keyCode == KEY_ARROW_DOWN) { if (_stack_offset[_curr_stack] + gInventorySlotsCount < _pud->length) { inventoryLootScrollBy(_stack_offset[_curr_stack], 1, _pud->length); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); } } else if (keyCode == KEY_PAGE_DOWN) { if (critterCount != 0) { if (critterIndex < critterCount - 1) { critterIndex += 1; } else { critterIndex = 0; } target = critters[critterIndex]; _target_pud = &(target->data.inventory); _target_stack[0] = target; _target_curr_stack = 0; _target_stack_offset[0] = 0; _display_target_inventory(0, -1, _target_pud, INVENTORY_WINDOW_TYPE_LOOT); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); _display_body(target->fid, INVENTORY_WINDOW_TYPE_LOOT); } } else if (keyCode == KEY_CTRL_ARROW_UP) { if (_target_stack_offset[_target_curr_stack] > 0) { inventoryLootScrollBy(_target_stack_offset[_target_curr_stack], -1, _target_pud->length); _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_LOOT); windowRefresh(gInventoryWindow); } } else if (keyCode == KEY_CTRL_ARROW_DOWN) { if (_target_stack_offset[_target_curr_stack] + gInventorySlotsCount < _target_pud->length) { inventoryLootScrollBy(_target_stack_offset[_target_curr_stack], 1, _target_pud->length); _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_LOOT); windowRefresh(gInventoryWindow); } } else if (keyCode >= 2500 && keyCode <= 2501) { _container_exit(keyCode, INVENTORY_WINDOW_TYPE_LOOT); } else { if ((mouseGetEvent() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_HAND) { inventorySetCursor(INVENTORY_WINDOW_CURSOR_ARROW); } else { inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); } } else if ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { if (keyCode >= 1000 && keyCode <= 1000 + gInventorySlotsCount) { if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_LOOT); } else { int slotIndex = keyCode - 1000; if (slotIndex + _stack_offset[_curr_stack] < _pud->length) { _gStealCount += 1; _gStealSize += itemGetSize(_stack[_curr_stack]); InventoryItem* inventoryItem = &(_pud->items[_pud->length - (slotIndex + _stack_offset[_curr_stack] + 1)]); int stealXpOverride = -1; InventoryMoveResult rc = _move_inventory(inventoryItem->item, slotIndex, _target_stack[_target_curr_stack], true, &stealXpOverride); if (rc == INVENTORY_MOVE_RESULT_CAUGHT_STEALING) { isCaughtStealing = true; } else if (rc == INVENTORY_MOVE_RESULT_SUCCESS) { stealingXp += stealXpOverride >= 0 ? stealXpOverride : stealingXpBonus; stealingXpBonus += 10; } _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_LOOT); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); } keyCode = -1; } } else if (keyCode >= 2000 && keyCode <= 2000 + gInventorySlotsCount) { if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_LOOT); } else { int slotIndex = keyCode - 2000; if (slotIndex + _target_stack_offset[_target_curr_stack] < _target_pud->length) { _gStealCount += 1; _gStealSize += itemGetSize(_stack[_curr_stack]); InventoryItem* inventoryItem = &(_target_pud->items[_target_pud->length - (slotIndex + _target_stack_offset[_target_curr_stack] + 1)]); int stealXpOverride = -1; InventoryMoveResult rc = _move_inventory(inventoryItem->item, slotIndex, _target_stack[_target_curr_stack], false, &stealXpOverride); if (rc == INVENTORY_MOVE_RESULT_CAUGHT_STEALING) { isCaughtStealing = true; } else if (rc == INVENTORY_MOVE_RESULT_SUCCESS) { stealingXp += stealXpOverride >= 0 ? stealXpOverride : stealingXpBonus; stealingXpBonus += 10; } _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_LOOT); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); } } } } else if ((mouseGetEvent() & MOUSE_EVENT_WHEEL) != 0) { int wheelX; int wheelY; mouseGetWheel(&wheelX, &wheelY); if (inventoryLootMouseHitTestScroller(false)) { if (wheelY > 0 && _stack_offset[_curr_stack] > 0) { inventoryLootScrollBy(_stack_offset[_curr_stack], -1, _pud->length); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); } else if (wheelY < 0 && _stack_offset[_curr_stack] + gInventorySlotsCount < _pud->length) { inventoryLootScrollBy(_stack_offset[_curr_stack], 1, _pud->length); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); } } else if (inventoryLootMouseHitTestScroller(true)) { if (wheelY > 0 && _target_stack_offset[_target_curr_stack] > 0) { inventoryLootScrollBy(_target_stack_offset[_target_curr_stack], -1, _target_pud->length); _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_LOOT); windowRefresh(gInventoryWindow); } else if (wheelY < 0 && _target_stack_offset[_target_curr_stack] + gInventorySlotsCount < _target_pud->length) { inventoryLootScrollBy(_target_stack_offset[_target_curr_stack], 1, _target_pud->length); _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_LOOT); windowRefresh(gInventoryWindow); } } } } if (keyCode == KEY_ESCAPE) { break; } renderPresent(); sharedFpsLimiter.throttle(); } if (critterCount != 0) { objectListFree(critters); } if (_gIsSteal) { critterRestoreEquipped(target, stealTargetEquipped); } itemMoveAll(hiddenBox, target); objectDestroy(hiddenBox, nullptr); if (_gIsSteal && !isCaughtStealing && stealingXp > 0 && !objectIsPartyMember(target)) { stealingXp = std::min(300 - skillGetValue(looter, SKILL_STEAL), stealingXp); debugPrint("\n[[[%d]]]", 300 - skillGetValue(looter, SKILL_STEAL)); // SFALL: Display actual xp received. int xpGained; pcAddExperience(stealingXp, &xpGained); // You gain %d experience points for successfully using your Steal skill. messageListItem.num = 29; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { char formattedText[200]; snprintf(formattedText, sizeof(formattedText), messageListItem.text, xpGained); displayMonitorAddMessage(formattedText); } } inventoryLootRightEquippedWeight = 0; gInventoryWindowDudeFid = savedDudeFid; _exit_inventory(isoWasEnabled); _inven_dude = playerObj; // NOTE: Uninline. inventoryCommonFree(); if (_gIsSteal && isCaughtStealing && _gStealCount > 0 && objectGetSid(target, &sid) != -1) { scriptSetObjects(sid, looter, nullptr); scriptExecProc(sid, SCRIPT_PROC_PICKUP); // TODO: Looks like inlining, script is not used. Script* script; scriptGetScript(sid, &script); } return 0; } // 0x4746A0 int inventoryOpenStealing(Object* thief, Object* target) { if (thief == target) { return -1; } _gIsSteal = PID_TYPE(thief->pid) == OBJ_TYPE_CRITTER && critterIsActive(target); _gStealCount = 0; _gStealSize = 0; int rc = inventoryOpenLooting(thief, target); _gIsSteal = 0; _gStealCount = 0; _gStealSize = 0; return rc; } // 0x474708 // note: this is looting and stealing, not the inventory screen static InventoryMoveResult _move_inventory(Object* item, int slotIndex, Object* targetObj, bool isPlanting, int* stealXpOverridePtr) { assert(stealXpOverridePtr != nullptr); *stealXpOverridePtr = -1; bool needRefresh = true; Rect rect; int quantity; if (isPlanting) { rect.left = inventoryLootGetSlotX(false, slotIndex); rect.top = inventoryLootGetSlotY(slotIndex); InventoryItem* inventoryItem = &(_pud->items[_pud->length - (slotIndex + _stack_offset[_curr_stack] + 1)]); quantity = inventoryItem->quantity; if (quantity > 1) { _display_inventory(_stack_offset[_curr_stack], slotIndex, INVENTORY_WINDOW_TYPE_LOOT); needRefresh = false; } } else { rect.left = inventoryLootGetSlotX(true, slotIndex); rect.top = inventoryLootGetSlotY(slotIndex); InventoryItem* inventoryItem = &(_target_pud->items[_target_pud->length - (slotIndex + _target_stack_offset[_target_curr_stack] + 1)]); quantity = inventoryItem->quantity; if (quantity > 1) { _display_target_inventory(_target_stack_offset[_target_curr_stack], slotIndex, _target_pud, INVENTORY_WINDOW_TYPE_LOOT); windowRefresh(gInventoryWindow); needRefresh = false; } } if (needRefresh) { unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); unsigned char* backgroundData = inventoryLootFrmImage.getData(); int backgroundWidth = inventoryLootFrmImage.getWidth(); if (backgroundData != nullptr) { blitBufferToBuffer(backgroundData + backgroundWidth * rect.top + rect.left, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, backgroundWidth, windowBuffer + inventoryLootLayout.windowWidth * rect.top + rect.left, inventoryLootLayout.windowWidth); } rect.right = rect.left + INVENTORY_SLOT_WIDTH - 1; rect.bottom = rect.top + INVENTORY_SLOT_HEIGHT - 1; windowRefreshRect(gInventoryWindow, &rect); } bool immediate = _ctrl_pressed(); _drag_item_loop(item, immediate); InventoryMoveResult result = INVENTORY_MOVE_RESULT_FAILED; MessageListItem messageListItem; if (isPlanting) { if (immediate || inventoryLootMouseHitTestScroller(true)) { int quantityToMove = quantity; if (quantity > 1 && !immediate) { quantityToMove = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, item, quantity); } if (quantityToMove != -1) { bool skipMove = false; if (_gIsSteal && _inven_dude == gDude) { SkillStealResult stealResult = skillsPerformStealing(_inven_dude, targetObj, item, quantityToMove, true, stealXpOverridePtr); if (stealResult == SkillStealResult::Caught) { result = INVENTORY_MOVE_RESULT_CAUGHT_STEALING; } else if (stealResult == SkillStealResult::Fail) { skipMove = true; } } if (!skipMove && result != INVENTORY_MOVE_RESULT_CAUGHT_STEALING) { if (itemMove(_inven_dude, targetObj, item, quantityToMove) != -1) { result = INVENTORY_MOVE_RESULT_SUCCESS; } else { // There is no space left for that item. messageListItem.num = 26; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { displayMonitorAddMessage(messageListItem.text); } } } } } } else { if (immediate || inventoryLootMouseHitTestScroller(false)) { int quantityToMove = quantity; if (quantity > 1 && !immediate) { quantityToMove = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, item, quantity); } if (quantityToMove != -1) { bool skipMove = false; if (_gIsSteal && _inven_dude == gDude) { SkillStealResult stealResult = skillsPerformStealing(_inven_dude, targetObj, item, quantityToMove, false, stealXpOverridePtr); if (stealResult == SkillStealResult::Caught) { result = INVENTORY_MOVE_RESULT_CAUGHT_STEALING; } else if (stealResult == SkillStealResult::Fail) { skipMove = true; } } if (!skipMove && result != INVENTORY_MOVE_RESULT_CAUGHT_STEALING) { if (itemMove(targetObj, _inven_dude, item, quantityToMove) == 0) { if ((item->flags & OBJECT_IN_RIGHT_HAND) != 0) { targetObj->fid = buildFid(FID_TYPE(targetObj->fid), targetObj->fid & 0xFFF, FID_ANIM_TYPE(targetObj->fid), 0, targetObj->rotation + 1); } targetObj->flags &= ~OBJECT_EQUIPPED; result = INVENTORY_MOVE_RESULT_SUCCESS; } else { // You cannot pick that up. You are at your maximum weight capacity. messageListItem.num = 25; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { displayMonitorAddMessage(messageListItem.text); } } } } } } inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); return result; } // TODO: move barter related code to separate file // Calculates value of NPC/barterer (request) and player (offer) tables. // // 0x474B2C barter_compute_value static std::pair<int, int> barterComputeTablesValue(Object* dude, Object* npc, bool offerButton) { assert(!gGameDialogSpeakerIsPartyMember); const int rawValue = objectGetCost(gBartererTableObj); const int caps = itemGetTotalCaps(gBartererTableObj); const int offerValue = objectGetCost(gPlayerTableObj); BarterPriceContext ctx { dude, npc, gBartererTableObj, gPlayerTableObj, 0, offerValue, rawValue, caps, offerButton, false }; const int valueMinusCaps = rawValue - caps; double perkBonus = 0.0; if (dude == gDude) { if (perkHasRank(gDude, PERK_MASTER_TRADER)) { perkBonus = 25.0; } } const int partyBarter = partyGetBestSkillValue(SKILL_BARTER); const int npcBarter = skillGetValue(npc, SKILL_BARTER); // TODO: Check in debugger, complex math, probably uses floats, not doubles. double barterModMult = (gBarterFinalModifier + 100.0 - perkBonus) * 0.01; const double balancedCost = (160.0 + npcBarter) / (160.0 + partyBarter) * (valueMinusCaps * 2.0); if (barterModMult < 0) { // TODO: Probably 0.01 as float. barterModMult = 0.0099999998; } ctx.value = static_cast<int>(barterModMult * balancedCost + caps); scriptHooks_BarterPrice(&ctx); return { ctx.value, ctx.offerValue }; } // Calculates weight of NPC (request) and player (offer) tables. Used when bartering with party members. static std::pair<int, int> barterComputeTablesWeight(Object* dude, Object* npc) { assert(gGameDialogSpeakerIsPartyMember); const int rawValue = objectGetCost(gBartererTableObj); const int caps = itemGetTotalCaps(gBartererTableObj); const int offerValue = objectGetCost(gPlayerTableObj); BarterPriceContext ctx { dude, npc, gBartererTableObj, gPlayerTableObj, 0, offerValue, rawValue, caps, false, true }; // Hook is invoked but return values are not used. This matches sfall behavior. scriptHooks_BarterPrice(&ctx); return { objectGetInventoryWeight(gBartererTableObj), objectGetInventoryWeight(gPlayerTableObj) }; } // 0x474C50 barter_attempt_transaction static int barterAttemptTransaction(Object* dude, Object* offerTable, Object* npc, Object* barterTable) { MessageListItem messageListItem; int weightAvailable = critterGetStat(dude, STAT_CARRY_WEIGHT) - objectGetInventoryWeight(dude); if (objectGetInventoryWeight(barterTable) > weightAvailable) { // Sorry, you cannot carry that much. messageListItem.num = 31; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { gameDialogRenderSupplementaryMessage(messageListItem.text); } return -1; } if (gGameDialogSpeakerIsPartyMember) { int npcWeightAvailable = critterGetStat(npc, STAT_CARRY_WEIGHT) - objectGetInventoryWeight(npc); if (objectGetInventoryWeight(offerTable) > npcWeightAvailable) { // Sorry, that's too much to carry. messageListItem.num = 32; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { gameDialogRenderSupplementaryMessage(messageListItem.text); } return -1; } } else { bool badOffer = false; if (offerTable->data.inventory.length == 0) { badOffer = true; } else { if (itemIsQueued(offerTable)) { if (offerTable->pid != PROTO_ID_GEIGER_COUNTER_I || miscItemTurnOff(offerTable) == -1) { badOffer = true; } } } if (!badOffer) { auto [requestValue, offerValue] = barterComputeTablesValue(dude, npc, true); if (requestValue > offerValue) { badOffer = true; } } if (badOffer) { // No, your offer is not good enough. messageListItem.num = 28; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { gameDialogRenderSupplementaryMessage(messageListItem.text); } return -1; } } itemMoveAll(barterTable, dude); itemMoveAll(offerTable, npc); return 0; } static int barterGetMovedQuantity(Object* item, int maxQuantity, bool fromPlayer, bool fromInventory, bool immediate) { if (maxQuantity <= 1) { return maxQuantity; } int suggestedValue = 1; if (item->pid == PROTO_ID_MONEY && !gGameDialogSpeakerIsPartyMember) { // Calculate change money automatically auto [totalCostNpc, totalCostPlayer] = barterComputeTablesValue(gDude, _target_stack[0]); // Actor's balance: negative - the actor must add money to balance the tables and vice versa int balance = fromPlayer ? totalCostPlayer - totalCostNpc : totalCostNpc - totalCostPlayer; if ((balance < 0 && fromInventory) || (balance > 0 && !fromInventory)) { suggestedValue = std::min(std::abs(balance), maxQuantity); if (immediate) { return suggestedValue; } } } if (immediate) { return maxQuantity; } return inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, item, maxQuantity, suggestedValue); } // clicked on an inventory in preparation of dragging or immediate transfer static void _drag_item_loop(Object* item, bool immediate) { if (immediate) { soundPlayFile("iputdown"); // prevent item look from occuring after immediate move _im_value = -1; return; } FrmImage itemInventoryFrmImage; int itemInventoryFid = itemGetInventoryFid(item); if (itemInventoryFrmImage.lock(itemInventoryFid)) { int width = itemInventoryFrmImage.getWidth(); int height = itemInventoryFrmImage.getHeight(); unsigned char* data = itemInventoryFrmImage.getData(); mouseSetFrame(data, width, height, width, width / 2, height / 2, 0); soundPlayFile("ipickup1"); } do { sharedFpsLimiter.mark(); inputGetInput(); renderPresent(); sharedFpsLimiter.throttle(); } while ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0); if (itemInventoryFrmImage.isLocked()) { itemInventoryFrmImage.unlock(); soundPlayFile("iputdown"); } } // 0x474DAC barter_move_inventory static void barterMoveToTable(Object* item, int quantity, int slotIndex, int indexOffset, Object* npc, Object* sourceTable, bool fromDude) { Rect rect; if (fromDude) { rect.left = 31; rect.top = INVENTORY_SLOT_HEIGHT * slotIndex + 31; } else { rect.left = 389; rect.top = INVENTORY_SLOT_HEIGHT * slotIndex + 31; } if (quantity > 1) { if (fromDude) { _display_inventory(indexOffset, slotIndex, INVENTORY_WINDOW_TYPE_TRADE); } else { _display_target_inventory(indexOffset, slotIndex, _target_pud, INVENTORY_WINDOW_TYPE_TRADE); } } else { unsigned char* dest = windowGetBuffer(gInventoryWindow); unsigned char* src = windowGetBuffer(gInventoryBarterBackgroundWindow); int pitch = INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH; blitBufferToBuffer(src + pitch * rect.top + rect.left + INVENTORY_TRADE_WINDOW_OFFSET, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, pitch, dest + INVENTORY_TRADE_WINDOW_WIDTH * rect.top + rect.left, INVENTORY_TRADE_WINDOW_WIDTH); rect.right = rect.left + INVENTORY_SLOT_WIDTH - 1; rect.bottom = rect.top + INVENTORY_SLOT_HEIGHT - 1; windowRefreshRect(gInventoryWindow, &rect); } bool immediate = _ctrl_pressed(); _drag_item_loop(item, immediate); MessageListItem messageListItem; if (fromDude) { if (immediate || mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y)) { int quantityToMove = barterGetMovedQuantity(item, quantity, true, true, immediate); if (quantityToMove != -1) { if (itemMoveForce(_inven_dude, sourceTable, item, quantityToMove) == -1) { // There is no space left for that item. messageListItem.num = 26; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { displayMonitorAddMessage(messageListItem.text); } } } } } else { if (immediate || mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_X, INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_Y)) { int quantityToMove = barterGetMovedQuantity(item, quantity, false, true, immediate); if (quantityToMove != -1) { if (itemMoveForce(npc, sourceTable, item, quantityToMove) == -1) { // You cannot pick that up. You are at your maximum weight capacity. messageListItem.num = 25; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { displayMonitorAddMessage(messageListItem.text); } } } } } inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); } // 0x475070 barter_move_from_table_inventory static void barterMoveFromTable(Object* item, int quantity, int slotIndex, Object* npc, Object* sourceTable, bool fromDude) { Rect rect; if (fromDude) { rect.left = INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD; rect.top = INVENTORY_SLOT_HEIGHT * slotIndex + INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y_PAD; } else { rect.left = INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD; rect.top = INVENTORY_SLOT_HEIGHT * slotIndex + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y_PAD; } if (quantity > 1) { if (fromDude) { barterDisplayTables(gInventoryBarterBackgroundWindow, sourceTable, nullptr, slotIndex); } else { barterDisplayTables(gInventoryBarterBackgroundWindow, nullptr, sourceTable, slotIndex); } } else { unsigned char* dest = windowGetBuffer(gInventoryWindow); unsigned char* src = windowGetBuffer(gInventoryBarterBackgroundWindow); int pitch = INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH; blitBufferToBuffer(src + pitch * rect.top + rect.left + INVENTORY_TRADE_WINDOW_OFFSET, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, pitch, dest + INVENTORY_TRADE_WINDOW_WIDTH * rect.top + rect.left, INVENTORY_TRADE_WINDOW_WIDTH); rect.right = rect.left + INVENTORY_SLOT_WIDTH - 1; rect.bottom = rect.top + INVENTORY_SLOT_HEIGHT - 1; windowRefreshRect(gInventoryWindow, &rect); } bool immediate = _ctrl_pressed(); _drag_item_loop(item, immediate); MessageListItem messageListItem; if (fromDude) { if (immediate || mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y)) { int quantityToMove = barterGetMovedQuantity(item, quantity, true, false, immediate); if (quantityToMove != -1) { if (itemMoveForce(sourceTable, _inven_dude, item, quantityToMove) == -1) { // There is no space left for that item. messageListItem.num = 26; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { displayMonitorAddMessage(messageListItem.text); } } } } } else { if (immediate || mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_X, INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_Y)) { int quantityToMove = barterGetMovedQuantity(item, quantity, false, false, immediate); if (quantityToMove != -1) { if (itemMoveForce(sourceTable, npc, item, quantityToMove) == -1) { // You cannot pick that up. You are at your maximum weight capacity. messageListItem.num = 25; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { displayMonitorAddMessage(messageListItem.text); } } } } } inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); } // 0x475334 display_table_inventories static void barterDisplayTables(int win, Object* leftTable, Object* rightTable, int draggedSlotIndex) { unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); int oldFont = fontGetCurrent(); fontSetCurrent(101); char formattedText[80]; int rectHeight = fontGetLineHeight() + INVENTORY_SLOT_HEIGHT * gInventorySlotsCount; auto [requestValue, offerValue] = gGameDialogSpeakerIsPartyMember ? barterComputeTablesWeight(gDude, _target_stack[0]) : barterComputeTablesValue(gDude, _target_stack[0]); if (leftTable != nullptr) { unsigned char* src = windowGetBuffer(win); blitBufferToBuffer(src + INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH * INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y + INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD + INVENTORY_TRADE_WINDOW_OFFSET, INVENTORY_SLOT_WIDTH, rectHeight + 1, INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH, windowBuffer + INVENTORY_TRADE_WINDOW_WIDTH * INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y + INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD, INVENTORY_TRADE_WINDOW_WIDTH); unsigned char* dest = windowBuffer + INVENTORY_TRADE_WINDOW_WIDTH * INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y_PAD + INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD; Inventory* inventory = &(leftTable->data.inventory); for (int index = 0; index < gInventorySlotsCount && index + gPlayerTableOffset < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[inventory->length - (index + gPlayerTableOffset + 1)]); int inventoryFid = itemGetInventoryFid(inventoryItem->item); artRender(inventoryFid, dest, INVENTORY_SLOT_WIDTH_PAD, INVENTORY_SLOT_HEIGHT_PAD, INVENTORY_TRADE_WINDOW_WIDTH); _display_inventory_info(inventoryItem->item, inventoryItem->quantity, dest, INVENTORY_TRADE_WINDOW_WIDTH, index == draggedSlotIndex); dest += INVENTORY_TRADE_WINDOW_WIDTH * INVENTORY_SLOT_HEIGHT; } if (gGameDialogSpeakerIsPartyMember) { MessageListItem messageListItem; messageListItem.num = 30; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { snprintf(formattedText, sizeof(formattedText), "%s %d", messageListItem.text, offerValue); } } else { snprintf(formattedText, sizeof(formattedText), "$%d", offerValue); } fontDrawText(windowBuffer + INVENTORY_TRADE_WINDOW_WIDTH * (INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y_PAD) + INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD, formattedText, 80, INVENTORY_TRADE_WINDOW_WIDTH, _colorTable[32767]); Rect rect; rect.left = INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD; rect.top = INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y_PAD; // NOTE: Odd math, the only way to get 223 is to subtract 2. rect.right = INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD + INVENTORY_SLOT_WIDTH_PAD - 2; rect.bottom = rect.top + rectHeight; windowRefreshRect(gInventoryWindow, &rect); } if (rightTable != nullptr) { unsigned char* src = windowGetBuffer(win); blitBufferToBuffer(src + INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH * INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD + INVENTORY_TRADE_WINDOW_OFFSET, INVENTORY_SLOT_WIDTH, rectHeight + 1, INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH, windowBuffer + INVENTORY_TRADE_WINDOW_WIDTH * INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD, INVENTORY_TRADE_WINDOW_WIDTH); unsigned char* dest = windowBuffer + INVENTORY_TRADE_WINDOW_WIDTH * INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y_PAD + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD; Inventory* inventory = &(rightTable->data.inventory); for (int index = 0; index < gInventorySlotsCount && index + gBartererTableOffset < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[inventory->length - (index + gBartererTableOffset + 1)]); int inventoryFid = itemGetInventoryFid(inventoryItem->item); artRender(inventoryFid, dest, INVENTORY_SLOT_WIDTH_PAD, INVENTORY_SLOT_HEIGHT_PAD, INVENTORY_TRADE_WINDOW_WIDTH); _display_inventory_info(inventoryItem->item, inventoryItem->quantity, dest, INVENTORY_TRADE_WINDOW_WIDTH, index == draggedSlotIndex); dest += INVENTORY_TRADE_WINDOW_WIDTH * INVENTORY_SLOT_HEIGHT; } if (gGameDialogSpeakerIsPartyMember) { MessageListItem messageListItem; messageListItem.num = 30; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { snprintf(formattedText, sizeof(formattedText), "%s %d", messageListItem.text, requestValue); } } else { snprintf(formattedText, sizeof(formattedText), "$%d", requestValue); } fontDrawText(windowBuffer + INVENTORY_TRADE_WINDOW_WIDTH * (INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y_PAD) + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD, formattedText, 80, INVENTORY_TRADE_WINDOW_WIDTH, _colorTable[32767]); Rect rect; rect.left = INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD; rect.top = INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y_PAD; // NOTE: Odd math, likely should be `INVENTORY_SLOT_WIDTH_PAD`. rect.right = INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD + INVENTORY_SLOT_WIDTH; rect.bottom = rect.top + rectHeight; windowRefreshRect(gInventoryWindow, &rect); } fontSetCurrent(oldFont); } static bool _ctrl_pressed() { const Uint8* keyboardState = SDL_GetKeyboardState(nullptr); return keyboardState[SDL_SCANCODE_LCTRL] || keyboardState[SDL_SCANCODE_RCTRL]; } // 0x4757F0 barter_inventory void barterProcessUI(int win, Object* barterer, Object* playerTable, Object* bartererTable, int barterMod) { ScopedGameMode gm(GameMode::kBarter); gBarterFinalModifier = barterMod; if (inventoryCommonInit() == -1) { return; } Object* armor = critterGetArmor(barterer); if (armor != nullptr) { itemRemove(barterer, armor, 1); } Object* item1 = nullptr; Object* item2 = critterGetItem2(barterer); if (item2 != nullptr) { itemRemove(barterer, item2, 1); } else { if (!gGameDialogSpeakerIsPartyMember) { item1 = inventoryFindByType(barterer, ITEM_TYPE_WEAPON, nullptr); if (item1 != nullptr) { itemRemove(barterer, item1, 1); } } } Object* hiddenBox = nullptr; if (objectCreateWithFidPid(&hiddenBox, -1, PROTO_ID_JESSE_CONTAINER) == -1) { return; } _pud = &(_inven_dude->data.inventory); gBartererTableObj = bartererTable; gPlayerTableObj = playerTable; gPlayerTableOffset = 0; gBartererTableOffset = 0; gPlayerTableInventory = &(playerTable->data.inventory); gBartererTableInventory = &(bartererTable->data.inventory); gInventoryBarterBackgroundWindow = win; _target_curr_stack = 0; _target_pud = &(barterer->data.inventory); _target_stack[0] = barterer; _target_stack_offset[0] = 0; Object* const playerObj = _inven_dude; int savedDudeFid = gInventoryWindowDudeFid; std::vector<Object*> partyTargets = { _inven_dude }; if (settings.qol.party_loot_and_barter) { for (Object* pm : get_all_party_members_objects(false)) { if (pm != gDude && pm != barterer) { partyTargets.push_back(pm); } } } int partyTargetIndex = 0; bool isoWasEnabled = _setup_inventory(INVENTORY_WINDOW_TYPE_TRADE); _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_TRADE); _display_inventory(_stack_offset[0], -1, INVENTORY_WINDOW_TYPE_TRADE); _display_body(barterer->fid, INVENTORY_WINDOW_TYPE_TRADE); windowRefresh(gInventoryBarterBackgroundWindow); barterDisplayTables(win, playerTable, bartererTable, -1); inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); int barterReactionModifier = 0; switch (reactionTranslateValue(reactionGetValue(barterer))) { case NPC_REACTION_BAD: barterReactionModifier = 25; break; case NPC_REACTION_NEUTRAL: break; case NPC_REACTION_GOOD: barterReactionModifier = -15; break; default: assert(false && "Should be unreachable"); } int keyCode = -1; for (;;) { sharedFpsLimiter.mark(); if (keyCode == KEY_ESCAPE || _game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) { break; } keyCode = inputGetInput(); if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { showQuitConfirmationDialog(); } if (_game_user_wants_to_quit != GAME_QUIT_REQUEST_NONE) { break; } gBarterFinalModifier = barterMod + barterReactionModifier; if (keyCode == KEY_LOWERCASE_T || barterReactionModifier <= -30) { // T == return to talk itemMoveAll(bartererTable, barterer); itemMoveAll(playerTable, gDude); gameDialogEndBarter(); break; } else if (keyCode == KEY_LOWERCASE_M) { // M == attempt offer if (playerTable->data.inventory.length != 0 || gBartererTableObj->data.inventory.length != 0) { // TODO: inven_dude can potentially be a container (bag) which was opened during trade, but code inside barterAttemptTransaction assumes it's a critter; maybe remove this arg and access gDude always? if (barterAttemptTransaction(_inven_dude, playerTable, barterer, bartererTable) == 0) { _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_TRADE); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); barterDisplayTables(win, playerTable, bartererTable, -1); // Ok, that's a good trade. MessageListItem messageListItem; messageListItem.num = 27; if (!gGameDialogSpeakerIsPartyMember) { if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { gameDialogRenderSupplementaryMessage(messageListItem.text); } } } } } else if (keyCode == KEY_ARROW_UP) { if (_stack_offset[_curr_stack] > 0) { _stack_offset[_curr_stack] -= 1; _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); } } else if (keyCode == KEY_PAGE_UP) { if (gPlayerTableOffset > 0) { gPlayerTableOffset -= 1; barterDisplayTables(win, playerTable, bartererTable, -1); } } else if (keyCode == KEY_ARROW_DOWN) { if (_stack_offset[_curr_stack] + gInventorySlotsCount < _pud->length) { _stack_offset[_curr_stack] += 1; _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); } } else if (keyCode == KEY_PAGE_DOWN) { if (gPlayerTableOffset + gInventorySlotsCount < gPlayerTableInventory->length) { gPlayerTableOffset += 1; barterDisplayTables(win, playerTable, bartererTable, -1); } } else if (keyCode == KEY_CTRL_PAGE_DOWN) { if (gBartererTableOffset + gInventorySlotsCount < gBartererTableInventory->length) { gBartererTableOffset++; barterDisplayTables(win, playerTable, bartererTable, -1); } } else if (keyCode == KEY_CTRL_PAGE_UP) { if (gBartererTableOffset > 0) { gBartererTableOffset -= 1; barterDisplayTables(win, playerTable, bartererTable, -1); } } else if (keyCode == KEY_CTRL_ARROW_UP) { if (_target_stack_offset[_target_curr_stack] > 0) { _target_stack_offset[_target_curr_stack] -= 1; _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_TRADE); windowRefresh(gInventoryWindow); } } else if (keyCode == KEY_CTRL_ARROW_DOWN) { if (_target_stack_offset[_target_curr_stack] + gInventorySlotsCount < _target_pud->length) { _target_stack_offset[_target_curr_stack] += 1; _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_TRADE); windowRefresh(gInventoryWindow); } } else if (keyCode == KEY_ARROW_LEFT) { if (partyTargets.size() > 1) { partyTargetIndex = (partyTargetIndex > 0) ? partyTargetIndex - 1 : (int)partyTargets.size() - 1; inventorySetLeftPaneCritter(partyTargets[partyTargetIndex], barterer, INVENTORY_WINDOW_TYPE_TRADE); } } else if (keyCode == KEY_ARROW_RIGHT) { if (partyTargets.size() > 1) { partyTargetIndex = (partyTargetIndex < (int)partyTargets.size() - 1) ? partyTargetIndex + 1 : 0; inventorySetLeftPaneCritter(partyTargets[partyTargetIndex], barterer, INVENTORY_WINDOW_TYPE_TRADE); } } else if (keyCode >= 2500 && keyCode <= 2501) { _container_exit(keyCode, INVENTORY_WINDOW_TYPE_TRADE); } else { if ((mouseGetEvent() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_HAND) { inventorySetCursor(INVENTORY_WINDOW_CURSOR_ARROW); } else { inventorySetCursor(INVENTORY_WINDOW_CURSOR_HAND); } } else if ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { if (keyCode >= 1000 && keyCode <= 1000 + gInventorySlotsCount) { if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_TRADE); barterDisplayTables(win, playerTable, nullptr, -1); } else { // player inventory int slotIndex = keyCode - 1000; if (slotIndex + _stack_offset[_curr_stack] < _pud->length) { int offset = _stack_offset[_curr_stack]; InventoryItem* inventoryItem = &(_pud->items[_pud->length - (slotIndex + offset + 1)]); barterMoveToTable(inventoryItem->item, inventoryItem->quantity, slotIndex, offset, barterer, playerTable, true); _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_TRADE); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); barterDisplayTables(win, playerTable, nullptr, -1); } } keyCode = -1; } else if (keyCode >= 2000 && keyCode <= 2000 + gInventorySlotsCount) { // merchant inventory if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_TRADE); barterDisplayTables(win, nullptr, bartererTable, -1); } else { int slotIndex = keyCode - 2000; if (slotIndex + _target_stack_offset[_target_curr_stack] < _target_pud->length) { int stackOffset = _target_stack_offset[_target_curr_stack]; InventoryItem* inventoryItem = &(_target_pud->items[_target_pud->length - (slotIndex + stackOffset + 1)]); barterMoveToTable(inventoryItem->item, inventoryItem->quantity, slotIndex, stackOffset, barterer, bartererTable, false); _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_TRADE); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); barterDisplayTables(win, nullptr, bartererTable, -1); } } keyCode = -1; } else if (keyCode >= 2300 && keyCode <= 2300 + gInventorySlotsCount) { // player table (offer) if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_TRADE); barterDisplayTables(win, playerTable, nullptr, -1); } else { int slotIndex = keyCode - 2300; if (slotIndex < gPlayerTableInventory->length) { InventoryItem* inventoryItem = &(gPlayerTableInventory->items[gPlayerTableInventory->length - (slotIndex + gPlayerTableOffset + 1)]); barterMoveFromTable(inventoryItem->item, inventoryItem->quantity, slotIndex, barterer, playerTable, true); _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_TRADE); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); barterDisplayTables(win, playerTable, nullptr, -1); } } keyCode = -1; } else if (keyCode >= 2400 && keyCode <= 2400 + gInventorySlotsCount) { // merchant table (offer) if (gInventoryCursor == INVENTORY_WINDOW_CURSOR_ARROW) { inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_TRADE); barterDisplayTables(win, nullptr, bartererTable, -1); } else { int slotIndex = keyCode - 2400; if (slotIndex < gBartererTableInventory->length) { InventoryItem* inventoryItem = &(gBartererTableInventory->items[gBartererTableInventory->length - (slotIndex + gBartererTableOffset + 1)]); barterMoveFromTable(inventoryItem->item, inventoryItem->quantity, slotIndex, barterer, bartererTable, false); _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_TRADE); _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); barterDisplayTables(win, nullptr, bartererTable, -1); } } keyCode = -1; } } else if ((mouseGetEvent() & MOUSE_EVENT_WHEEL) != 0) { if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y)) { int wheelX; int wheelY; mouseGetWheel(&wheelX, &wheelY); if (wheelY > 0) { if (_stack_offset[_curr_stack] > 0) { _stack_offset[_curr_stack] -= 1; _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); } } else if (wheelY < 0) { if (_stack_offset[_curr_stack] + gInventorySlotsCount < _pud->length) { _stack_offset[_curr_stack] += 1; _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); } } } else if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y)) { int wheelX; int wheelY; mouseGetWheel(&wheelX, &wheelY); if (wheelY > 0) { if (gPlayerTableOffset > 0) { gPlayerTableOffset -= 1; barterDisplayTables(win, playerTable, bartererTable, -1); } } else if (wheelY < 0) { if (gPlayerTableOffset + gInventorySlotsCount < gPlayerTableInventory->length) { gPlayerTableOffset += 1; barterDisplayTables(win, playerTable, bartererTable, -1); } } } else if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_X, INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_Y)) { int wheelX; int wheelY; mouseGetWheel(&wheelX, &wheelY); if (wheelY > 0) { if (_target_stack_offset[_target_curr_stack] > 0) { _target_stack_offset[_target_curr_stack] -= 1; _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_TRADE); windowRefresh(gInventoryWindow); } } else if (wheelY < 0) { if (_target_stack_offset[_target_curr_stack] + gInventorySlotsCount < _target_pud->length) { _target_stack_offset[_target_curr_stack] += 1; _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, INVENTORY_WINDOW_TYPE_TRADE); windowRefresh(gInventoryWindow); } } } else if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_X, INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_Y)) { int wheelX; int wheelY; mouseGetWheel(&wheelX, &wheelY); if (wheelY > 0) { if (gBartererTableOffset > 0) { gBartererTableOffset -= 1; barterDisplayTables(win, playerTable, bartererTable, -1); } } else if (wheelY < 0) { if (gBartererTableOffset + gInventorySlotsCount < gBartererTableInventory->length) { gBartererTableOffset++; barterDisplayTables(win, playerTable, bartererTable, -1); } } } } } renderPresent(); sharedFpsLimiter.throttle(); } itemMoveAll(hiddenBox, barterer); objectDestroy(hiddenBox, nullptr); if (armor != nullptr) { armor->flags |= OBJECT_WORN; itemAdd(barterer, armor, 1); } if (item2 != nullptr) { item2->flags |= OBJECT_IN_RIGHT_HAND; itemAdd(barterer, item2, 1); } if (item1 != nullptr) { itemAdd(barterer, item1, 1); } gInventoryWindowDudeFid = savedDudeFid; _exit_inventory(isoWasEnabled); _inven_dude = playerObj; // NOTE: Uninline. inventoryCommonFree(); } // 0x47620C static void _container_enter(int keyCode, int inventoryWindowType) { if (keyCode >= 2000) { int index = _target_pud->length - (_target_stack_offset[_target_curr_stack] + keyCode - 2000 + 1); if (index < _target_pud->length && _target_curr_stack < 9) { InventoryItem* inventoryItem = &(_target_pud->items[index]); Object* item = inventoryItem->item; if (itemGetType(item) == ITEM_TYPE_CONTAINER) { _target_curr_stack += 1; _target_stack[_target_curr_stack] = item; _target_stack_offset[_target_curr_stack] = 0; _target_pud = &(item->data.inventory); _display_body(item->fid, inventoryWindowType); _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, inventoryWindowType); windowRefresh(gInventoryWindow); } } } else { int index = _pud->length - (_stack_offset[_curr_stack] + keyCode - 1000 + 1); if (index < _pud->length && _curr_stack < 9) { InventoryItem* inventoryItem = &(_pud->items[index]); Object* item = inventoryItem->item; if (itemGetType(item) == ITEM_TYPE_CONTAINER) { _curr_stack += 1; _stack[_curr_stack] = item; _stack_offset[_curr_stack] = 0; _inven_dude = _stack[_curr_stack]; _pud = &(item->data.inventory); _adjust_fid(); _display_body(-1, inventoryWindowType); _display_inventory(_stack_offset[_curr_stack], -1, inventoryWindowType); } } } } // 0x476394 static void _container_exit(int keyCode, int inventoryWindowType) { if (keyCode == 2500) { if (_curr_stack > 0) { _curr_stack -= 1; _inven_dude = _stack[_curr_stack]; _pud = &_inven_dude->data.inventory; _adjust_fid(); _display_body(-1, inventoryWindowType); _display_inventory(_stack_offset[_curr_stack], -1, inventoryWindowType); } } else if (keyCode == 2501) { if (_target_curr_stack > 0) { _target_curr_stack -= 1; Object* target = _target_stack[_target_curr_stack]; _target_pud = &(target->data.inventory); _display_body(target->fid, inventoryWindowType); _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, inventoryWindowType); windowRefresh(gInventoryWindow); } } } // Drop item inside a container item (bag, backpack, etc.). // 0x476464 static int _drop_into_container(Object* container, Object* item, int sourceIndex, Object** itemSlot, int quantity) { if (!scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_CONTAINER, item, container)) { return -1; } int quantityToMove; if (quantity > 1) { quantityToMove = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, item, quantity); } else { quantityToMove = 1; } if (quantityToMove == -1) { return -1; } if (sourceIndex != -1) { if (itemRemove(_inven_dude, item, quantityToMove) == -1) { return -1; } } int rc = itemAttemptAdd(container, item, quantityToMove); if (rc != 0) { if (sourceIndex != -1) { // SFALL: Fix for items disappearing from inventory when you try to // drag them to bag/backpack in the inventory list and are // overloaded. itemAdd(_inven_dude, item, quantityToMove); } } else { if (itemSlot != nullptr) { if (itemSlot == &gInventoryArmor) { adjustCritterStatsOnArmorChange(_stack[0], gInventoryArmor, nullptr); } *itemSlot = nullptr; } } return rc; } // 0x47650C static InventoryAmmoMoveResult _drop_ammo_into_weapon(Object* weapon, Object* ammo, Object** ammoItemSlot, int quantity, int keyCode) { if (itemGetType(weapon) != ITEM_TYPE_WEAPON) { return INVENTORY_AMMO_MOVE_RESULT_FAILED; } if (itemGetType(ammo) != ITEM_TYPE_AMMO) { return INVENTORY_AMMO_MOVE_RESULT_FAILED; } if (!weaponCanBeReloadedWith(weapon, ammo)) { return INVENTORY_AMMO_MOVE_RESULT_FAILED; } if (!scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_WEAPON_RELOAD, ammo, weapon)) { return INVENTORY_AMMO_MOVE_RESULT_BLOCKED; } int quantityToMove; if (quantity > 1) { quantityToMove = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, ammo, quantity); } else { quantityToMove = 1; } if (quantityToMove == -1) { return INVENTORY_AMMO_MOVE_RESULT_FAILED; } Object* sourceItem = ammo; bool isReloaded = false; int rc = itemRemove(_inven_dude, weapon, 1); for (int index = 0; index < quantityToMove; index++) { int rcReload = weaponReload(weapon, sourceItem); if (rcReload == 0) { if (ammoItemSlot != nullptr) { *ammoItemSlot = nullptr; } objectDestroy(sourceItem); isReloaded = true; if (_inven_from_button(keyCode, &sourceItem, nullptr, nullptr) == 0) { break; } } if (rcReload != -1) { isReloaded = true; } if (rcReload != 0) { break; } } if (rc != -1) { itemAdd(_inven_dude, weapon, 1); } if (!isReloaded) { return INVENTORY_AMMO_MOVE_RESULT_FAILED; } const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_READY, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY, nullptr); soundPlayFile(sfx); return INVENTORY_AMMO_MOVE_RESULT_SUCCESS; } // 0x47664C static void _draw_amount(int value, int inventoryWindowType) { // BIGNUM.frm FrmImage numbersFrmImage; int numbersFid = buildFid(OBJ_TYPE_INTERFACE, 170, 0, 0, 0); if (!numbersFrmImage.lock(numbersFid)) { return; } Rect rect; int windowWidth = windowGetWidth(_mt_wid); unsigned char* windowBuffer = windowGetBuffer(_mt_wid); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { rect.left = 125; rect.top = 45; rect.right = 195; rect.bottom = 69; int ranks[5]; ranks[4] = value % 10; ranks[3] = value / 10 % 10; ranks[2] = value / 100 % 10; ranks[1] = value / 1000 % 10; ranks[0] = value / 10000 % 10; windowBuffer += rect.top * windowWidth + rect.left; for (int index = 0; index < 5; index++) { unsigned char* src = numbersFrmImage.getData() + 14 * ranks[index]; blitBufferToBuffer(src, 14, 24, 336, windowBuffer, windowWidth); windowBuffer += 14; } } else { rect.left = 133; rect.top = 64; rect.right = 189; rect.bottom = 88; windowBuffer += windowWidth * rect.top + rect.left; blitBufferToBuffer(numbersFrmImage.getData() + 14 * (value / 60), 14, 24, 336, windowBuffer, windowWidth); blitBufferToBuffer(numbersFrmImage.getData() + 14 * (value % 60 / 10), 14, 24, 336, windowBuffer + 14 * 2, windowWidth); blitBufferToBuffer(numbersFrmImage.getData() + 14 * (value % 10), 14, 24, 336, windowBuffer + 14 * 3, windowWidth); } windowRefreshRect(_mt_wid, &rect); } // 0x47688C static int inventoryQuantitySelect(int inventoryWindowType, Object* item, int max, int defaultValue) { ScopedGameMode gm(GameMode::kCounter); inventoryQuantityWindowInit(inventoryWindowType, item); int value; int min; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { if (max > 99999) { max = 99999; } min = 1; value = std::clamp(defaultValue, min, max); } else { value = 60; min = 10; } _draw_amount(value, inventoryWindowType); bool isTyping = false; for (;;) { sharedFpsLimiter.mark(); int keyCode = inputGetInput(); if (keyCode == KEY_ESCAPE) { inventoryQuantityWindowFree(inventoryWindowType); return -1; } if (keyCode == KEY_RETURN || keyCode == 500) { if (value >= min && value <= max) { if (inventoryWindowType != INVENTORY_WINDOW_TYPE_SET_TIMER || value % 10 == 0) { if (keyCode != 500) { soundPlayFile("ib1p1xx1"); } } } else { soundPlayFile("iisxxxx1"); } break; } else if (keyCode == 5000 || keyCode == KEY_LOWERCASE_A) { if (keyCode == KEY_LOWERCASE_A) { soundPlayFile("ib1p1xx1"); } isTyping = false; value = max; _draw_amount(value, inventoryWindowType); } else if (keyCode == 6000) { isTyping = false; if (value < max) { if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { if ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) { getTicks(); unsigned int delay = 100; while ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) { sharedFpsLimiter.mark(); if (value < max) { value++; } _draw_amount(value, inventoryWindowType); inputGetInput(); if (delay > 1) { delay--; inputPauseForTocks(delay); } renderPresent(); sharedFpsLimiter.throttle(); } } else { if (value < max) { value++; } } } else { value += 10; } _draw_amount(value, inventoryWindowType); continue; } } else if (keyCode == 7000) { isTyping = false; if (value > min) { if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { if ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) { getTicks(); unsigned int delay = 100; while ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) { sharedFpsLimiter.mark(); if (value > min) { value--; } _draw_amount(value, inventoryWindowType); inputGetInput(); if (delay > 1) { delay--; inputPauseForTocks(delay); } renderPresent(); sharedFpsLimiter.throttle(); } } else { if (value > min) { value--; } } } else { value -= 10; } _draw_amount(value, inventoryWindowType); continue; } } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { if (keyCode >= KEY_0 && keyCode <= KEY_9) { int number = keyCode - KEY_0; if (!isTyping) { value = 0; } value = 10 * value % 100000 + number; isTyping = true; _draw_amount(value, inventoryWindowType); continue; } else if (keyCode == KEY_BACKSPACE) { if (!isTyping) { value = 0; } value /= 10; isTyping = true; _draw_amount(value, inventoryWindowType); continue; } } renderPresent(); sharedFpsLimiter.throttle(); } inventoryQuantityWindowFree(inventoryWindowType); return value; } // Creates move items/set timer interface. // // 0x476AB8 static int inventoryQuantityWindowInit(int inventoryWindowType, Object* item) { const int oldFont = fontGetCurrent(); fontSetCurrent(103); const InventoryWindowDescription* windowDescription = &(gInventoryWindowDescriptions[inventoryWindowType]); // Maintain original position in original resolution, otherwise center it. int quantityWindowX = screenGetWidth() != 640 ? (screenGetWidth() - windowDescription->width) / 2 : windowDescription->x; int quantityWindowY = screenGetHeight() != 480 ? inventoryGetCenteredWindowY(windowDescription->height) : windowDescription->y; _mt_wid = windowCreate(quantityWindowX, quantityWindowY, windowDescription->width, windowDescription->height, 257, WINDOW_MODAL | WINDOW_MOVE_ON_TOP); unsigned char* windowBuffer = windowGetBuffer(_mt_wid); FrmImage backgroundFrmImage; int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, windowDescription->frmId, 0, 0, 0); if (backgroundFrmImage.lock(backgroundFid)) { blitBufferToBuffer(backgroundFrmImage.getData(), windowDescription->width, windowDescription->height, windowDescription->width, windowBuffer, windowDescription->width); } MessageListItem messageListItem; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { // MOVE ITEMS messageListItem.num = 21; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { int length = fontGetStringWidth(messageListItem.text); fontDrawText(windowBuffer + windowDescription->width * 9 + (windowDescription->width - length) / 2, messageListItem.text, 200, windowDescription->width, _colorTable[21091]); } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_SET_TIMER) { // SET TIMER messageListItem.num = 23; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { int length = fontGetStringWidth(messageListItem.text); fontDrawText(windowBuffer + windowDescription->width * 9 + (windowDescription->width - length) / 2, messageListItem.text, 200, windowDescription->width, _colorTable[21091]); } // Timer overlay FrmImage overlayFrmImage; int overlayFid = buildFid(OBJ_TYPE_INTERFACE, 306, 0, 0, 0); if (overlayFrmImage.lock(overlayFid)) { blitBufferToBuffer(overlayFrmImage.getData(), 105, 81, 105, windowBuffer + 34 * windowDescription->width + 113, windowDescription->width); } } int inventoryFid = itemGetInventoryFid(item); artRender(inventoryFid, windowBuffer + windowDescription->width * 46 + 16, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, windowDescription->width); int x; int y; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { x = 200; y = 46; } else { x = 194; y = 64; } int fid; int btn; // Plus button fid = buildFid(OBJ_TYPE_INTERFACE, 193, 0, 0, 0); _moveFrmImages[0].lock(fid); fid = buildFid(OBJ_TYPE_INTERFACE, 194, 0, 0, 0); _moveFrmImages[1].lock(fid); if (_moveFrmImages[0].isLocked() && _moveFrmImages[1].isLocked()) { btn = buttonCreate(_mt_wid, x, y, 16, 12, -1, -1, 6000, -1, _moveFrmImages[0].getData(), _moveFrmImages[1].getData(), nullptr, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } } // Minus button fid = buildFid(OBJ_TYPE_INTERFACE, 191, 0, 0, 0); _moveFrmImages[2].lock(fid); fid = buildFid(OBJ_TYPE_INTERFACE, 192, 0, 0, 0); _moveFrmImages[3].lock(fid); if (_moveFrmImages[2].isLocked() && _moveFrmImages[3].isLocked()) { btn = buttonCreate(_mt_wid, x, y + 12, 17, 12, -1, -1, 7000, -1, _moveFrmImages[2].getData(), _moveFrmImages[3].getData(), nullptr, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } } fid = buildFid(OBJ_TYPE_INTERFACE, 8, 0, 0, 0); _moveFrmImages[4].lock(fid); fid = buildFid(OBJ_TYPE_INTERFACE, 9, 0, 0, 0); _moveFrmImages[5].lock(fid); if (_moveFrmImages[4].isLocked() && _moveFrmImages[5].isLocked()) { // Done btn = buttonCreate(_mt_wid, 98, 128, 15, 16, -1, -1, -1, 500, _moveFrmImages[4].getData(), _moveFrmImages[5].getData(), nullptr, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } // Cancel btn = buttonCreate(_mt_wid, 148, 128, 15, 16, -1, -1, -1, KEY_ESCAPE, _moveFrmImages[4].getData(), _moveFrmImages[5].getData(), nullptr, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { fid = buildFid(OBJ_TYPE_INTERFACE, 307, 0, 0, 0); _moveFrmImages[6].lock(fid); fid = buildFid(OBJ_TYPE_INTERFACE, 308, 0, 0, 0); _moveFrmImages[7].lock(fid); if (_moveFrmImages[6].isLocked() && _moveFrmImages[7].isLocked()) { // ALL messageListItem.num = 22; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { int length = fontGetStringWidth(messageListItem.text); // TODO: Where is y? Is it hardcoded in to 376? fontDrawText(_moveFrmImages[6].getData() + (94 - length) / 2 + 376, messageListItem.text, 200, 94, _colorTable[21091]); fontDrawText(_moveFrmImages[7].getData() + (94 - length) / 2 + 376, messageListItem.text, 200, 94, _colorTable[18977]); btn = buttonCreate(_mt_wid, 120, 80, 94, 33, -1, -1, -1, 5000, _moveFrmImages[6].getData(), _moveFrmImages[7].getData(), nullptr, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { buttonSetCallbacks(btn, _gsound_red_butt_press, _gsound_red_butt_release); } } } } windowRefresh(_mt_wid); inventorySetCursor(INVENTORY_WINDOW_CURSOR_ARROW); fontSetCurrent(oldFont); return 0; } // 0x477030 static int inventoryQuantityWindowFree(int inventoryWindowType) { int count = inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS ? 8 : 6; for (int index = 0; index < count; index++) { _moveFrmImages[index].unlock(); } windowDestroy(_mt_wid); return 0; } // 0x477074 int inventorySetTimer(Object* item) { bool isInitialized = _inven_is_initialized; if (!isInitialized) { if (inventoryCommonInit() == -1) { return -1; } } int seconds = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_SET_TIMER, item, 180); if (!isInitialized) { // NOTE: Uninline. inventoryCommonFree(); } return seconds; } Object* inventoryGetTargetObject() { return _target_stack[_target_curr_stack]; } int inventoryUnwieldSlot(Object* critter, InvenSlot slot) { if (critter == nullptr) { return -1; } bool isDude = critter == gDude; bool uiModeActive = GameMode::isInGameMode(GameMode::kInventory) || GameMode::isInGameMode(GameMode::kUseOn) || GameMode::isInGameMode(GameMode::kLoot) || GameMode::isInGameMode(GameMode::kBarter); bool update = false; if (slot != InvenSlot::Armor && !uiModeActive) { int rc = inventoryUnequipFunc(critter, slot == InvenSlot::LeftHand ? HAND_LEFT : HAND_RIGHT, false); if (rc == 0) { update = isDude; } if (update) { interfaceUpdateItems(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } return rc; } bool forceAdd = false; Object* item = nullptr; Object** itemSlot = nullptr; if (slot != InvenSlot::Armor) { if (!isDude || critter != _inven_dude) { return 0; } if (slot == InvenSlot::RightHand) { item = gInventoryRightHandItem; itemSlot = &gInventoryRightHandItem; } else { item = gInventoryLeftHandItem; itemSlot = &gInventoryLeftHandItem; } if (item != nullptr) { if (!scriptHooks_InvenWield(critter, item, slot, 0, 0, false)) { return -1; } int oldFlags = item->flags; bool clearBothHandSlots = gInventoryLeftHandItem == item && gInventoryRightHandItem == item; if (clearBothHandSlots) { gInventoryLeftHandItem = nullptr; gInventoryRightHandItem = nullptr; } else { *itemSlot = nullptr; } item->flags &= ~OBJECT_IN_ANY_HAND; if (itemAdd(critter, item, 1) != 0) { // item will be in an invalid state, but this can only happen on malloc failure return -1; } update = true; } } else { if (isDude && critter == _inven_dude) { item = gInventoryArmor; } if (item == nullptr) { item = critterGetArmor(critter); } else { gInventoryArmor = nullptr; forceAdd = true; } if (item != nullptr) { item->flags &= ~OBJECT_WORN; if (correctFidForRemovedItem(critter, item, OBJECT_WORN) != 0) { if (forceAdd) { gInventoryArmor = item; item->flags |= OBJECT_WORN; } return -1; } if (forceAdd && itemAdd(critter, item, 1) != 0) { // improper cleanup, but this can only happen on malloc failure return -1; } if (isDude) { interfaceRenderArmorClass(false); } } } if (!uiModeActive) { return 0; } InventoryWindowType inventoryWindowType = INVENTORY_WINDOW_TYPE_NORMAL; if (GameMode::isInGameMode(GameMode::kBarter)) { inventoryWindowType = INVENTORY_WINDOW_TYPE_TRADE; } else if (GameMode::isInGameMode(GameMode::kLoot)) { inventoryWindowType = INVENTORY_WINDOW_TYPE_LOOT; } else if (GameMode::isInGameMode(GameMode::kUseOn)) { inventoryWindowType = INVENTORY_WINDOW_TYPE_USE_ITEM_ON; } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { inventoryRenderSummary(); } int targetFid = -1; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT || inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { targetFid = _target_stack[_target_curr_stack]->fid; _display_target_inventory(_target_stack_offset[_target_curr_stack], -1, _target_pud, inventoryWindowType); } _adjust_fid(); _display_inventory(_stack_offset[_curr_stack], -1, inventoryWindowType); _display_body(targetFid, inventoryWindowType); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { barterDisplayTables(gInventoryBarterBackgroundWindow, gPlayerTableObj, gBartererTableObj, -1); } if (update) { interfaceUpdateItems(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } return 0; } } // namespace fallout