/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "ags/engine/ac/inv_window.h" #include "ags/shared/ac/common.h" #include "ags/engine/ac/character_extras.h" #include "ags/shared/ac/character_info.h" #include "ags/engine/ac/draw.h" #include "ags/engine/ac/event.h" #include "ags/engine/ac/game_state.h" #include "ags/shared/ac/game_setup_struct.h" #include "ags/engine/ac/global_character.h" #include "ags/engine/ac/global_display.h" #include "ags/engine/ac/global_inventory_item.h" #include "ags/engine/ac/global_room.h" #include "ags/engine/ac/mouse.h" #include "ags/shared/ac/sprite_cache.h" #include "ags/engine/ac/sys_events.h" #include "ags/engine/ac/timer.h" #include "ags/engine/ac/dynobj/cc_character.h" #include "ags/engine/ac/dynobj/cc_inventory.h" #include "ags/engine/debugging/debug_log.h" #include "ags/engine/gui/gui_dialog.h" #include "ags/shared/gui/gui_main.h" #include "ags/engine/main/game_run.h" #include "ags/engine/media/audio/audio_system.h" #include "ags/engine/platform/base/ags_platform_driver.h" #include "ags/engine/script/runtime_script_value.h" #include "ags/shared/util/math.h" #include "ags/shared/util/wgt2_allg.h" #include "ags/shared/debugging/out.h" #include "ags/engine/script/script_api.h" #include "ags/engine/script/script_runtime.h" #include "ags/globals.h" namespace AGS3 { using namespace AGS::Shared; // *** INV WINDOW FUNCTIONS void InvWindow_SetCharacterToUse(GUIInvWindow *guii, CharacterInfo *chaa) { if (chaa == nullptr) guii->CharId = -1; else guii->CharId = chaa->index_id; // reset to top of list guii->TopItem = 0; guii->MarkChanged(); } CharacterInfo *InvWindow_GetCharacterToUse(GUIInvWindow *guii) { if (guii->CharId < 0) return nullptr; return &_GP(game).chars[guii->CharId]; } void InvWindow_SetItemWidth(GUIInvWindow *guii, int newwidth) { guii->ItemWidth = newwidth; guii->OnResized(); } int InvWindow_GetItemWidth(GUIInvWindow *guii) { return guii->ItemWidth; } void InvWindow_SetItemHeight(GUIInvWindow *guii, int newhit) { guii->ItemHeight = newhit; guii->OnResized(); } int InvWindow_GetItemHeight(GUIInvWindow *guii) { return guii->ItemHeight; } void InvWindow_SetTopItem(GUIInvWindow *guii, int topitem) { if (guii->TopItem != topitem) { guii->TopItem = topitem; guii->MarkChanged(); } } int InvWindow_GetTopItem(GUIInvWindow *guii) { return guii->TopItem; } int InvWindow_GetItemsPerRow(GUIInvWindow *guii) { return guii->ColCount; } int InvWindow_GetItemCount(GUIInvWindow *guii) { return _GP(charextra)[guii->GetCharacterId()].invorder_count; } int InvWindow_GetRowCount(GUIInvWindow *guii) { return guii->RowCount; } void InvWindow_ScrollDown(GUIInvWindow *guii) { if ((_GP(charextra)[guii->GetCharacterId()].invorder_count) > (guii->TopItem + (guii->ColCount * guii->RowCount))) { guii->TopItem += guii->ColCount; guii->MarkChanged(); } } void InvWindow_ScrollUp(GUIInvWindow *guii) { if (guii->TopItem > 0) { guii->TopItem -= guii->ColCount; if (guii->TopItem < 0) guii->TopItem = 0; guii->MarkChanged(); } } ScriptInvItem *InvWindow_GetItemAtIndex(GUIInvWindow *guii, int index) { if ((index < 0) || (index >= _GP(charextra)[guii->GetCharacterId()].invorder_count)) return nullptr; return &_G(scrInv)[_GP(charextra)[guii->GetCharacterId()].invorder[index]]; } //============================================================================= int offset_over_inv(GUIInvWindow *inv) { if (inv->ItemWidth <= 0 || inv->ItemHeight <= 0) return -1; int mover = _G(mouse_ifacebut_xoffs) / data_to_game_coord(inv->ItemWidth); // if it's off the edge of the visible items, ignore if (mover >= inv->ColCount) return -1; mover += (_G(mouse_ifacebut_yoffs) / data_to_game_coord(inv->ItemHeight)) * inv->ColCount; if (mover >= inv->ColCount * inv->RowCount) return -1; mover += inv->TopItem; if ((mover < 0) || (mover >= _GP(charextra)[inv->GetCharacterId()].invorder_count)) return -1; return _GP(charextra)[inv->GetCharacterId()].invorder[mover]; } // // NOTE: This is an old default inventory screen implementation, // which became completely obsolete after AGS 2.72. // #define ICONSPERLINE 4 struct DisplayInvItem { int num; int sprnum; }; struct InventoryScreen { static const int ARROWBUTTONWID = 11; int BUTTONAREAHEIGHT; int cmode; int toret; int top_item; int num_visible_items; int MAX_ITEMAREA_HEIGHT; int wasonitem; int bartop; int barxp; int numitems; int widest; int highest; int windowwid; int windowhit; int windowxp; int windowyp; int buttonyp; DisplayInvItem dii[MAX_INV]; int btn_look_sprite; int btn_select_sprite; int btn_ok_sprite; int break_code; void Prepare(); int Redraw(); void Draw(Bitmap *ds); void RedrawOverItem(Bitmap *ds, int isonitem); bool Run(); void Close(); }; InventoryScreen InvScr; void InventoryScreen::Prepare() { BUTTONAREAHEIGHT = get_fixed_pixel_size(30); cmode = CURS_ARROW; toret = -1; top_item = 0; num_visible_items = 0; MAX_ITEMAREA_HEIGHT = ((_GP(play).GetUIViewport().GetHeight() - BUTTONAREAHEIGHT) - get_fixed_pixel_size(20)); _G(in_inv_screen)++; _G(inv_screen_newroom) = -1; // Sprites 2041, 2042 and 2043 were hardcoded in the older versions of // the engine to be used in the built-in inventory window. // If they did not exist engine first fell back to sprites 0, 1, 2 instead. // Fun fact: this fallback does not seem to be intentional, and was a // coincidental result of SpriteCache incorrectly remembering "last seek'd // sprite" as 2041/2042/2043 while in fact stream was after sprite 0. if (!_GP(spriteset).DoesSpriteExist(2041) || !_GP(spriteset).DoesSpriteExist(2042) || !_GP(spriteset).DoesSpriteExist(2043)) { debug_script_warn("InventoryScreen: one or more of the inventory screen graphics (sprites 2041, 2042, 2043) does not exist, fallback to sprites 0, 1, 2 instead"); } btn_look_sprite = _GP(spriteset).DoesSpriteExist(2041) ? 2041 : 0; btn_select_sprite = _GP(spriteset).DoesSpriteExist(2042) ? 2042 : (_GP(spriteset).DoesSpriteExist(1) ? 1 : 0); btn_ok_sprite = _GP(spriteset).DoesSpriteExist(2043) ? 2043 : (_GP(spriteset).DoesSpriteExist(2) ? 2 : 0); break_code = 0; } int InventoryScreen::Redraw() { numitems = 0; widest = 0; highest = 0; if (_GP(charextra)[_GP(game).playercharacter].invorder_count < 0) update_invorder(); if (_GP(charextra)[_GP(game).playercharacter].invorder_count == 0) { DisplayMessage(996); _G(in_inv_screen)--; return -1; } if (_G(inv_screen_newroom) >= 0) { _G(in_inv_screen)--; NewRoom(_G(inv_screen_newroom)); return -1; } for (int i = 0; i < _GP(charextra)[_GP(game).playercharacter].invorder_count; ++i) { if (!_GP(game).invinfo[_GP(charextra)[_GP(game).playercharacter].invorder[i]].name.IsEmpty()) { dii[numitems].num = _GP(charextra)[_GP(game).playercharacter].invorder[i]; dii[numitems].sprnum = _GP(game).invinfo[_GP(charextra)[_GP(game).playercharacter].invorder[i]].pic; int snn = dii[numitems].sprnum; if (_GP(game).SpriteInfos[snn].Width > widest) widest = _GP(game).SpriteInfos[snn].Width; if (_GP(game).SpriteInfos[snn].Height > highest) highest = _GP(game).SpriteInfos[snn].Height; numitems++; } } if (numitems != _GP(charextra)[_GP(game).playercharacter].invorder_count) quit("inconsistent inventory calculations"); widest += get_fixed_pixel_size(4); highest += get_fixed_pixel_size(4); num_visible_items = (MAX_ITEMAREA_HEIGHT / highest) * ICONSPERLINE; windowhit = highest * (numitems / ICONSPERLINE) + get_fixed_pixel_size(4); if ((numitems % ICONSPERLINE) != 0) windowhit += highest; if (windowhit > MAX_ITEMAREA_HEIGHT) { windowhit = (MAX_ITEMAREA_HEIGHT / highest) * highest + get_fixed_pixel_size(4); } windowhit += BUTTONAREAHEIGHT; windowwid = widest * ICONSPERLINE + get_fixed_pixel_size(4); if (windowwid < get_fixed_pixel_size(105)) windowwid = get_fixed_pixel_size(105); windowxp = _GP(play).GetUIViewport().GetWidth() / 2 - windowwid / 2; windowyp = _GP(play).GetUIViewport().GetHeight() / 2 - windowhit / 2; buttonyp = windowhit - BUTTONAREAHEIGHT; bartop = get_fixed_pixel_size(2); barxp = get_fixed_pixel_size(2); Bitmap *ds = prepare_gui_screen(windowxp, windowyp, windowwid, windowhit, true); Draw(ds); set_mouse_cursor(cmode); wasonitem = -1; return 0; } void InventoryScreen::Draw(Bitmap *ds) { color_t draw_color = ds->GetCompatibleColor(_GP(play).sierra_inv_color); ds->FillRect(Rect(0, 0, windowwid, windowhit), draw_color); draw_color = ds->GetCompatibleColor(0); ds->FillRect(Rect(barxp, bartop, windowwid - get_fixed_pixel_size(2), buttonyp - 1), draw_color); for (int i = top_item; i < numitems; ++i) { if (i >= top_item + num_visible_items) break; Bitmap *spof = _GP(spriteset)[dii[i].sprnum]; wputblock(ds, barxp + 1 + ((i - top_item) % 4) * widest + widest / 2 - spof->GetWidth() / 2, bartop + 1 + ((i - top_item) / 4) * highest + highest / 2 - spof->GetHeight() / 2, spof, 1); } #define BUTTONWID MAX(1, _GP(game).SpriteInfos[btn_select_sprite].Width) // Draw select, look and OK buttons wputblock(ds, 2, buttonyp + get_fixed_pixel_size(2), _GP(spriteset)[btn_look_sprite], 1); wputblock(ds, 3 + BUTTONWID, buttonyp + get_fixed_pixel_size(2), _GP(spriteset)[btn_select_sprite], 1); wputblock(ds, 4 + BUTTONWID * 2, buttonyp + get_fixed_pixel_size(2), _GP(spriteset)[btn_ok_sprite], 1); // Draw Up and Down buttons if required Bitmap *arrowblock = BitmapHelper::CreateTransparentBitmap(ARROWBUTTONWID, ARROWBUTTONWID); draw_color = arrowblock->GetCompatibleColor(0); if (_GP(play).sierra_inv_color == 0) draw_color = ds->GetCompatibleColor(14); arrowblock->DrawLine(Line(ARROWBUTTONWID / 2, 2, ARROWBUTTONWID - 2, 9), draw_color); arrowblock->DrawLine(Line(ARROWBUTTONWID / 2, 2, 2, 9), draw_color); arrowblock->DrawLine(Line(2, 9, ARROWBUTTONWID - 2, 9), draw_color); arrowblock->FloodFill(ARROWBUTTONWID / 2, 4, draw_color); if (top_item > 0) wputblock(ds, windowwid - ARROWBUTTONWID, buttonyp + get_fixed_pixel_size(2), arrowblock, 1); if (top_item + num_visible_items < numitems) arrowblock->FlipBlt(arrowblock, windowwid - ARROWBUTTONWID, buttonyp + get_fixed_pixel_size(4) + ARROWBUTTONWID, Shared::kFlip_Vertical); delete arrowblock; } void InventoryScreen::RedrawOverItem(Bitmap *ds, int isonitem) { int rectxp = barxp + 1 + (wasonitem % 4) * widest; int rectyp = bartop + 1 + ((wasonitem - top_item) / 4) * highest; if (wasonitem >= 0) { color_t draw_color = ds->GetCompatibleColor(0); ds->DrawRect(Rect(rectxp, rectyp, rectxp + widest - 1, rectyp + highest - 1), draw_color); } if (isonitem >= 0) { color_t draw_color = ds->GetCompatibleColor(14);//opts.invrectcol); rectxp = barxp + 1 + (isonitem % 4) * widest; rectyp = bartop + 1 + ((isonitem - top_item) / 4) * highest; ds->DrawRect(Rect(rectxp, rectyp, rectxp + widest - 1, rectyp + highest - 1), draw_color); } } bool InventoryScreen::Run() { // Run() can be called in a loop, so keep events going. sys_evt_process_pending(); // Handle all the buffered key events bool do_break = false; while (ags_keyevent_ready()) { KeyInput ki; if (run_service_key_controls(ki) && !_GP(play).IsIgnoringInput() && !IsAGSServiceKey(ki.Key)) { ags_clear_input_buffer(); do_break = true; // end inventory screen loop } } if (do_break) return false; update_audio_system_on_game_loop(); refresh_gui_screen(); // NOTE: this is because old code was working with full game screen const int mx = _G(mousex) - windowxp; const int my = _G(mousey) - windowyp; int isonitem = ((my - bartop) / highest) * ICONSPERLINE + (mx - barxp) / widest; if (my <= bartop) isonitem = -1; else if (isonitem >= 0) isonitem += top_item; if ((isonitem < 0) || (isonitem >= numitems) || (isonitem >= top_item + num_visible_items)) isonitem = -1; eAGSMouseButton mbut; int mwheelz; if (!run_service_mb_controls(mbut, mwheelz) || _GP(play).IsIgnoringInput()) { mbut = kMouseNone; } if (mbut == kMouseLeft) { if ((my < 0) || (my > windowhit) || (mx < 0) || (mx > windowwid)) return true; // continue inventory screen loop if (my < buttonyp) { int clickedon = isonitem; if (clickedon < 0) return true; // continue inventory screen loop _GP(play).used_inv_on = dii[clickedon].num; if (cmode == MODE_LOOK) { RunInventoryInteraction(dii[clickedon].num, MODE_LOOK); // in case the script did anything to the screen, redraw it UpdateGameOnce(); break_code = Redraw(); return break_code == 0; } else if (cmode == MODE_USE) { // use objects on each other _GP(play).usedinv = toret; // set the activeinv so the script can check it int activeinvwas = _G(playerchar)->activeinv; _G(playerchar)->activeinv = toret; RunInventoryInteraction(dii[clickedon].num, MODE_USE); // if the script didn't change it, then put it back if (_G(playerchar)->activeinv == toret) _G(playerchar)->activeinv = activeinvwas; // in case the script did anything to the screen, redraw it UpdateGameOnce(); // They used the active item and lost it if (_G(playerchar)->inv[toret] < 1) { cmode = CURS_ARROW; set_mouse_cursor(cmode); toret = -1; } break_code = Redraw(); return break_code == 0; } toret = dii[clickedon].num; // int plusng=_GP(play).using; _GP(play).using=toret; update_inv_cursor(toret); set_mouse_cursor(MODE_USE); cmode = MODE_USE; // _GP(play).using=plusng; // break; return true; // continue inventory screen loop } else { if (mx >= windowwid - ARROWBUTTONWID) { if (my < buttonyp + get_fixed_pixel_size(2) + ARROWBUTTONWID) { if (top_item > 0) { top_item -= ICONSPERLINE; break_code = Redraw(); return break_code == 0; } } else if ((my < buttonyp + get_fixed_pixel_size(4) + ARROWBUTTONWID * 2) && (top_item + num_visible_items < numitems)) { top_item += ICONSPERLINE; break_code = Redraw(); return break_code == 0; } return true; // continue inventory screen loop } int buton = mx - 2; if (buton < 0) return true; // continue inventory screen loop buton /= BUTTONWID; if (buton >= 3) return true; // continue inventory screen loop if (buton == 0) { toret = -1; cmode = MODE_LOOK; } else if (buton == 1) { cmode = CURS_ARROW; toret = -1; } else { return false; // end inventory screen loop } set_mouse_cursor(cmode); } } else if (mbut == kMouseRight) { if (cmode == CURS_ARROW) cmode = MODE_LOOK; else cmode = CURS_ARROW; toret = -1; set_mouse_cursor(cmode); } else if (isonitem != wasonitem) { RedrawOverItem(get_gui_screen(), isonitem); } wasonitem = isonitem; update_polled_stuff(); WaitForNextFrame(); return true; // continue inventory screen loop } void InventoryScreen::Close() { clear_gui_screen(); set_default_cursor(); invalidate_screen(); _G(in_inv_screen)--; } int __actual_invscreen() { InvScr.Prepare(); InvScr.break_code = InvScr.Redraw(); if (InvScr.break_code != 0) { return InvScr.break_code; } while (InvScr.Run()); if (InvScr.break_code != 0) { return InvScr.break_code; } ags_clear_input_buffer(); InvScr.Close(); return InvScr.toret; } int invscreen() { int selt = __actual_invscreen(); if (selt < 0) return -1; _G(playerchar)->activeinv = selt; GUI::MarkInventoryForUpdate(_G(playerchar)->index_id, true); set_cursor_mode(MODE_USE); return selt; } //============================================================================= // // Script API Functions // //============================================================================= // void (GUIInvWindow *guii) RuntimeScriptValue Sc_InvWindow_ScrollDown(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_VOID(GUIInvWindow, InvWindow_ScrollDown); } // void (GUIInvWindow *guii) RuntimeScriptValue Sc_InvWindow_ScrollUp(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_VOID(GUIInvWindow, InvWindow_ScrollUp); } // CharacterInfo* (GUIInvWindow *guii) RuntimeScriptValue Sc_InvWindow_GetCharacterToUse(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_OBJ(GUIInvWindow, CharacterInfo, _GP(ccDynamicCharacter), InvWindow_GetCharacterToUse); } // void (GUIInvWindow *guii, CharacterInfo *chaa) RuntimeScriptValue Sc_InvWindow_SetCharacterToUse(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_VOID_POBJ(GUIInvWindow, InvWindow_SetCharacterToUse, CharacterInfo); } // ScriptInvItem* (GUIInvWindow *guii, int index) RuntimeScriptValue Sc_InvWindow_GetItemAtIndex(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_OBJ_PINT(GUIInvWindow, ScriptInvItem, _GP(ccDynamicInv), InvWindow_GetItemAtIndex); } // int (GUIInvWindow *guii) RuntimeScriptValue Sc_InvWindow_GetItemCount(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_INT(GUIInvWindow, InvWindow_GetItemCount); } // int (GUIInvWindow *guii) RuntimeScriptValue Sc_InvWindow_GetItemHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_INT(GUIInvWindow, InvWindow_GetItemHeight); } // void (GUIInvWindow *guii, int newhit) RuntimeScriptValue Sc_InvWindow_SetItemHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_VOID_PINT(GUIInvWindow, InvWindow_SetItemHeight); } // int (GUIInvWindow *guii) RuntimeScriptValue Sc_InvWindow_GetItemWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_INT(GUIInvWindow, InvWindow_GetItemWidth); } // void (GUIInvWindow *guii, int newwidth) RuntimeScriptValue Sc_InvWindow_SetItemWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_VOID_PINT(GUIInvWindow, InvWindow_SetItemWidth); } // int (GUIInvWindow *guii) RuntimeScriptValue Sc_InvWindow_GetItemsPerRow(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_INT(GUIInvWindow, InvWindow_GetItemsPerRow); } // int (GUIInvWindow *guii) RuntimeScriptValue Sc_InvWindow_GetRowCount(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_INT(GUIInvWindow, InvWindow_GetRowCount); } // int (GUIInvWindow *guii) RuntimeScriptValue Sc_InvWindow_GetTopItem(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_INT(GUIInvWindow, InvWindow_GetTopItem); } // void (GUIInvWindow *guii, int topitem) RuntimeScriptValue Sc_InvWindow_SetTopItem(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_VOID_PINT(GUIInvWindow, InvWindow_SetTopItem); } void RegisterInventoryWindowAPI() { ScFnRegister invwindow_api[] = { {"InvWindow::ScrollDown^0", API_FN_PAIR(InvWindow_ScrollDown)}, {"InvWindow::ScrollUp^0", API_FN_PAIR(InvWindow_ScrollUp)}, {"InvWindow::get_CharacterToUse", API_FN_PAIR(InvWindow_GetCharacterToUse)}, {"InvWindow::set_CharacterToUse", API_FN_PAIR(InvWindow_SetCharacterToUse)}, {"InvWindow::geti_ItemAtIndex", API_FN_PAIR(InvWindow_GetItemAtIndex)}, {"InvWindow::get_ItemCount", API_FN_PAIR(InvWindow_GetItemCount)}, {"InvWindow::get_ItemHeight", API_FN_PAIR(InvWindow_GetItemHeight)}, {"InvWindow::set_ItemHeight", API_FN_PAIR(InvWindow_SetItemHeight)}, {"InvWindow::get_ItemWidth", API_FN_PAIR(InvWindow_GetItemWidth)}, {"InvWindow::set_ItemWidth", API_FN_PAIR(InvWindow_SetItemWidth)}, {"InvWindow::get_ItemsPerRow", API_FN_PAIR(InvWindow_GetItemsPerRow)}, {"InvWindow::get_RowCount", API_FN_PAIR(InvWindow_GetRowCount)}, {"InvWindow::get_TopItem", API_FN_PAIR(InvWindow_GetTopItem)}, {"InvWindow::set_TopItem", API_FN_PAIR(InvWindow_SetTopItem)}, }; ccAddExternalFunctions361(invwindow_api); } } // namespace AGS3