diff --git a/Source/inv.cpp b/Source/inv.cpp index e2ecbafe6..138b220f1 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -155,13 +155,17 @@ void AddItemToInvGrid(Player &player, int invGridIndex, int invListIndex, Size i { const int pitch = 10; for (int y = 0; y < itemSize.height; y++) { + int rowGridIndex = invGridIndex + pitch * y; for (int x = 0; x < itemSize.width; x++) { if (x == 0 && y == itemSize.height - 1) - player.InvGrid[invGridIndex + x] = invListIndex; + player.InvGrid[rowGridIndex + x] = invListIndex; else - player.InvGrid[invGridIndex + x] = -invListIndex; + player.InvGrid[rowGridIndex + x] = -invListIndex; } - invGridIndex += pitch; + } + + if (&player == MyPlayer) { + NetSendCmdChInvItem(false, invGridIndex); } } @@ -556,6 +560,9 @@ void CheckInvPaste(Player &player, Point cursorPosition) if (player.HoldItem._itype == ItemType::Gold) player._pGold = CalculateGold(player); } + if (&player == MyPlayer) { + NetSendCmdChBeltItem(false, ii); + } drawsbarflag = true; } break; case ILOC_NONE: @@ -799,8 +806,7 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool } if (!automaticMove || automaticallyMoved) { - beltItem.clear(); - drawsbarflag = true; + player.RemoveSpdBarItem(r - SLOTXY_BELT_FIRST); } } } @@ -1233,6 +1239,10 @@ bool AutoPlaceItemInBelt(Player &player, const Item &item, bool persistItem) beltItem = item; player.CalcScrolls(); drawsbarflag = true; + if (&player == MyPlayer) { + size_t beltIndex = std::distance(&player.SpdList[0], &beltItem); + NetSendCmdChBeltItem(false, beltIndex); + } } return true; @@ -1469,6 +1479,75 @@ void inv_update_rem_item(Player &player, inv_body_loc iv) CalcPlrInv(player, player._pmode != PM_DEATH); } +void CheckInvSwap(Player &player, int invGridIndex, int idx, uint16_t wCI, int seed, bool bId, uint32_t dwBuff) +{ + Item item = {}; + RecreateItem(item, idx, wCI, seed, 0, (dwBuff & CF_HELLFIRE) != 0); + + if (bId) { + item._iIdentified = true; + } + + auto itemSize = GetInventorySize(item); + + const int pitch = 10; + int invListIndex = [&]() -> int { + for (int y = 0; y < itemSize.height; y++) { + int rowGridIndex = invGridIndex + pitch * y; + for (int x = 0; x < itemSize.width; x++) { + if (player.InvGrid[rowGridIndex + x] != 0) + return abs(player.InvGrid[rowGridIndex]); + } + } + player._pNumInv++; + return player._pNumInv; + }(); + + if (invListIndex < player._pNumInv) { + for (auto &itemIndex : player.InvGrid) { + if (itemIndex == invListIndex) + itemIndex = 0; + if (itemIndex == -invListIndex) + itemIndex = 0; + } + } + + player.InvList[invListIndex - 1] = item; + + for (int y = 0; y < itemSize.height; y++) { + int rowGridIndex = invGridIndex + pitch * y; + for (int x = 0; x < itemSize.width; x++) { + if (x == 0 && y == itemSize.height - 1) + player.InvGrid[rowGridIndex + x] = invListIndex; + else + player.InvGrid[rowGridIndex + x] = -invListIndex; + } + } + + CalcPlrInv(player, true); +} + +void CheckInvRemove(Player &player, int invGridIndex) +{ + int invListIndex = abs(player.InvGrid[invGridIndex]) - 1; + + if (invListIndex >= 0) { + player.RemoveInvItem(invListIndex); + } +} + +void CheckBeltSwap(Player &player, int beltIndex, int idx, uint16_t wCI, int seed, bool bId, uint32_t dwBuff) +{ + Item &item = player.SpdList[beltIndex]; + + item = {}; + RecreateItem(item, idx, wCI, seed, 0, (dwBuff & CF_HELLFIRE) != 0); + + if (bId) { + item._iIdentified = true; + } +} + void TransferItemToStash(Player &player, int location) { if (location == -1) { diff --git a/Source/inv.h b/Source/inv.h index 89c64b648..226983e2e 100644 --- a/Source/inv.h +++ b/Source/inv.h @@ -182,6 +182,9 @@ int AddGoldToInventory(Player &player, int value); bool GoldAutoPlace(Player &player, Item &goldStack); void CheckInvSwap(Player &player, inv_body_loc bLoc, int idx, uint16_t wCI, int seed, bool bId, uint32_t dwBuff); void inv_update_rem_item(Player &player, inv_body_loc iv); +void CheckInvSwap(Player &player, int invGridIndex, int idx, uint16_t wCI, int seed, bool bId, uint32_t dwBuff); +void CheckInvRemove(Player &player, int invGridIndex); +void CheckBeltSwap(Player &player, int beltIndex, int idx, uint16_t wCI, int seed, bool bId, uint32_t dwBuff); void TransferItemToStash(Player &player, int location); void CheckInvItem(bool isShiftHeld = false, bool isCtrlHeld = false); diff --git a/Source/msg.cpp b/Source/msg.cpp index c7a184738..aec5c7db0 100644 --- a/Source/msg.cpp +++ b/Source/msg.cpp @@ -1739,6 +1739,62 @@ size_t OnDeletePlayerItems(const TCmd *pCmd, int pnum) return sizeof(message); } +size_t OnChangeInventoryItems(const TCmd *pCmd, int pnum) +{ + const auto &message = *reinterpret_cast(pCmd); + Player &player = Players[pnum]; + + if (gbBufferMsgs == 1) { + SendPacket(pnum, &message, sizeof(message)); + } else if (&player != MyPlayer && message.bLoc < InventoryGridCells && message.wIndx <= IDI_LAST) { + CheckInvSwap(player, message.bLoc, message.wIndx, message.wCI, message.dwSeed, message.bId != 0, message.dwBuff); + } + + return sizeof(message); +} + +size_t OnDeleteInventoryItems(const TCmd *pCmd, int pnum) +{ + const auto &message = *reinterpret_cast(pCmd); + Player &player = Players[pnum]; + + if (gbBufferMsgs == 1) { + SendPacket(pnum, &message, sizeof(message)); + } else if (&player != MyPlayer && message.wParam1 < InventoryGridCells) { + CheckInvRemove(player, message.wParam1); + } + + return sizeof(message); +} + +size_t OnChangeBeltItems(const TCmd *pCmd, int pnum) +{ + const auto &message = *reinterpret_cast(pCmd); + Player &player = Players[pnum]; + + if (gbBufferMsgs == 1) { + SendPacket(pnum, &message, sizeof(message)); + } else if (&player != MyPlayer && message.bLoc < MaxBeltItems && message.wIndx <= IDI_LAST) { + CheckBeltSwap(player, message.bLoc, message.wIndx, message.wCI, message.dwSeed, message.bId != 0, message.dwBuff); + } + + return sizeof(message); +} + +size_t OnDeleteBeltItems(const TCmd *pCmd, int pnum) +{ + const auto &message = *reinterpret_cast(pCmd); + Player &player = Players[pnum]; + + if (gbBufferMsgs == 1) { + SendPacket(pnum, &message, sizeof(message)); + } else if (&player != MyPlayer && message.wParam1 < MaxBeltItems) { + player.RemoveSpdBarItem(message.wParam1); + } + + return sizeof(message); +} + size_t OnPlayerLevel(const TCmd *pCmd, int pnum) { const auto &message = *reinterpret_cast(pCmd); @@ -2893,6 +2949,47 @@ void NetSendCmdDelItem(bool bHiPri, uint8_t bLoc) NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); } +void NetSendCmdChInvItem(bool bHiPri, int invGridIndex) +{ + TCmdChItem cmd; + + int8_t invListIndex = abs(MyPlayer->InvGrid[invGridIndex]) - 1; + const Item &item = MyPlayer->InvList[invListIndex]; + + cmd.bCmd = CMD_CHANGEINVITEMS; + cmd.bLoc = invGridIndex; + cmd.wIndx = item.IDidx; + cmd.wCI = item._iCreateInfo; + cmd.dwSeed = item._iSeed; + cmd.bId = item._iIdentified ? 1 : 0; + cmd.dwBuff = item.dwBuff; + + if (bHiPri) + NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + else + NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); +} + +void NetSendCmdChBeltItem(bool bHiPri, int beltIndex) +{ + TCmdChItem cmd; + + const Item &item = MyPlayer->SpdList[beltIndex]; + + cmd.bCmd = CMD_CHANGEBELTITEMS; + cmd.bLoc = beltIndex; + cmd.wIndx = item.IDidx; + cmd.wCI = item._iCreateInfo; + cmd.dwSeed = item._iSeed; + cmd.bId = item._iIdentified ? 1 : 0; + cmd.dwBuff = item.dwBuff; + + if (bHiPri) + NetSendHiPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); + else + NetSendLoPri(MyPlayerId, (byte *)&cmd, sizeof(cmd)); +} + void NetSendCmdDamage(bool bHiPri, uint8_t bPlr, uint32_t dwDam) { TCmdDamage cmd; @@ -3047,6 +3144,14 @@ size_t ParseCmd(int pnum, const TCmd *pCmd) return OnChangePlayerItems(pCmd, pnum); case CMD_DELPLRITEMS: return OnDeletePlayerItems(pCmd, pnum); + case CMD_CHANGEINVITEMS: + return OnChangeInventoryItems(pCmd, pnum); + case CMD_DELINVITEMS: + return OnDeleteInventoryItems(pCmd, pnum); + case CMD_CHANGEBELTITEMS: + return OnChangeBeltItems(pCmd, pnum); + case CMD_DELBELTITEMS: + return OnDeleteBeltItems(pCmd, pnum); case CMD_PLRLEVEL: return OnPlayerLevel(pCmd, pnum); case CMD_DROPITEM: diff --git a/Source/msg.h b/Source/msg.h index c13f8b8b1..5d223d82e 100644 --- a/Source/msg.h +++ b/Source/msg.h @@ -281,6 +281,22 @@ enum _cmd_id : uint8_t { // // body (TCmdDelItem) CMD_DELPLRITEMS, + // Put item into player's backpack. + // + // body (TCmdChItem) + CMD_CHANGEINVITEMS, + // Remove item from player's backpack. + // + // body (TCmdParam1) + CMD_DELINVITEMS, + // Put item into player's belt. + // + // body (TCmdChItem) + CMD_CHANGEBELTITEMS, + // Remove item from player's belt. + // + // body (TCmdParam1) + CMD_DELBELTITEMS, // Damage target player. // // body (TCmdDamage) @@ -743,6 +759,8 @@ void NetSendCmdGItem(bool bHiPri, _cmd_id bCmd, uint8_t pnum, uint8_t ii); void NetSendCmdPItem(bool bHiPri, _cmd_id bCmd, Point position, const Item &item); void NetSendCmdChItem(bool bHiPri, uint8_t bLoc); void NetSendCmdDelItem(bool bHiPri, uint8_t bLoc); +void NetSendCmdChInvItem(bool bHiPri, int invGridIndex); +void NetSendCmdChBeltItem(bool bHiPri, int invGridIndex); void NetSendCmdDamage(bool bHiPri, uint8_t bPlr, uint32_t dwDam); void NetSendCmdMonDmg(bool bHiPri, uint16_t wMon, uint32_t dwDam); void NetSendCmdString(uint32_t pmask, const char *pszStr); diff --git a/Source/player.cpp b/Source/player.cpp index 032baf97e..1a114dd5a 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -1775,6 +1775,17 @@ void Player::CalcScrolls() void Player::RemoveInvItem(int iv, bool calcScrolls) { + if (this == MyPlayer) { + // Locate the first grid index containing this item and notify remote clients + for (size_t i = 0; i < InventoryGridCells; i++) { + int8_t itemIndex = InvGrid[i]; + if (abs(itemIndex) - 1 == iv) { + NetSendCmdParam1(false, CMD_DELINVITEMS, i); + break; + } + } + } + // Iterate through invGrid and remove every reference to item for (int8_t &itemIndex : InvGrid) { if (abs(itemIndex) - 1 == iv) { @@ -1807,6 +1818,10 @@ void Player::RemoveInvItem(int iv, bool calcScrolls) void Player::RemoveSpdBarItem(int iv) { + if (this == MyPlayer) { + NetSendCmdParam1(false, CMD_DELBELTITEMS, iv); + } + SpdList[iv].clear(); CalcScrolls(); diff --git a/Source/storm/storm_net.cpp b/Source/storm/storm_net.cpp index 59b0805da..548a39fa8 100644 --- a/Source/storm/storm_net.cpp +++ b/Source/storm/storm_net.cpp @@ -9,6 +9,7 @@ #endif #include "dvlnet/abstract_net.h" +#include "engine/demomode.h" #include "menu.h" #include "options.h" #include "utils/stubs.h" @@ -142,7 +143,7 @@ bool SNetInitializeProvider(uint32_t provider, struct GameData *gameData) std::lock_guard lg(storm_net_mutex); #endif dvlnet_inst = net::abstract_net::MakeNet(provider); - return mainmenu_select_hero_dialog(gameData); + return (HeadlessMode && !demo::IsRunning()) || mainmenu_select_hero_dialog(gameData); } /** diff --git a/test/inv_test.cpp b/test/inv_test.cpp index d7b155581..d764ed974 100644 --- a/test/inv_test.cpp +++ b/test/inv_test.cpp @@ -3,6 +3,7 @@ #include "cursor.h" #include "inv.h" #include "player.h" +#include "storm/storm_net.hpp" using namespace devilution; @@ -136,6 +137,8 @@ TEST(Inv, GoldAutoPlace) // Test removing an item from inventory with no other items. TEST(Inv, RemoveInvItem) { + SNetInitializeProvider(SELCONN_LOOPBACK, nullptr); + clear_inventory(); // Put a two-slot misc item into the inventory: // | (item) | (item) | ... | ... @@ -153,6 +156,8 @@ TEST(Inv, RemoveInvItem) // Test removing an item from inventory with other items in it. TEST(Inv, RemoveInvItem_other_item) { + SNetInitializeProvider(SELCONN_LOOPBACK, nullptr); + clear_inventory(); // Put a two-slot misc item and a ring into the inventory: // | (item) | (item) | (ring) | ... @@ -175,6 +180,8 @@ TEST(Inv, RemoveInvItem_other_item) // Test removing an item from the belt TEST(Inv, RemoveSpdBarItem) { + SNetInitializeProvider(SELCONN_LOOPBACK, nullptr); + // Clear the belt for (int i = 0; i < MaxBeltItems; i++) { MyPlayer->SpdList[i].clear(); @@ -206,6 +213,8 @@ TEST(Inv, RemoveCurrentSpellScroll_inventory) // Test removing a scroll from the belt TEST(Inv, RemoveCurrentSpellScroll_belt) { + SNetInitializeProvider(SELCONN_LOOPBACK, nullptr); + // Clear the belt for (int i = 0; i < MaxBeltItems; i++) { MyPlayer->SpdList[i].clear();