From 00e8d8d75b89de6a706e1fc44f02d5aa733fc991 Mon Sep 17 00:00:00 2001 From: KPhoenix Date: Fri, 25 Aug 2023 17:03:12 -0400 Subject: [PATCH 01/22] Add Save Game confirmation --- Source/error.cpp | 2 +- Source/error.h | 2 +- Source/gamemenu.cpp | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/error.cpp b/Source/error.cpp index 451fb953e..899b38984 100644 --- a/Source/error.cpp +++ b/Source/error.cpp @@ -51,7 +51,7 @@ void InitNextLines() /** Maps from error_id to error message. */ const char *const MsgStrings[] = { "", - N_("No automap available in town"), + N_("Game saved"), N_("No multiplayer functions in demo"), N_("Direct Sound Creation Failed"), N_("Not available in shareware version"), diff --git a/Source/error.h b/Source/error.h index 57b66b936..6e10e011e 100644 --- a/Source/error.h +++ b/Source/error.h @@ -15,7 +15,7 @@ namespace devilution { enum diablo_message : uint8_t { EMSG_NONE, - EMSG_NO_AUTOMAP_IN_TOWN, + EMSG_GAME_SAVED, EMSG_NO_MULTIPLAYER_IN_DEMO, EMSG_DIRECT_SOUND_FAILED, EMSG_NOT_IN_SHAREWARE, diff --git a/Source/gamemenu.cpp b/Source/gamemenu.cpp index 89fe16bb2..bcd3d878e 100644 --- a/Source/gamemenu.cpp +++ b/Source/gamemenu.cpp @@ -332,6 +332,7 @@ void gamemenu_save_game(bool /*bActivate*/) DrawAndBlit(); SaveGame(); ClrDiabloMsg(); + InitDiabloMsg(EMSG_GAME_SAVED); RedrawEverything(); NewCursor(CURSOR_HAND); if (CornerStone.activated) { From 2a393397a136b308c3ba9d12042a2156e32399ef Mon Sep 17 00:00:00 2001 From: Eric Robinson <68359262+kphoenix137@users.noreply.github.com> Date: Fri, 25 Aug 2023 19:27:48 -0400 Subject: [PATCH 02/22] Fix cursor jitter when clicking inv items (#6510) --- Source/cursor.cpp | 5 +++-- Source/inv.cpp | 10 ++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Source/cursor.cpp b/Source/cursor.cpp index c5af4f927..6f20233e9 100644 --- a/Source/cursor.cpp +++ b/Source/cursor.cpp @@ -297,8 +297,9 @@ void DrawSoftwareCursor(const Surface &out, Point position, int cursId) const ClxSprite sprite = GetInvItemSprite(cursId); if (!MyPlayer->HoldItem.isEmpty()) { const auto &heldItem = MyPlayer->HoldItem; - ClxDrawOutline(out, GetOutlineColor(heldItem, true), position, sprite); - DrawItem(heldItem, out, position, sprite); + Size cursSize = GetInvItemSize(cursId); + ClxDrawOutline(out, GetOutlineColor(heldItem, true), position - Displacement(cursSize / 2), sprite); + DrawItem(heldItem, out, position - Displacement(cursSize / 2), sprite); } else { ClxDraw(out, position, sprite); } diff --git a/Source/inv.cpp b/Source/inv.cpp index 8350fc7aa..3a52b2366 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -320,8 +320,9 @@ int FindSlotUnderCursor(Point cursorPosition, Size itemSize) void CheckInvPaste(Player &player, Point cursorPosition) { Size itemSize = GetInventorySize(player.HoldItem); + Size cursSize = GetInvItemSize(player.HoldItem._iCurs); - int slot = FindSlotUnderCursor(cursorPosition, itemSize); + int slot = FindSlotUnderCursor(cursorPosition - Displacement(cursSize / 2), itemSize); if (slot == NUM_XY_SLOTS) return; @@ -554,8 +555,6 @@ void CheckInvPaste(Player &player, Point cursorPosition) } CalcPlrInv(player, true); if (&player == MyPlayer) { - if (player.HoldItem.isEmpty() && !IsHardwareCursor()) - SetCursorPos(MousePosition + Displacement { itemSize * INV_SLOT_HALF_SIZE_PX }); NewCursor(player.HoldItem); } } @@ -819,11 +818,6 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool holdItem.clear(); } else { NewCursor(holdItem); - if (!IsHardwareCursor() && !dropItem) { - // For a hardware cursor, we set the "hot point" to the center of the item instead. - Size cursSize = GetInvItemSize(holdItem._iCurs + CURSOR_FIRSTITEM); - SetCursorPos(cursorPosition - Displacement(cursSize / 2)); - } } } } From c91e69384a3a608d59febf064313eab78221b228 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Sat, 26 Aug 2023 04:00:38 +0200 Subject: [PATCH 03/22] Revert "Fix cursor jitter when clicking inv items (#6510)" This reverts commit 2a393397a136b308c3ba9d12042a2156e32399ef. --- Source/cursor.cpp | 5 ++--- Source/inv.cpp | 10 ++++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Source/cursor.cpp b/Source/cursor.cpp index 6f20233e9..c5af4f927 100644 --- a/Source/cursor.cpp +++ b/Source/cursor.cpp @@ -297,9 +297,8 @@ void DrawSoftwareCursor(const Surface &out, Point position, int cursId) const ClxSprite sprite = GetInvItemSprite(cursId); if (!MyPlayer->HoldItem.isEmpty()) { const auto &heldItem = MyPlayer->HoldItem; - Size cursSize = GetInvItemSize(cursId); - ClxDrawOutline(out, GetOutlineColor(heldItem, true), position - Displacement(cursSize / 2), sprite); - DrawItem(heldItem, out, position - Displacement(cursSize / 2), sprite); + ClxDrawOutline(out, GetOutlineColor(heldItem, true), position, sprite); + DrawItem(heldItem, out, position, sprite); } else { ClxDraw(out, position, sprite); } diff --git a/Source/inv.cpp b/Source/inv.cpp index 3a52b2366..8350fc7aa 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -320,9 +320,8 @@ int FindSlotUnderCursor(Point cursorPosition, Size itemSize) void CheckInvPaste(Player &player, Point cursorPosition) { Size itemSize = GetInventorySize(player.HoldItem); - Size cursSize = GetInvItemSize(player.HoldItem._iCurs); - int slot = FindSlotUnderCursor(cursorPosition - Displacement(cursSize / 2), itemSize); + int slot = FindSlotUnderCursor(cursorPosition, itemSize); if (slot == NUM_XY_SLOTS) return; @@ -555,6 +554,8 @@ void CheckInvPaste(Player &player, Point cursorPosition) } CalcPlrInv(player, true); if (&player == MyPlayer) { + if (player.HoldItem.isEmpty() && !IsHardwareCursor()) + SetCursorPos(MousePosition + Displacement { itemSize * INV_SLOT_HALF_SIZE_PX }); NewCursor(player.HoldItem); } } @@ -818,6 +819,11 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool holdItem.clear(); } else { NewCursor(holdItem); + if (!IsHardwareCursor() && !dropItem) { + // For a hardware cursor, we set the "hot point" to the center of the item instead. + Size cursSize = GetInvItemSize(holdItem._iCurs + CURSOR_FIRSTITEM); + SetCursorPos(cursorPosition - Displacement(cursSize / 2)); + } } } } From 45dbe6aa611810be103e14e503fb84b59915fa33 Mon Sep 17 00:00:00 2001 From: "Stephen C. Wills" Date: Sat, 26 Aug 2023 10:45:36 -0400 Subject: [PATCH 04/22] Shift software cursor graphic and remove special casing (#6512) --- Source/engine/render/scrollrt.cpp | 8 +++--- Source/inv.cpp | 13 ---------- Source/qol/stash.cpp | 23 +++--------------- .../timedemo/WarriorLevel1to2/demo_0.dmo | Bin 590790 -> 590790 bytes 4 files changed, 9 insertions(+), 35 deletions(-) diff --git a/Source/engine/render/scrollrt.cpp b/Source/engine/render/scrollrt.cpp index dfdd273d1..847b90696 100644 --- a/Source/engine/render/scrollrt.cpp +++ b/Source/engine/render/scrollrt.cpp @@ -263,13 +263,15 @@ void DrawCursor(const Surface &out) // Copy the buffer before the item cursor and its 1px outline are drawn to a temporary buffer. const int outlineWidth = !MyPlayer->HoldItem.isEmpty() ? 1 : 0; + Displacement offset = !MyPlayer->HoldItem.isEmpty() ? Displacement { cursSize / 2 } : Displacement { 0 }; + Point cursPosition = MousePosition - offset; Rectangle &rect = cursor.rect; - rect.position.x = MousePosition.x - outlineWidth; + rect.position.x = cursPosition.x - outlineWidth; rect.size.width = cursSize.width + 2 * outlineWidth; Clip(rect.position.x, rect.size.width, out.w()); - rect.position.y = MousePosition.y - outlineWidth; + rect.position.y = cursPosition.y - outlineWidth; rect.size.height = cursSize.height + 2 * outlineWidth; Clip(rect.position.y, rect.size.height, out.h()); @@ -277,7 +279,7 @@ void DrawCursor(const Surface &out) return; BlitCursor(cursor.behindBuffer, rect.size.width, &out[rect.position], out.pitch(), rect.size.width, rect.size.height); - DrawSoftwareCursor(out, MousePosition + Displacement { 0, cursSize.height - 1 }, pcurs); + DrawSoftwareCursor(out, cursPosition + Displacement { 0, cursSize.height - 1 }, pcurs); } /** diff --git a/Source/inv.cpp b/Source/inv.cpp index 8350fc7aa..dee00b18f 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -288,12 +288,6 @@ int FindSlotUnderCursor(Point cursorPosition, Size itemSize) int i = cursorPosition.x; int j = cursorPosition.y; - if (!IsHardwareCursor()) { - // offset the cursor position to match the hot pixel we'd use for a hardware cursor - i += itemSize.width * INV_SLOT_HALF_SIZE_PX; - j += itemSize.height * INV_SLOT_HALF_SIZE_PX; - } - for (int r = 0; r < NUM_XY_SLOTS; r++) { int xo = GetRightPanel().position.x; int yo = GetRightPanel().position.y; @@ -554,8 +548,6 @@ void CheckInvPaste(Player &player, Point cursorPosition) } CalcPlrInv(player, true); if (&player == MyPlayer) { - if (player.HoldItem.isEmpty() && !IsHardwareCursor()) - SetCursorPos(MousePosition + Displacement { itemSize * INV_SLOT_HALF_SIZE_PX }); NewCursor(player.HoldItem); } } @@ -819,11 +811,6 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool holdItem.clear(); } else { NewCursor(holdItem); - if (!IsHardwareCursor() && !dropItem) { - // For a hardware cursor, we set the "hot point" to the center of the item instead. - Size cursSize = GetInvItemSize(holdItem._iCurs + CURSOR_FIRSTITEM); - SetCursorPos(cursorPosition - Displacement(cursSize / 2)); - } } } } diff --git a/Source/qol/stash.cpp b/Source/qol/stash.cpp index 14e5105e3..630554d13 100644 --- a/Source/qol/stash.cpp +++ b/Source/qol/stash.cpp @@ -94,12 +94,10 @@ void CheckStashPaste(Point cursorPosition) Player &player = *MyPlayer; const Size itemSize = GetInventorySize(player.HoldItem); - const Displacement hotPixelOffset = Displacement(itemSize * INV_SLOT_HALF_SIZE_PX); - if (IsHardwareCursor()) { - // It's more natural to select the top left cell of the region the sprite is overlapping when putting an item - // into an inventory grid, so compensate for the adjusted hot pixel of hardware cursors. - cursorPosition -= hotPixelOffset; - } + + // It's more natural to select the top left cell of the region the sprite is overlapping when putting an item + // into an inventory grid, so compensate for the adjusted hot pixel of item cursors. + cursorPosition -= Displacement(itemSize * INV_SLOT_HALF_SIZE_PX); if (!IsItemAllowedInStash(player.HoldItem)) return; @@ -111,10 +109,6 @@ void CheckStashPaste(Point cursorPosition) player.HoldItem.clear(); PlaySFX(IS_GOLD); Stash.dirty = true; - if (!IsHardwareCursor()) { - // To make software cursors behave like hardware cursors we need to adjust the hand cursor position manually - SetCursorPos(cursorPosition + hotPixelOffset); - } NewCursor(CURSOR_HAND); return; } @@ -165,10 +159,6 @@ void CheckStashPaste(Point cursorPosition) Stash.dirty = true; - if (player.HoldItem.isEmpty() && !IsHardwareCursor()) { - // To make software cursors behave like hardware cursors we need to adjust the hand cursor position manually - SetCursorPos(cursorPosition + hotPixelOffset); - } NewCursor(player.HoldItem); } @@ -243,11 +233,6 @@ void CheckStashCut(Point cursorPosition, bool automaticMove) holdItem.clear(); } else { NewCursor(holdItem); - if (!IsHardwareCursor()) { - // For a hardware cursor, we set the "hot point" to the center of the item instead. - Size cursSize = GetInvItemSize(holdItem._iCurs + CURSOR_FIRSTITEM); - SetCursorPos(cursorPosition - Displacement(cursSize / 2)); - } } } } diff --git a/test/fixtures/timedemo/WarriorLevel1to2/demo_0.dmo b/test/fixtures/timedemo/WarriorLevel1to2/demo_0.dmo index a7c10bc76ed0f0aee5a71578ca77e2f8faf33d00..ebebbb49150f7f807652bd1d65bff6ac57f4b9d5 100644 GIT binary patch delta 220 zcmV<203-j#ha|>_B(Pq`17!hsvth@0+y?CdL;wH~m(gbgP`59n0pJ1y=K(vn5~l(9 z2LsmudAAL(0b~dZZvl+}01yEH0026dk+TOVm%w!d1(!Rf0ttt*w*j}Zw*nei1A764 zx6cm)CI<#-0Sf^D5SQ^q1tPZ{7XyEY18xCvho7GVho7GWho7GXx1XN|>emKn0gnIx z5SM}W0uz@{j|M20;6noomoSY24wtV&0}7Y$#RmnKuv7;Mm%xz*6}J(12iye%Ujg&C W@O=lgF9SjXUx%ly2e+rK2p?7ss7(?8 delta 220 zcmX@su5zqhWkcL?=5R*e&56e=?yy~C)L>u`n0_&xC1AV$62=Ei%%>UUw+k(2{K3w= zo6&ze?|Q}*4(>$8WS}ZW1_lPJ=@U1zYfL{-%ECI`dKnXE`=;%T+c#}zl8RuCXN=l@ zo}XEboh_R2A;{2=uB=MiWyP86JD3v~-P`BSX9i*xAZFb@e?Hr*>ugbssUUTY-0a%st(pmx*usdM9SC=^u`=vrgX-!p=4Qz(h9D?Sd8TcUYMN8BcEiP|LnW UpIME`xqbOs_U+5pa>$1P07S-1jQ{`u From dd296d25e0a59041ade975af213e399356e24eec Mon Sep 17 00:00:00 2001 From: ephphatha Date: Sun, 27 Aug 2023 15:33:20 +1000 Subject: [PATCH 05/22] Find the closest point when pasting items on the edge of the stash --- Source/qol/stash.cpp | 45 ++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/Source/qol/stash.cpp b/Source/qol/stash.cpp index 630554d13..681f51e1b 100644 --- a/Source/qol/stash.cpp +++ b/Source/qol/stash.cpp @@ -50,7 +50,8 @@ constexpr Rectangle StashButtonRect[] = { // clang-format on }; -constexpr PointsInRectangleRange StashGridRange { { { 0, 0 }, Size { 10, 10 } } }; +constexpr Size StashGridSize { 10, 10 }; +constexpr PointsInRectangleRange StashGridRange { { { 0, 0 }, StashGridSize } }; OptionalOwnedClxSpriteList StashPanelArt; OptionalOwnedClxSpriteList StashNavButtonArt; @@ -68,7 +69,7 @@ void AddItemToStashGrid(unsigned page, Point position, uint16_t stashListIndex, } } -Point FindSlotUnderCursor(Point cursorPosition) +std::optional FindTargetSlotUnderItemCursor(Point cursorPosition, Size itemSize) { for (auto point : StashGridRange) { Rectangle cell { @@ -77,11 +78,30 @@ Point FindSlotUnderCursor(Point cursorPosition) }; if (cell.contains(cursorPosition)) { + // When trying to paste into the stash we need to determine the top left cell of the nearest area that could fit the item, not the slot under the center/hot pixel. + if (itemSize.height <= 1 && itemSize.width <= 1) { + // top left cell of a 1x1 item is the same cell as the hot pixel, no work to do + return point; + } + // Otherwise work out how far the central cell is from the top-left cell + Displacement hotPixelCellOffset = { (itemSize.width - 1) / 2, (itemSize.height - 1) / 2 }; + // For even dimension items we need to work out if the cursor is in the left/right (or top/bottom) half of the central cell and adjust the offset so the item lands in the area most covered by the cursor. + if (itemSize.width % 2 == 0 && cell.contains(cursorPosition + Displacement { INV_SLOT_HALF_SIZE_PX, 0 })) { + // hot pixel was in the left half of the cell, so we want to increase the offset to preference the column to the left + hotPixelCellOffset.deltaX++; + } + if (itemSize.height % 2 == 0 && cell.contains(cursorPosition + Displacement { 0, INV_SLOT_HALF_SIZE_PX })) { + // hot pixel was in the top half of the cell, so we want to increase the offset to preference the row above + hotPixelCellOffset.deltaY++; + } + // Then work out the top left cell of the nearest area that could fit this item (as pasting on the edge of the stash would otherwise put it out of bounds) + point.y = clamp(point.y - hotPixelCellOffset.deltaY, 0, StashGridSize.height - itemSize.height); + point.x = clamp(point.x - hotPixelCellOffset.deltaX, 0, StashGridSize.width - itemSize.width); return point; } } - return InvalidStashPoint; + return {}; } bool IsItemAllowedInStash(const Item &item) @@ -93,12 +113,6 @@ void CheckStashPaste(Point cursorPosition) { Player &player = *MyPlayer; - const Size itemSize = GetInventorySize(player.HoldItem); - - // It's more natural to select the top left cell of the region the sprite is overlapping when putting an item - // into an inventory grid, so compensate for the adjusted hot pixel of item cursors. - cursorPosition -= Displacement(itemSize * INV_SLOT_HALF_SIZE_PX); - if (!IsItemAllowedInStash(player.HoldItem)) return; @@ -113,15 +127,13 @@ void CheckStashPaste(Point cursorPosition) return; } - // Make the hot pixel the center of the top-left cell of the item, this favors the cell which contains more of the - // item sprite - Point firstSlot = FindSlotUnderCursor(cursorPosition + Displacement(INV_SLOT_HALF_SIZE_PX)); - if (firstSlot == InvalidStashPoint) + const Size itemSize = GetInventorySize(player.HoldItem); + + std::optional targetSlot = FindTargetSlotUnderItemCursor(cursorPosition, itemSize); + if (!targetSlot) return; - if (firstSlot.x + itemSize.width > 10 || firstSlot.y + itemSize.height > 10) { - return; // Item does not fit - } + Point firstSlot = *targetSlot; // Check that no more than 1 item is replaced by the move StashStruct::StashCell stashIndex = StashStruct::EmptyCell; @@ -138,6 +150,7 @@ void CheckStashPaste(Point cursorPosition) PlaySFX(ItemInvSnds[ItemCAnimTbl[player.HoldItem._iCurs]]); + // Need to set the item anchor position to the bottom left so drawing code functions correctly. player.HoldItem.position = firstSlot + Displacement { 0, itemSize.height - 1 }; if (stashIndex == StashStruct::EmptyCell) { From fb97eb711419fbc07a2621f51f9c7c00b14c9e27 Mon Sep 17 00:00:00 2001 From: ephphatha Date: Sun, 27 Aug 2023 12:14:09 +1000 Subject: [PATCH 06/22] Clean up hit detection for inventory slots when pasting large items --- Source/inv.cpp | 111 ++++++++++++++++++++++++++----------------------- Source/inv.h | 7 +++- 2 files changed, 64 insertions(+), 54 deletions(-) diff --git a/Source/inv.cpp b/Source/inv.cpp index dee00b18f..df76149cc 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -3,7 +3,6 @@ * * Implementation of player inventory. */ -#include #include #include @@ -31,6 +30,7 @@ #include "utils/format_int.hpp" #include "utils/language.h" #include "utils/sdl_geometry.h" +#include "utils/stdcompat/algorithm.hpp" #include "utils/stdcompat/optional.hpp" #include "utils/str_cat.hpp" #include "utils/utf8.hpp" @@ -44,21 +44,21 @@ bool invflag; * arranged as follows: * * @code{.unparsed} - * 00 01 - * 02 03 06 + * 00 00 + * 00 00 03 * - * 07 08 19 20 13 14 - * 09 10 21 22 15 16 - * 11 12 23 24 17 18 + * 04 04 06 06 05 05 + * 04 04 06 06 05 05 + * 04 04 06 06 05 05 * - * 04 05 + * 01 02 * - * 25 26 27 28 29 30 31 32 33 34 - * 35 36 37 38 39 40 41 42 43 44 - * 45 46 47 48 49 50 51 52 53 54 - * 55 56 57 58 59 60 61 62 63 64 + * 07 08 09 10 11 12 13 14 15 16 + * 17 18 19 20 21 22 23 24 25 26 + * 27 28 29 30 31 32 33 34 35 36 + * 37 38 39 40 41 42 43 44 45 46 * - * 65 66 67 68 69 70 71 72 + * 47 48 49 50 51 52 53 54 * @endcode */ const Rectangle InvRect[] = { @@ -283,30 +283,43 @@ bool AutoEquip(Player &player, const Item &item, inv_body_loc bodyLocation, bool return true; } -int FindSlotUnderCursor(Point cursorPosition, Size itemSize) +int FindTargetSlotUnderItemCursor(Point cursorPosition, Size itemSize) { - int i = cursorPosition.x; - int j = cursorPosition.y; - - for (int r = 0; r < NUM_XY_SLOTS; r++) { - int xo = GetRightPanel().position.x; - int yo = GetRightPanel().position.y; - if (r >= SLOTXY_BELT_FIRST) { - xo = GetMainPanel().position.x; - yo = GetMainPanel().position.y; + Displacement panelOffset = Point { 0, 0 } - GetRightPanel().position; + for (int r = SLOTXY_EQUIPPED_FIRST; r <= SLOTXY_EQUIPPED_LAST; r++) { + if (InvRect[r].contains(cursorPosition + panelOffset)) + return r; + } + for (int r = SLOTXY_INV_FIRST; r <= SLOTXY_INV_LAST; r++) { + if (InvRect[r].contains(cursorPosition + panelOffset)) { + // When trying to paste into the inventory we need to determine the top left cell of the nearest area that could fit the item, not the slot under the center/hot pixel. + if (itemSize.height <= 1 && itemSize.width <= 1) { + // top left cell of a 1x1 item is the same cell as the hot pixel, no work to do + return r; + } + // Otherwise work out how far the central cell is from the top-left cell + Displacement hotPixelCellOffset = { (itemSize.width - 1) / 2, (itemSize.height - 1) / 2 }; + // For even dimension items we need to work out if the cursor is in the left/right (or top/bottom) half of the central cell and adjust the offset so the item lands in the area most covered by the cursor. + if (itemSize.width % 2 == 0 && InvRect[r].contains(cursorPosition + panelOffset + Displacement { INV_SLOT_HALF_SIZE_PX, 0 })) { + // hot pixel was in the left half of the cell, so we want to increase the offset to preference the column to the left + hotPixelCellOffset.deltaX++; + } + if (itemSize.height % 2 == 0 && InvRect[r].contains(cursorPosition + panelOffset + Displacement { 0, INV_SLOT_HALF_SIZE_PX })) { + // hot pixel was in the top half of the cell, so we want to increase the offset to preference the row above + hotPixelCellOffset.deltaY++; + } + // Then work out the top left cell of the nearest area that could fit this item (as pasting on the edge of the inventory would otherwise put it out of bounds) + int hotPixelCell = r - SLOTXY_INV_FIRST; + int targetRow = clamp((hotPixelCell / InventorySizeInSlots.width) - hotPixelCellOffset.deltaY, 0, InventorySizeInSlots.height - itemSize.height); + int targetColumn = clamp((hotPixelCell % InventorySizeInSlots.width) - hotPixelCellOffset.deltaX, 0, InventorySizeInSlots.width - itemSize.width); + return SLOTXY_INV_FIRST + targetRow * InventorySizeInSlots.width + targetColumn; } + } - if (r == SLOTXY_INV_FIRST) { - if (itemSize.width % 2 == 0) - i -= INV_SLOT_HALF_SIZE_PX; - if (itemSize.height % 2 == 0) - j -= INV_SLOT_HALF_SIZE_PX; - } - if (InvRect[r].contains(i - xo, j - yo)) { + panelOffset = Point { 0, 0 } - GetMainPanel().position; + for (int r = SLOTXY_BELT_FIRST; r <= SLOTXY_BELT_LAST; r++) { + if (InvRect[r].contains(cursorPosition + panelOffset)) return r; - } - if (r == SLOTXY_INV_LAST && itemSize.height % 2 == 0) - j += INV_SLOT_HALF_SIZE_PX; } return NUM_XY_SLOTS; } @@ -315,7 +328,7 @@ void CheckInvPaste(Player &player, Point cursorPosition) { Size itemSize = GetInventorySize(player.HoldItem); - int slot = FindSlotUnderCursor(cursorPosition, itemSize); + int slot = FindTargetSlotUnderItemCursor(cursorPosition, itemSize); if (slot == NUM_XY_SLOTS) return; @@ -352,26 +365,25 @@ void CheckInvPaste(Player &player, Point cursorPosition) } } } else { - int yy = std::max(INV_ROW_SLOT_SIZE * ((ii / INV_ROW_SLOT_SIZE) - ((itemSize.height - 1) / 2)), 0); - for (int j = 0; j < itemSize.height; j++) { - if (yy >= InventoryGridCells) - return; - int xx = std::max((ii % INV_ROW_SLOT_SIZE) - ((itemSize.width - 1) / 2), 0); - for (int i = 0; i < itemSize.width; i++) { - if (xx >= INV_ROW_SLOT_SIZE) - return; - if (player.InvGrid[xx + yy] != 0) { - int8_t iv = abs(player.InvGrid[xx + yy]); + // check that the item we're pasting only overlaps one other item (or is going into empty space) + unsigned originCell = static_cast(slot - SLOTXY_INV_FIRST); + for (unsigned rowOffset = 0; rowOffset < static_cast(itemSize.height * InventorySizeInSlots.width); rowOffset += InventorySizeInSlots.width) { + for (unsigned columnOffset = 0; columnOffset < static_cast(itemSize.width); columnOffset++) { + unsigned testCell = originCell + rowOffset + columnOffset; + // FindTargetSlotUnderItemCursor returns the top left slot of the inventory region that fits the item, we can be confident this calculation is not going to read out of range. + assert(testCell < sizeof(player.InvGrid)); + if (player.InvGrid[testCell] != 0) { + int8_t iv = abs(player.InvGrid[testCell]); if (it != 0) { - if (it != iv) + if (it != iv) { + // Found two different items that would be displaced by the held item, can't paste the item here. return; + } } else { it = iv; } } - xx++; } - yy += INV_ROW_SLOT_SIZE; } } } else if (il == ILOC_BELT) { @@ -519,13 +531,8 @@ void CheckInvPaste(Player &player, Point cursorPosition) itemIndex = 0; } } - int ii = slot - SLOTXY_INV_FIRST; - - // Calculate top-left position of item for InvGrid and then add item to InvGrid - int xx = std::max(ii % INV_ROW_SLOT_SIZE - ((itemSize.width - 1) / 2), 0); - int yy = std::max(INV_ROW_SLOT_SIZE * (ii / INV_ROW_SLOT_SIZE - ((itemSize.height - 1) / 2)), 0); - AddItemToInvGrid(player, xx + yy, it, itemSize); + AddItemToInvGrid(player, slot - SLOTXY_INV_FIRST, it, itemSize); } break; case ILOC_BELT: { diff --git a/Source/inv.h b/Source/inv.h index b65d64970..41b798c67 100644 --- a/Source/inv.h +++ b/Source/inv.h @@ -17,7 +17,8 @@ namespace devilution { #define INV_SLOT_SIZE_PX 28 #define INV_SLOT_HALF_SIZE_PX (INV_SLOT_SIZE_PX / 2) -#define INV_ROW_SLOT_SIZE 10 +constexpr Size InventorySizeInSlots { 10, 4 }; +#define INV_ROW_SLOT_SIZE InventorySizeInSlots.width constexpr Size InventorySlotSizeInPixels { INV_SLOT_SIZE_PX }; enum inv_item : int8_t { @@ -43,12 +44,14 @@ enum inv_item : int8_t { enum inv_xy_slot : uint8_t { // clang-format off SLOTXY_HEAD = 0, + SLOTXY_EQUIPPED_FIRST = SLOTXY_HEAD, SLOTXY_RING_LEFT = 1, SLOTXY_RING_RIGHT = 2, SLOTXY_AMULET = 3, SLOTXY_HAND_LEFT = 4, SLOTXY_HAND_RIGHT = 5, SLOTXY_CHEST = 6, + SLOTXY_EQUIPPED_LAST = SLOTXY_CHEST, // regular inventory SLOTXY_INV_FIRST = 7, @@ -79,7 +82,7 @@ enum item_color : uint8_t { }; extern bool invflag; -extern const Rectangle InvRect[73]; +extern const Rectangle InvRect[NUM_XY_SLOTS]; void InvDrawSlotBack(const Surface &out, Point targetPosition, Size size, item_quality itemQuality); /** From 9995c00323223532bc8f6ec26ab27e02f1089a43 Mon Sep 17 00:00:00 2001 From: Eric Robinson <68359262+kphoenix137@users.noreply.github.com> Date: Sun, 27 Aug 2023 12:22:24 -0400 Subject: [PATCH 07/22] Validate Item Locations (#6427) --- Source/pack.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Source/pack.cpp b/Source/pack.cpp index c01011171..faaf741dd 100644 --- a/Source/pack.cpp +++ b/Source/pack.cpp @@ -556,6 +556,28 @@ bool UnPackNetPlayer(const PlayerNetPack &packed, Player &player) for (int i = 0; i < NUM_INVLOC; i++) { if (!UnPackNetItem(player, packed.InvBody[i], player.InvBody[i])) return false; + if (player.InvBody[i].isEmpty()) + continue; + auto loc = static_cast(player.GetItemLocation(player.InvBody[i])); + switch (i) { + case INVLOC_HEAD: + ValidateField(loc, loc == ILOC_HELM); + break; + case INVLOC_RING_LEFT: + case INVLOC_RING_RIGHT: + ValidateField(loc, loc == ILOC_RING); + break; + case INVLOC_AMULET: + ValidateField(loc, loc == ILOC_AMULET); + break; + case INVLOC_HAND_LEFT: + case INVLOC_HAND_RIGHT: + ValidateField(loc, IsAnyOf(loc, ILOC_ONEHAND, ILOC_TWOHAND)); + break; + case INVLOC_CHEST: + ValidateField(loc, loc == ILOC_ARMOR); + break; + } } player._pNumInv = packed._pNumInv; @@ -570,6 +592,10 @@ bool UnPackNetPlayer(const PlayerNetPack &packed, Player &player) for (int i = 0; i < MaxBeltItems; i++) { if (!UnPackNetItem(player, packed.SpdList[i], player.SpdList[i])) return false; + if (player.SpdList[i].isEmpty()) + continue; + auto loc = static_cast(player.GetItemLocation(player.SpdList[i])); + ValidateField(loc, loc == ILOC_BELT); } CalcPlrInv(player, false); From 4deae11871b7cf0e684f75733c2eabc467598bdb Mon Sep 17 00:00:00 2001 From: staphen Date: Sun, 27 Aug 2023 14:21:25 -0400 Subject: [PATCH 08/22] Fix PlayerNetPack validation and tests --- Source/pack.cpp | 13 +++++++++---- test/pack_test.cpp | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Source/pack.cpp b/Source/pack.cpp index faaf741dd..626fcad5e 100644 --- a/Source/pack.cpp +++ b/Source/pack.cpp @@ -590,12 +590,17 @@ bool UnPackNetPlayer(const PlayerNetPack &packed, Player &player) player.InvGrid[i] = packed.InvGrid[i]; for (int i = 0; i < MaxBeltItems; i++) { - if (!UnPackNetItem(player, packed.SpdList[i], player.SpdList[i])) + Item &item = player.SpdList[i]; + if (!UnPackNetItem(player, packed.SpdList[i], item)) return false; - if (player.SpdList[i].isEmpty()) + if (item.isEmpty()) continue; - auto loc = static_cast(player.GetItemLocation(player.SpdList[i])); - ValidateField(loc, loc == ILOC_BELT); + Size beltItemSize = GetInventorySize(item); + int8_t beltItemType = static_cast(item._itype); + bool beltItemUsable = item.isUsable(); + ValidateFields(beltItemSize.width, beltItemSize.height, (beltItemSize == Size { 1, 1 })); + ValidateField(beltItemType, item._itype != ItemType::Gold); + ValidateField(beltItemUsable, beltItemUsable); } CalcPlrInv(player, false); diff --git a/test/pack_test.cpp b/test/pack_test.cpp index 31fa8d522..e24872687 100644 --- a/test/pack_test.cpp +++ b/test/pack_test.cpp @@ -870,6 +870,7 @@ public: { Players.resize(2); MyPlayer = &Players[0]; + gbIsMultiplayer = true; PlayerPack testPack { 0, 0, -1, 9, 0, 2, 61, 24, 0, 0, "MP-Warrior", 0, 120, 25, 60, 60, 37, 0, 85670061, 3921, 13568, 13568, 3904, 3904, From e9de3cc820d00d20c292c0a9fadfb7decaff60d4 Mon Sep 17 00:00:00 2001 From: Oleksandr Kalko Date: Tue, 29 Aug 2023 14:28:26 +0300 Subject: [PATCH 09/22] Upgrade vcpkg baseline commit (#6528) --- .github/workflows/Windows_MSVC_x64.yml | 4 ++-- vcpkg.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/Windows_MSVC_x64.yml b/.github/workflows/Windows_MSVC_x64.yml index 32fd06c50..49e76d0fc 100644 --- a/.github/workflows/Windows_MSVC_x64.yml +++ b/.github/workflows/Windows_MSVC_x64.yml @@ -31,9 +31,9 @@ jobs: uses: lukka/get-cmake@latest - name: Restore or setup vcpkg - uses: lukka/run-vcpkg@v11 + uses: lukka/run-vcpkg@v11.1 with: - vcpkgGitCommitId: '78b61582c9e093fda56a01ebb654be15a0033897' + vcpkgGitCommitId: '927bc12e31148b0d44ae9d174b96c20e3bcf08eb' - name: Fetch test data run: | diff --git a/vcpkg.json b/vcpkg.json index 4eeef04be..c6a6010da 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -6,7 +6,7 @@ "bzip2", "simpleini" ], - "builtin-baseline": "78b61582c9e093fda56a01ebb654be15a0033897", + "builtin-baseline": "927bc12e31148b0d44ae9d174b96c20e3bcf08eb", "features": { "sdl1": { "description": "Use SDL1.2 instead of SDL2", From e3d79ae1c14d920bcec4967c6e284ee6b7b3094e Mon Sep 17 00:00:00 2001 From: Eric Robinson <68359262+kphoenix137@users.noreply.github.com> Date: Tue, 29 Aug 2023 07:43:13 -0400 Subject: [PATCH 10/22] Fix automap (#6513) --- Source/automap.cpp | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/Source/automap.cpp b/Source/automap.cpp index 94da6860b..eabf18b71 100644 --- a/Source/automap.cpp +++ b/Source/automap.cpp @@ -116,16 +116,28 @@ void DrawDiamond(const Surface &out, Point center, uint8_t color) void DrawMapVerticalDoor(const Surface &out, Point center, uint8_t colorBright, uint8_t colorDim) { - DrawMapLineNE(out, { center.x + AmLine(8), center.y - AmLine(4) }, AmLine(4), colorDim); - DrawMapLineNE(out, { center.x - AmLine(16), center.y + AmLine(8) }, AmLine(4), colorDim); - DrawDiamond(out, center, colorBright); + if (leveltype != DTYPE_CATACOMBS) { + DrawMapLineNE(out, { center.x + AmLine(8), center.y - AmLine(4) }, AmLine(4), colorDim); + DrawMapLineNE(out, { center.x - AmLine(16), center.y + AmLine(8) }, AmLine(4), colorDim); + DrawDiamond(out, center, colorBright); + } else { + DrawMapLineNE(out, { center.x - AmLine(8), center.y + AmLine(4) }, AmLine(8), colorDim); + DrawMapLineNE(out, { center.x - AmLine(16), center.y + AmLine(8) }, AmLine(4), colorDim); + DrawDiamond(out, { center.x + AmLine(16), center.y - AmLine(8) }, colorBright); + } } void DrawMapHorizontalDoor(const Surface &out, Point center, uint8_t colorBright, uint8_t colorDim) { - DrawMapLineSE(out, { center.x - AmLine(16), center.y - AmLine(8) }, AmLine(4), colorDim); - DrawMapLineSE(out, { center.x + AmLine(8), center.y + AmLine(4) }, AmLine(4), colorDim); - DrawDiamond(out, center, colorBright); + if (leveltype != DTYPE_CATACOMBS) { + DrawMapLineSE(out, { center.x - AmLine(16), center.y - AmLine(8) }, AmLine(4), colorDim); + DrawMapLineSE(out, { center.x + AmLine(8), center.y + AmLine(4) }, AmLine(4), colorDim); + DrawDiamond(out, center, colorBright); + } else { + DrawMapLineSE(out, { center.x - AmLine(8), center.y - AmLine(4) }, AmLine(8), colorDim); + DrawMapLineSE(out, { center.x + AmLine(8), center.y + AmLine(4) }, AmLine(4), colorDim); + DrawDiamond(out, { center.x - AmLine(16), center.y - AmLine(8) }, colorBright); + } } void DrawDirt(const Surface &out, Point center, uint8_t color) @@ -897,6 +909,7 @@ void DrawAutomap(const Surface &out) Displacement myPlayerOffset = {}; if (myPlayer.isWalking()) myPlayerOffset = GetOffsetForWalking(myPlayer.AnimInfo, myPlayer._pdir, true); + myPlayerOffset += Displacement { -1, (leveltype != DTYPE_CAVES) ? TILE_HEIGHT - 1 : -1 }; int d = (AutoMapScale * 64) / 100; int cells = 2 * (gnScreenWidth / 2 / d) + 1; @@ -958,6 +971,8 @@ void DrawAutomap(const Surface &out) screen.y += AmLine(32); } + if (leveltype == DTYPE_CAVES) + myPlayerOffset.deltaY += TILE_HEIGHT; for (size_t playerId = 0; playerId < Players.size(); playerId++) { Player &player = Players[playerId]; if (player.isOnActiveLevel() && player.plractive && !player._pLvlChanging && (&player == MyPlayer || player.friendlyMode)) { @@ -965,6 +980,7 @@ void DrawAutomap(const Surface &out) } } + myPlayerOffset.deltaY -= TILE_HEIGHT / 2; if (AutoMapShowItems) SearchAutomapItem(out, myPlayerOffset, 8, [](Point position) { return dItem[position.x][position.y] != 0; }); #ifdef _DEBUG From 6cfa1cc752ba89382579738b603a857e7e2c9975 Mon Sep 17 00:00:00 2001 From: Oleksandr Kalko Date: Wed, 16 Aug 2023 13:17:27 +0300 Subject: [PATCH 11/22] Add draft for 1.5.1 release notes in metainfo --- Packaging/nix/devilutionx.metainfo.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Packaging/nix/devilutionx.metainfo.xml b/Packaging/nix/devilutionx.metainfo.xml index f4ee23893..a0bc80642 100644 --- a/Packaging/nix/devilutionx.metainfo.xml +++ b/Packaging/nix/devilutionx.metainfo.xml @@ -64,6 +64,23 @@ + + +

This is a primarily bugfix release, which includes the following updates:

+
    +
  • "Loopback" multiplayer is renamed "Offline"
  • +
  • HP/Mana display and item graphics options are moved from "Graphics" to "Gameplay" option menu
  • +
  • Gameplay fixes, such as book requirements not updating
  • +
  • Fixes to crashes found in 1.5.0
  • +
  • Reduction in RAM usage
  • +
  • Updates to PVP arenas
  • +
  • Multiplayer stability improvements
  • +
  • Updates to French, Portuguese, German, Greek, Ukrainian, Korean and Japanese translations
  • +
+

Please visit the full changelog for more detailed notes

+
+ https://github.com/diasurgical/devilutionX/releases/tag/1.5.1 +

This release includes a lot of features, such as:

From 3411bd08b3e9da4806485c8ad70ba535a9819dc9 Mon Sep 17 00:00:00 2001 From: Oleksandr Kalko Date: Wed, 16 Aug 2023 13:18:05 +0300 Subject: [PATCH 12/22] Fix typo in changelog --- docs/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1c63c2e19..e66108939 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -44,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Being able to enter Lazarus' chamber before opening the portal - Book requirements not updating - Diablo: Incorrect level 4 layout when Magic Banner quest is active -- Halls of the Blind not being compleated by picking up the amulet +- Halls of the Blind not being completed by picking up the amulet #### Platforms @@ -399,7 +399,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improve Romanian localization - Improve Russian localization ([optional dub](https://github.com/diasurgical/devilutionx-assets/releases/download/v2/ru.mpq) by Stream) - Improve Spanish localization - + #### Gameplay - Added a stash at Gillian's house From 37614c496f13694322d68e7c48012c93460dbef2 Mon Sep 17 00:00:00 2001 From: Mr-Bajs <93934125+Mr-Bajs@users.noreply.github.com> Date: Tue, 29 Aug 2023 12:02:35 +0000 Subject: [PATCH 13/22] Small changes to swedish translation (#6397) --- Translations/sv.po | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Translations/sv.po b/Translations/sv.po index 58b308891..9f93f1a85 100644 --- a/Translations/sv.po +++ b/Translations/sv.po @@ -1087,7 +1087,7 @@ msgstr "" #: Source/cursor.cpp:220 msgid "Town Portal" -msgstr "Stadsportal" +msgstr "Byportal" #: Source/cursor.cpp:221 msgid "from {:s}" @@ -1481,7 +1481,7 @@ msgstr "fel: läste 0 bytes från servern" #: Source/error.cpp:55 msgid "No automap available in town" -msgstr "Ingen autokarta tillgänglig i staden" +msgstr "Ingen autokarta tillgänglig i byn" #: Source/error.cpp:56 msgid "No multiplayer functions in demo" @@ -1501,7 +1501,7 @@ msgstr "Inte tillräckligt med utrymme för att spara" #: Source/error.cpp:60 msgid "No Pause in town" -msgstr "Ingen Paus i staden" +msgstr "Ingen Paus i byn" #: Source/error.cpp:61 msgid "Copying to a hard disk is recommended" @@ -1745,7 +1745,7 @@ msgstr "Avsluta Spel" #: Source/gamemenu.cpp:51 msgid "Restart In Town" -msgstr "Starta Om i Staden" +msgstr "Starta Om i Byn" #: Source/gamemenu.cpp:63 msgid "Gamma" @@ -2063,7 +2063,7 @@ msgstr "Köttyxa" #: Source/itemdat.cpp:60 Source/itemdat.cpp:434 msgid "The Undead Crown" -msgstr "De Vandödas Krona" +msgstr "De Odödas Krona" #: Source/itemdat.cpp:61 Source/itemdat.cpp:435 msgid "Empyrean Band" @@ -2143,7 +2143,7 @@ msgstr "Identifiera-Skriftrulle" #: Source/itemdat.cpp:80 Source/itemdat.cpp:151 msgid "Scroll of Town Portal" -msgstr "Stadsportal-Skriftrulle" +msgstr "Byportal-Skriftrulle" #: Source/itemdat.cpp:81 Source/itemdat.cpp:440 msgid "Arkaine's Valor" @@ -2483,7 +2483,7 @@ msgstr "Huggare" #: Source/itemdat.cpp:175 msgid "Claymore" -msgstr "Höglandssvärd" +msgstr "Slagsvärd" #: Source/itemdat.cpp:176 msgid "Blade" @@ -2560,7 +2560,7 @@ msgstr "Spikklubba" #: Source/itemdat.cpp:194 msgid "Flail" -msgstr "Slaga" +msgstr "Stridsgissel" #: Source/itemdat.cpp:195 msgid "Maul" @@ -4413,7 +4413,7 @@ msgstr "extra PK mot demoner" #: Source/items.cpp:3754 msgid "extra AC vs undead" -msgstr "extra PK mot vandöda" +msgstr "extra PK mot ödöda" #: Source/items.cpp:3756 msgid "50% Mana moved to Health" @@ -4788,7 +4788,7 @@ msgstr "Likdemon" #: Source/monstdat.cpp:91 msgctxt "monster" msgid "Undead Balrog" -msgstr "Vandöd Balrog" +msgstr "Odöd Balrog" #: Source/monstdat.cpp:92 msgctxt "monster" @@ -5614,7 +5614,7 @@ msgstr "Demon" #: Source/monster.cpp:3386 msgid "Undead" -msgstr "Vandöd" +msgstr "Odöd" #: Source/monster.cpp:4648 msgid "Type: {:s} Kills: {:d}" @@ -6848,7 +6848,7 @@ msgstr "Magi" #: Source/panels/spell_list.cpp:172 msgid "Damages undead only" -msgstr "Skadar endast vandöda" +msgstr "Skadar endast odöda" #: Source/panels/spell_list.cpp:183 msgid "Scroll" @@ -7080,7 +7080,7 @@ msgstr "Eldmur" #: Source/spelldat.cpp:22 msgctxt "spell" msgid "Town Portal" -msgstr "Stadsportal" +msgstr "Byportal" #: Source/spelldat.cpp:23 msgctxt "spell" @@ -7683,7 +7683,7 @@ msgstr "" "\n" "Här tar historien en ännu mörkare vändning än vad jag trodde var möjligt! " "Vår forne kung har stigit upp från sin eviga sömn och styr nu en legion av " -"vandöda tjänare inne i Labyrinten. Hans kropp begravdes i en grift tre " +"odöda tjänare inne i Labyrinten. Hans kropp begravdes i en grift tre " "våningar under Katedralen. Snälla, kära mästare, släpp hans själ fri genom " "att förgöra hans förbannade nuvarande form..." @@ -7768,7 +7768,7 @@ msgid "" "all who still live here." msgstr "" "De döda som går bland de levande följer den förbannade Kungen. Han har " -"makten att väcka upp ännu fler krigare för de vandödas evigt växande armé. " +"makten att väcka upp ännu fler krigare för de odödas evigt växande armé. " "Om du inte stoppar hans styre kommer han säkerligen tåga över detta land och " "slå ned alla som ännu bor här." @@ -7782,7 +7782,7 @@ msgid "" msgstr "" "Lyssna, jag har affärer att sköta här. Jag säljer inte information, och jag " "bryr mig inte om någon Kung som varit död längre än jag levat. Men om du " -"behöver något att använda mot den här vandöda Kungen, så kanske jag kan " +"behöver något att använda mot den här odöda Kungen, så kanske jag kan " "hjälpa dig..." #. TRANSLATORS: Quest dialog spoken by The Skeleton King (Hostile) @@ -8142,7 +8142,7 @@ msgid "" "Please, do what you can or I don't know what we will do." msgstr "" "Jag har alltid försökt hålla stora förråd av mat och dryck i vår källare, " -"men om hela staden saknar färskvatten kommer till och med våra förråd sina " +"men om hela byn saknar färskvatten kommer till och med våra förråd sina " "snart.\n" "\n" "Snälla, gör vad du kan, annars vet jag inte vad vi ska ta oss till." @@ -10005,7 +10005,7 @@ msgid "" "little skeletons!" msgstr "" "Om du är ute efter ett bra vapen så se här. Ta ett vanligt trubbigt vapen, " -"som en stridsklubba. Funkar utmärkt mot de där vandöda fasorna där nere, och " +"som en stridsklubba. Funkar utmärkt mot de där odöda fasorna där nere, och " "det finns inget bättre för att spräcka smala små skelett!" #. TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) @@ -10030,7 +10030,7 @@ msgid "" msgstr "" "Titta på den här klingan, balansen. Ett svärd i rätt händer och mot rätt " "fiende, är alla vapens mästare. Dess skarpa egg har inte mycket att skära " -"eller hugga igenom mot de vandöda, men mot levande fiender kan ett svärd på " +"eller hugga igenom mot de odöda, men mot levande fiender kan ett svärd på " "riktigt skära deras kött!" #. TRANSLATORS: Neutral dialog spoken by Griswold (Gossip) @@ -10945,7 +10945,7 @@ msgid "" "the talk of an old sick woman, but anything seems possible these days." msgstr "" "Min mormor berättar ofta historier om mystiska krafter som bebor kyrkogården " -"utanför stade. Och du kanske vill höra en av dem. Hon sade att om man lämnar " +"utanför byn. Och du kanske vill höra en av dem. Hon sade att om man lämnar " "en lämplig gåva i kyrkogården, går in i katedralen för att be för de döda, " "och sedan kommer tillbaka, så skulle gåvan ändrats på något konstigt sätt. " "Jag vet inte om det bara är en sjuk gammal kvinnas prat, men allt verkar " @@ -11463,7 +11463,7 @@ msgstr "Krögaren Ogden" #: Source/towners.cpp:110 msgid "Wounded Townsman" -msgstr "Skadad Stadsbo" +msgstr "Skadad Bybo" #: Source/towners.cpp:132 msgid "Adria the Witch" @@ -11499,7 +11499,7 @@ msgstr "Celia" #: Source/towners.cpp:277 msgid "Slain Townsman" -msgstr "Dräpt Stadsbo" +msgstr "Dräpt Bybo" #: Source/trigs.cpp:343 msgid "Down to dungeon" @@ -11533,7 +11533,7 @@ msgstr "Upp till nivå {:d}" #: Source/trigs.cpp:416 Source/trigs.cpp:471 Source/trigs.cpp:523 #: Source/trigs.cpp:602 Source/trigs.cpp:619 Source/trigs.cpp:666 msgid "Up to town" -msgstr "Upp till staden" +msgstr "Upp till byn" #: Source/trigs.cpp:427 Source/trigs.cpp:505 Source/trigs.cpp:558 #: Source/trigs.cpp:583 Source/trigs.cpp:648 From c0caedcd96bb280d089b0f3c94fcf27a61ed5e40 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 31 Aug 2023 03:58:07 +0200 Subject: [PATCH 14/22] Delete .bettercodehub.yml --- .bettercodehub.yml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .bettercodehub.yml diff --git a/.bettercodehub.yml b/.bettercodehub.yml deleted file mode 100644 index 70e508efa..000000000 --- a/.bettercodehub.yml +++ /dev/null @@ -1,6 +0,0 @@ -exclude: -- /Packaging/.* -- /3rdParty/.* -languages: -- cpp -component_depth: 2 From d4682082e2ad88b9e9053018c10d674f7655ea7d Mon Sep 17 00:00:00 2001 From: Eric Robinson <68359262+kphoenix137@users.noreply.github.com> Date: Thu, 31 Aug 2023 02:03:46 -0400 Subject: [PATCH 15/22] Add duration parameter to InitDiabloMsg() (#6514) --- Source/error.cpp | 50 ++++++++++++++++++++++++++++++--------------- Source/error.h | 4 ++-- Source/gamemenu.cpp | 2 +- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/Source/error.cpp b/Source/error.cpp index 899b38984..dddf68001 100644 --- a/Source/error.cpp +++ b/Source/error.cpp @@ -20,19 +20,23 @@ namespace devilution { namespace { -std::deque DiabloMessages; +struct MessageEntry { + std::string text; + uint32_t duration; // Duration in milliseconds +}; + +std::deque DiabloMessages; +uint32_t msgStartTime = 0; std::vector TextLines; -uint32_t msgdelay; int ErrorWindowHeight = 54; const int LineHeight = 12; const int LineWidth = 418; void InitNextLines() { - msgdelay = SDL_GetTicks(); TextLines.clear(); - const std::string paragraphs = WordWrapString(DiabloMessages.front(), LineWidth, GameFont12, 1); + const std::string paragraphs = WordWrapString(DiabloMessages.front().text, LineWidth, GameFont12, 1); size_t previous = 0; while (true) { @@ -107,22 +111,26 @@ const char *const MsgStrings[] = { N_(/* TRANSLATORS: Shrine Text. Keep atmospheric. :) */ "That which can break will."), }; -void InitDiabloMsg(diablo_message e) +void InitDiabloMsg(diablo_message e, uint32_t duration /*= 3500*/) { - InitDiabloMsg(LanguageTranslate(MsgStrings[e])); + InitDiabloMsg(LanguageTranslate(MsgStrings[e]), duration); } -void InitDiabloMsg(string_view msg) +void InitDiabloMsg(string_view msg, uint32_t duration /*= 3500*/) { if (DiabloMessages.size() >= MAX_SEND_STR_LEN) return; - if (std::find(DiabloMessages.begin(), DiabloMessages.end(), msg) != DiabloMessages.end()) + if (std::find_if(DiabloMessages.begin(), DiabloMessages.end(), + [&msg](const MessageEntry &entry) { return entry.text == msg; }) + != DiabloMessages.end()) return; - DiabloMessages.push_back(std::string(msg)); - if (DiabloMessages.size() == 1) + DiabloMessages.push_back({ std::string(msg), duration }); + if (DiabloMessages.size() == 1) { InitNextLines(); + msgStartTime = SDL_GetTicks(); + } } bool IsDiabloMsgAvailable() @@ -132,7 +140,13 @@ bool IsDiabloMsgAvailable() void CancelCurrentDiabloMsg() { - msgdelay = 0; + if (!DiabloMessages.empty()) { + DiabloMessages.pop_front(); + if (!DiabloMessages.empty()) { + InitNextLines(); + msgStartTime = SDL_GetTicks(); + } + } } void ClrDiabloMsg() @@ -171,13 +185,17 @@ void DrawDiabloMsg(const Surface &out) lineNumber += 1; } - if (msgdelay > 0 && msgdelay <= SDL_GetTicks() - 3500) { - msgdelay = 0; - } - if (msgdelay == 0) { + // Calculate the time the current message has been displayed + uint32_t currentTime = SDL_GetTicks(); + uint32_t messageElapsedTime = currentTime - msgStartTime; + + // Check if the current message's duration has passed + if (!DiabloMessages.empty() && messageElapsedTime >= DiabloMessages.front().duration) { DiabloMessages.pop_front(); - if (!DiabloMessages.empty()) + if (!DiabloMessages.empty()) { InitNextLines(); + msgStartTime = currentTime; + } } } diff --git a/Source/error.h b/Source/error.h index 6e10e011e..9aa41ff99 100644 --- a/Source/error.h +++ b/Source/error.h @@ -71,8 +71,8 @@ enum diablo_message : uint8_t { EMSG_SHRINE_MURPHYS, }; -void InitDiabloMsg(diablo_message e); -void InitDiabloMsg(string_view msg); +void InitDiabloMsg(diablo_message e, uint32_t duration = 3500); +void InitDiabloMsg(string_view msg, uint32_t duration = 3500); bool IsDiabloMsgAvailable(); void CancelCurrentDiabloMsg(); void ClrDiabloMsg(); diff --git a/Source/gamemenu.cpp b/Source/gamemenu.cpp index bcd3d878e..3237c414a 100644 --- a/Source/gamemenu.cpp +++ b/Source/gamemenu.cpp @@ -332,7 +332,7 @@ void gamemenu_save_game(bool /*bActivate*/) DrawAndBlit(); SaveGame(); ClrDiabloMsg(); - InitDiabloMsg(EMSG_GAME_SAVED); + InitDiabloMsg(EMSG_GAME_SAVED, 1000); RedrawEverything(); NewCursor(CURSOR_HAND); if (CornerStone.activated) { From 24b345573885807a84329d4903343e4cfc3e9214 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 31 Aug 2023 08:05:11 +0200 Subject: [PATCH 16/22] Update CHANGELOG.md (#6531) --- docs/CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e66108939..efc7405bd 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -17,12 +17,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Translations +- Update Simplified Chinese translation - Update French translation - Update German translation - Update Greek translation - Update Japanese translation - Update Korean translation - Update Portuguese translation +- Update Spanish translation +- Update Swedish translation - Update Ukrainian translation #### Platforms @@ -35,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Validate properties when reloading items - Demomode: Improve replay stability - Update [Discord link](https://discord.gg/devilutionx) +- Display save game confirmation - Reduce ram usage ### Bugfixes @@ -43,13 +47,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Being able to enter Lazarus' chamber before opening the portal - Book requirements not updating +- Some monsters not walking +- Missiles not traveleing the full distance at some angles - Diablo: Incorrect level 4 layout when Magic Banner quest is active - Halls of the Blind not being completed by picking up the amulet +- Shareware: Bucklers not dropping +- Player animation stuttering + +#### Multiplayer +- Potions droped by Divine shrine not being synced #### Platforms - Linux: Add sdl-image dependency for deb package - Linux: Include discord dependency +- Xbox One: Missing assets #### Graphics / Audio @@ -57,14 +69,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Incorrect outlines at the right edge of the screen - NPC speech continuing after starting a new game - Correct various font rendering issues +- Hide hit indicator when only one player is in game +- Issues with flashing lights +- Floating number still appearing after death +- Missaligned automap #### Controls +- Inconsistencies with placing items in to the stash - Gamepad: Being stuck in dialogs - Gamepad: Unable to use some scrolls directly #### Stability / Performance / System +- Unable to playback 1.5.0 demo files - Various crashes ### Bugfixes for original Diablo bugs @@ -72,11 +90,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Gameplay - Durability overflowing when reloading items +- Teleporting onto an occupied tile +- Right click during dialogs casts spells #### Graphics / Audio +- Cursor jitter when interacting with the inventory - Broken lava tiles +#### Controls + +- Inconsistencies with placing items in to the inventory + +### Bugfixes for original Hellfire bugs + +#### Gameplay + +- Warping onto a solid tile + ## DevilutionX 1.5.0 ### Features From 26977f37c5904ec7652c2c14c3acf016c1284217 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 31 Aug 2023 08:06:04 +0200 Subject: [PATCH 17/22] Update 1.5.1 release notes in metainfo --- Packaging/nix/devilutionx.metainfo.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Packaging/nix/devilutionx.metainfo.xml b/Packaging/nix/devilutionx.metainfo.xml index a0bc80642..20d820e63 100644 --- a/Packaging/nix/devilutionx.metainfo.xml +++ b/Packaging/nix/devilutionx.metainfo.xml @@ -68,14 +68,14 @@

This is a primarily bugfix release, which includes the following updates:

    -
  • "Loopback" multiplayer is renamed "Offline"
  • -
  • HP/Mana display and item graphics options are moved from "Graphics" to "Gameplay" option menu
  • -
  • Gameplay fixes, such as book requirements not updating
  • -
  • Fixes to crashes found in 1.5.0
  • -
  • Reduction in RAM usage
  • +
  • Resolve various gameplay and graphical issues
  • +
  • Revamped settings menu for better organization
  • +
  • Rectification of crashes identified in version 1.5.0
  • +
  • Reduced RAM usage for improved performance
  • Updates to PVP arenas
  • -
  • Multiplayer stability improvements
  • -
  • Updates to French, Portuguese, German, Greek, Ukrainian, Korean and Japanese translations
  • +
  • Increased reliability in multiplayer functionality
  • +
  • Improved translations
  • +
  • Fixed gameplay recording playback issues

Please visit the full changelog for more detailed notes

From 070a11901fa3a7fa314e65a97355c3f34e1d7c8c Mon Sep 17 00:00:00 2001 From: ephphatha Date: Thu, 31 Aug 2023 23:01:14 +1000 Subject: [PATCH 18/22] Fix handling of gamepad cursor movement following item cursor change --- Source/controls/plrctrls.cpp | 111 ++++++++++++++++------------------- 1 file changed, 49 insertions(+), 62 deletions(-) diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index 6256980f2..2c5c982d0 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -654,10 +654,10 @@ Point InvGetEquipSlotCoordFromInvSlot(const inv_xy_slot slot) Point GetSlotCoord(int slot) { if (slot >= SLOTXY_BELT_FIRST && slot <= SLOTXY_BELT_LAST) { - return GetPanelPosition(UiPanels::Main, InvRect[slot].position); + return GetPanelPosition(UiPanels::Main, InvRect[slot].Center()); } - return GetPanelPosition(UiPanels::Inventory, InvRect[slot].position); + return GetPanelPosition(UiPanels::Inventory, InvRect[slot].Center()); } /** @@ -733,25 +733,12 @@ void ResetInvCursorPosition() } else { mousePos = GetSlotCoord(Slot); } - - if (!MyPlayer->HoldItem.isEmpty()) { - mousePos += Displacement { -INV_SLOT_HALF_SIZE_PX, -INV_SLOT_HALF_SIZE_PX }; - } } else if (Slot >= SLOTXY_BELT_FIRST && Slot <= SLOTXY_BELT_LAST) { mousePos = GetSlotCoord(Slot); - if (!MyPlayer->HoldItem.isEmpty()) - mousePos += Displacement { -INV_SLOT_HALF_SIZE_PX, -INV_SLOT_HALF_SIZE_PX }; } else { mousePos = InvGetEquipSlotCoordFromInvSlot((inv_xy_slot)Slot); - if (!MyPlayer->HoldItem.isEmpty()) { - Size itemSize = GetInventorySize(MyPlayer->HoldItem); - mousePos += Displacement { -INV_SLOT_HALF_SIZE_PX, -INV_SLOT_HALF_SIZE_PX * itemSize.height }; - } } - mousePos.x += (InventorySlotSizeInPixels.width / 2); - mousePos.y -= (InventorySlotSizeInPixels.height / 2); - SetCursorPos(mousePos); } @@ -759,7 +746,6 @@ int FindClosestInventorySlot(Point mousePos) { int shortestDistance = std::numeric_limits::max(); int bestSlot = 0; - mousePos += Displacement { -INV_SLOT_HALF_SIZE_PX, INV_SLOT_HALF_SIZE_PX }; for (int i = 0; i < NUM_XY_SLOTS; i++) { int distance = mousePos.ManhattanDistance(GetSlotCoord(i)); @@ -776,7 +762,6 @@ Point FindClosestStashSlot(Point mousePos) { int shortestDistance = std::numeric_limits::max(); Point bestSlot = {}; - mousePos += Displacement { -INV_SLOT_HALF_SIZE_PX, -INV_SLOT_HALF_SIZE_PX }; for (Point point : PointsInRectangle(Rectangle { { 0, 0 }, Size { 10, 10 } })) { int distance = mousePos.ManhattanDistance(GetStashSlotCoord(point)); @@ -819,7 +804,7 @@ void InventoryMove(AxisDirection dir) const Item &heldItem = MyPlayer->HoldItem; const bool isHoldingItem = !heldItem.isEmpty(); - Size itemSize = GetInventorySize(heldItem); + Size itemSize = isHoldingItem ? GetInventorySize(heldItem) : Size { 1 }; // when item is on cursor (pcurs > 1), this is the real cursor XY if (dir.x == AxisDirectionX_LEFT) { @@ -1006,28 +991,27 @@ void InventoryMove(AxisDirection dir) } else { mousePos = GetSlotCoord(Slot); } - // move cursor to the center of the slot if not holding anything or top left is holding an object - if (isHoldingItem) { - if (Slot < SLOTXY_INV_FIRST) { - // The coordinates we get for body slots are based on the centre of the region relative to the hand cursor - // Need to adjust the position for items larger than 1x1 so they're aligned as expected - mousePos.x -= itemSize.width * INV_SLOT_HALF_SIZE_PX; - mousePos.y -= itemSize.height * INV_SLOT_HALF_SIZE_PX; - } - } else { - // get item under new slot if navigating on the inventory - if (Slot >= SLOTXY_INV_FIRST && Slot <= SLOTXY_BELT_LAST) { + // If we're in the inventory we may need to move the cursor to an area that doesn't line up with the center of a cell + if (Slot >= SLOTXY_INV_FIRST && Slot <= SLOTXY_INV_LAST) { + if (!isHoldingItem) { + // If we're not holding an item int8_t itemInvId = GetItemIdOnSlot(Slot); - int itemSlot = FindFirstSlotOnItem(itemInvId); - if (itemSlot < 0) - itemSlot = Slot; - - // offset the cursor so it shows over the center of the item - mousePos = GetSlotCoord(itemSlot); - itemSize = GetItemSizeOnSlot(itemSlot); - mousePos.x += (itemSize.width * InventorySlotSizeInPixels.width) / 2; - mousePos.y += (itemSize.height * InventorySlotSizeInPixels.height) / 2; + if (itemInvId != 0) { + // but the cursor moved over an item + int itemSlot = FindFirstSlotOnItem(itemInvId); + if (itemSlot < 0) + itemSlot = Slot; + + // then we need to offset the cursor so it shows over the center of the item + mousePos = GetSlotCoord(itemSlot); + itemSize = GetItemSizeOnSlot(itemSlot); + } } + // At this point itemSize is either the size of the cell/item the hand cursor is over, or the size of the item we're currently holding. + // mousePos is the center of the top left cell of the item under the hand cursor, or the top left cell of the region that could fit the item we're holding. + // either way we need to offset the mouse position to account for items (we're holding or hovering over) with a dimension larger than a single cell. + mousePos.x += ((itemSize.width - 1) * InventorySlotSizeInPixels.width) / 2; + mousePos.y += ((itemSize.height - 1) * InventorySlotSizeInPixels.height) / 2; } if (mousePos == MousePosition) { @@ -1826,36 +1810,39 @@ void PerformPrimaryAction() } else if (GetRightPanel().contains(MousePosition) || GetMainPanel().contains(MousePosition)) { int inventorySlot = (Slot >= 0) ? Slot : FindClosestInventorySlot(MousePosition); - const Size cursorSizeInCells = MyPlayer->HoldItem.isEmpty() ? Size { 1, 1 } : GetInventorySize(MyPlayer->HoldItem); - - // Find any item occupying a slot that is currently under the cursor - int8_t itemUnderCursor = [](int inventorySlot, Size cursorSizeInCells) { - if (inventorySlot < SLOTXY_INV_FIRST || inventorySlot > SLOTXY_INV_LAST) - return 0; - for (int x = 0; x < cursorSizeInCells.width; x++) { - for (int y = 0; y < cursorSizeInCells.height; y++) { - int slotUnderCursor = inventorySlot + x + y * INV_ROW_SLOT_SIZE; - if (slotUnderCursor > SLOTXY_INV_LAST) - continue; - int itemId = GetItemIdOnSlot(slotUnderCursor); - if (itemId != 0) - return itemId; + int jumpSlot = inventorySlot; // If the cursor is over an inventory slot we may need to adjust it due to pasting items of different sizes over each other + if (inventorySlot >= SLOTXY_INV_FIRST && inventorySlot <= SLOTXY_INV_LAST) { + const Size cursorSizeInCells = MyPlayer->HoldItem.isEmpty() ? Size { 1, 1 } : GetInventorySize(MyPlayer->HoldItem); + + // Find any item occupying a slot that is currently under the cursor + int8_t itemUnderCursor = [](int inventorySlot, Size cursorSizeInCells) { + if (inventorySlot < SLOTXY_INV_FIRST || inventorySlot > SLOTXY_INV_LAST) + return 0; + for (int x = 0; x < cursorSizeInCells.width; x++) { + for (int y = 0; y < cursorSizeInCells.height; y++) { + int slotUnderCursor = inventorySlot + x + y * INV_ROW_SLOT_SIZE; + if (slotUnderCursor > SLOTXY_INV_LAST) + continue; + int itemId = GetItemIdOnSlot(slotUnderCursor); + if (itemId != 0) + return itemId; + } } - } - return 0; - }(inventorySlot, cursorSizeInCells); + return 0; + }(inventorySlot, cursorSizeInCells); - // The cursor will need to be shifted to - // this slot if the item is swapped or lifted - int jumpSlot = FindFirstSlotOnItem(itemUnderCursor); + // Capture the first slot of the first item (if any) under the cursor + if (itemUnderCursor > 0) + jumpSlot = FindFirstSlotOnItem(itemUnderCursor); + } CheckInvItem(); - // If we don't find the item in the same position as before, - // it suggests that the item was swapped or lifted - int newSlot = FindFirstSlotOnItem(itemUnderCursor); - if (jumpSlot >= 0 && jumpSlot != newSlot) { + if (inventorySlot >= SLOTXY_INV_FIRST && inventorySlot <= SLOTXY_INV_LAST) { Point mousePos = GetSlotCoord(jumpSlot); Slot = jumpSlot; + const Size newCursorSizeInCells = MyPlayer->HoldItem.isEmpty() ? GetItemSizeOnSlot(jumpSlot) : GetInventorySize(MyPlayer->HoldItem); + mousePos.x += ((newCursorSizeInCells.width - 1) * InventorySlotSizeInPixels.width) / 2; + mousePos.y += ((newCursorSizeInCells.height - 1) * InventorySlotSizeInPixels.height) / 2; SetCursorPos(mousePos); } } else if (IsStashOpen && GetLeftPanel().contains(MousePosition)) { From 0d292b10c400d13dd40536f46c50cd1ba5680964 Mon Sep 17 00:00:00 2001 From: ephphatha Date: Thu, 31 Aug 2023 23:13:29 +1000 Subject: [PATCH 19/22] Move to the first inventory column from the left hand while holding wide items --- Source/controls/plrctrls.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index 2c5c982d0..d10729598 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -933,7 +933,7 @@ void InventoryMove(AxisDirection dir) if (Slot == SLOTXY_HEAD || Slot == SLOTXY_CHEST) { Slot = SLOTXY_INV_ROW1_FIRST + 4; } else if (Slot == SLOTXY_RING_LEFT || Slot == SLOTXY_HAND_LEFT) { - Slot = SLOTXY_INV_ROW1_FIRST + 1; + Slot = SLOTXY_INV_ROW1_FIRST + (itemSize.width > 1 ? 0 : 1); } else if (Slot == SLOTXY_RING_RIGHT || Slot == SLOTXY_HAND_RIGHT || Slot == SLOTXY_AMULET) { Slot = SLOTXY_INV_ROW1_LAST - 1; } else if (Slot <= (SLOTXY_INV_ROW4_LAST - (itemSize.height * INV_ROW_SLOT_SIZE))) { From 52b806c6a05b24cd9ed2c7fbf5ad802da43125a9 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 31 Aug 2023 22:30:44 +0200 Subject: [PATCH 20/22] Update CHANGELOG.md --- docs/CHANGELOG.md | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index efc7405bd..1ae15e992 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -15,6 +15,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update the pvp arenas - Rename "Loopback" to "Offline" +#### Stability / Performance / System + +- Move hp/mana display and item graphics to gameplay options +- Validate properties when reloading items +- Demomode: Improve replay stability +- Update [Discord link](https://discord.gg/devilutionx) +- Display save game confirmation +- Reduce ram usage + #### Translations - Update Simplified Chinese translation @@ -28,19 +37,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update Swedish translation - Update Ukrainian translation -#### Platforms - -- Android TV: Update banner to include app name - -#### Stability / Performance / System - -- Move hp/mana display and item graphics to gameplay options -- Validate properties when reloading items -- Demomode: Improve replay stability -- Update [Discord link](https://discord.gg/devilutionx) -- Display save game confirmation -- Reduce ram usage - ### Bugfixes #### Gameplay @@ -48,18 +44,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Being able to enter Lazarus' chamber before opening the portal - Book requirements not updating - Some monsters not walking -- Missiles not traveleing the full distance at some angles -- Diablo: Incorrect level 4 layout when Magic Banner quest is active +- Missiles not traveling the full distance at some angles +- Diablo: Incorrect level 4 layout when the Magic Banner quest is active - Halls of the Blind not being completed by picking up the amulet - Shareware: Bucklers not dropping - Player animation stuttering #### Multiplayer -- Potions droped by Divine shrine not being synced + +- Potions dropped by Divine shrines not being synced #### Platforms -- Linux: Add sdl-image dependency for deb package +- Linux: Add sdl-image dependency for the deb package - Linux: Include discord dependency - Xbox One: Missing assets @@ -69,10 +66,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Incorrect outlines at the right edge of the screen - NPC speech continuing after starting a new game - Correct various font rendering issues -- Hide hit indicator when only one player is in game +- Hide the hit indicator when only one player is in the game - Issues with flashing lights - Floating number still appearing after death -- Missaligned automap +- Misaligned automap #### Controls @@ -82,7 +79,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Stability / Performance / System -- Unable to playback 1.5.0 demo files +- Unable to playback new demo files - Various crashes ### Bugfixes for original Diablo bugs @@ -91,7 +88,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Durability overflowing when reloading items - Teleporting onto an occupied tile -- Right click during dialogs casts spells +- Right-click during dialogs casts spells #### Graphics / Audio From c85fcbdfb172c8812768ab7af7117c23c89d2daf Mon Sep 17 00:00:00 2001 From: ephphatha Date: Fri, 1 Sep 2023 20:09:36 +1000 Subject: [PATCH 21/22] fix cursor alignment following gamepad movement in stash align the cursor to the middle of the cell/region when moving through the stash to match the way we simulate mouse movement for inventory cells --- Source/controls/plrctrls.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index d10729598..947398b71 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -1167,9 +1167,9 @@ void StashMove(AxisDirection dir) if (ActiveStashSlot != InvalidStashPoint) { Point mousePos = GetStashSlotCoord(ActiveStashSlot); - if (pcurs == CURSOR_HAND) { - mousePos += Displacement { INV_SLOT_HALF_SIZE_PX, INV_SLOT_HALF_SIZE_PX }; - } + // Stash coordinates are all the top left of the cell, so we need to shift the mouse to the center of the held item + // or the center of the cell if we have a hand cursor (itemSize will be 1x1 here so we can use the same calculation) + mousePos += Displacement { itemSize.width * INV_SLOT_HALF_SIZE_PX, itemSize.height * INV_SLOT_HALF_SIZE_PX }; SetCursorPos(mousePos); return; } From bd8aab0aff5bb0ba26d2b958a1f4e55294f464ac Mon Sep 17 00:00:00 2001 From: ephphatha Date: Fri, 1 Sep 2023 20:10:09 +1000 Subject: [PATCH 22/22] handle stash item swapping using the gamepad a bit better --- Source/controls/plrctrls.cpp | 32 +++++++++++++++++++------------- Source/qol/stash.cpp | 4 +++- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index 947398b71..e26f6416d 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -1848,11 +1848,11 @@ void PerformPrimaryAction() } else if (IsStashOpen && GetLeftPanel().contains(MousePosition)) { Point stashSlot = (ActiveStashSlot != InvalidStashPoint) ? ActiveStashSlot : FindClosestStashSlot(MousePosition); - const Size cursorSizeInCells = MyPlayer->HoldItem.isEmpty() ? Size { 1, 1 } : GetInventorySize(MyPlayer->HoldItem); + Size cursorSizeInCells = MyPlayer->HoldItem.isEmpty() ? Size { 1, 1 } : GetInventorySize(MyPlayer->HoldItem); // Find any item occupying a slot that is currently under the cursor StashStruct::StashCell itemUnderCursor = [](Point stashSlot, Size cursorSizeInCells) -> StashStruct::StashCell { - if (stashSlot != InvalidStashPoint) + if (stashSlot == InvalidStashPoint) return StashStruct::EmptyCell; for (Point slotUnderCursor : PointsInRectangle(Rectangle { stashSlot, cursorSizeInCells })) { if (slotUnderCursor.x >= 10 || slotUnderCursor.y >= 10) @@ -1864,20 +1864,26 @@ void PerformPrimaryAction() return StashStruct::EmptyCell; }(stashSlot, cursorSizeInCells); - // The cursor will need to be shifted to - // this slot if the item is swapped or lifted - Point jumpSlot = FindFirstStashSlotOnItem(itemUnderCursor); + Point jumpSlot = itemUnderCursor == StashStruct::EmptyCell ? stashSlot : FindFirstStashSlotOnItem(itemUnderCursor); CheckStashItem(MousePosition); - // If we don't find the item in the same position as before, - // it suggests that the item was swapped or lifted - Point newSlot = FindFirstStashSlotOnItem(itemUnderCursor); - if (jumpSlot != InvalidStashPoint && jumpSlot != newSlot) { - Point mousePos = GetStashSlotCoord(jumpSlot); - mousePos.y -= InventorySlotSizeInPixels.height; - ActiveStashSlot = jumpSlot; - SetCursorPos(mousePos); + Point mousePos = GetStashSlotCoord(jumpSlot); + ActiveStashSlot = jumpSlot; + if (MyPlayer->HoldItem.isEmpty()) { + // For inventory cut/paste we can combine the cases where we swap or simply paste items. Because stash movement is always cell based (there's no fast + // movement over large items) it looks better if we offset the hand cursor to the bottom right cell of the item we just placed. + ActiveStashSlot += Displacement { cursorSizeInCells - 1 }; // shift the active stash slot coordinates to account for items larger than 1x1 + // Then we displace the mouse position to the bottom right corner of the item, then shift it back half a cell to center it. + // Could also be written as (cursorSize - 1) * InventorySlotSize + HalfInventorySlotSize, same thing in the end. + mousePos += Displacement { cursorSizeInCells } * Displacement { InventorySlotSizeInPixels } - Displacement { InventorySlotSizeInPixels } / 2; + } else { + // If we've picked up an item then use the same logic as the inventory so that the cursor is offset to the center of where the old item location was + // (in this case jumpSlot was the top left cell of where it used to be in the grid, and we need to update the cursor size since we're now holding the item) + cursorSizeInCells = GetInventorySize(MyPlayer->HoldItem); + mousePos.x += ((cursorSizeInCells.width) * InventorySlotSizeInPixels.width) / 2; + mousePos.y += ((cursorSizeInCells.height) * InventorySlotSizeInPixels.height) / 2; } + SetCursorPos(mousePos); } return; } diff --git a/Source/qol/stash.cpp b/Source/qol/stash.cpp index 681f51e1b..6707d96dd 100644 --- a/Source/qol/stash.cpp +++ b/Source/qol/stash.cpp @@ -158,8 +158,9 @@ void CheckStashPaste(Point cursorPosition) // stashList will have at most 10 000 items, up to 65 535 are supported with uint16_t indexes stashIndex = static_cast(Stash.stashList.size() - 1); } else { - // remove item from stash grid + // swap the held item and whatever was in the stash at this position std::swap(Stash.stashList[stashIndex], player.HoldItem); + // then clear the space occupied by the old item for (auto &row : Stash.GetCurrentGrid()) { for (auto &itemId : row) { if (itemId - 1 == stashIndex) @@ -168,6 +169,7 @@ void CheckStashPaste(Point cursorPosition) } } + // Finally mark the area now occupied by the pasted item in the current page/grid. AddItemToStashGrid(Stash.GetPage(), firstSlot, stashIndex, itemSize); Stash.dirty = true;