Files
mtgodot-poc/tools/server/inventory-5-pages/server.patch
T
shenleiandClaude Opus 5.5 e33e41c12b 背包扩展到 5 页:服务端补丁 + DB 迁移 + 客户端 mt_inventory
- GameType.h: c_Inventory_Page_Count 2 -> 5; SQuickSlot.Position BYTE -> WORD
- quickslot 包/接口 pos 改为 WORD(CG/GC 5 字节),wire_classic 同步
- mt_inventory PORT 模块:inventorywindow.py 装备格 90 -> EQUIPMENT_SLOT_START 重映射、5 个小页签、
  自动药水高亮与 SelectItemWindow 扫描全部 5 页;40250 脚本保持不变
- tools/server/inventory-5-pages: server.patch(格子布局 +135、quickslot WORD)、migrate_db.py、README

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:37:44 +09:00

363 lines
13 KiB
Diff

diff --git a/server/common/length.h b/server/common/length.h
index fba03e3..f4cafc3 100755
--- a/server/common/length.h
+++ b/server/common/length.h
@@ -12,7 +12,9 @@ enum EMisc
ACCOUNT_STATUS_MAX_LEN = 8,
CHARACTER_NAME_MAX_LEN = 24,
SHOP_SIGN_MAX_LEN = 32,
- INVENTORY_MAX_NUM = 90,
+ INVENTORY_PAGE_SIZE = 45, // 5 x 9
+ INVENTORY_PAGE_COUNT = 5, // MT: 40250 ships 2 pages (90 cells)
+ INVENTORY_MAX_NUM = INVENTORY_PAGE_SIZE * INVENTORY_PAGE_COUNT,
ABILITY_MAX_NUM = 50,
EMPIRE_MAX_NUM = 4,
BANWORD_MAX_LEN = 24,
@@ -87,15 +89,15 @@ enum EMisc
**** 현재까지 할당 된 아이템 영역 정리 (DB상 Item Position) ****
+------------------------------------------------------+ 0
| 캐릭터 기본 인벤토리 (45칸 * 2페이지) 90칸 |
- +------------------------------------------------------+ 90 = INVENTORY_MAX_NUM(90)
+ +------------------------------------------------------+ 225 = INVENTORY_MAX_NUM(225) MT: 5 pages; 40250 had 90 and every cell below moved by +135
| 캐릭터 장비 창 (착용중인 아이템) 32칸 |
- +------------------------------------------------------+ 122 = INVENTORY_MAX_NUM(90) + WEAR_MAX_NUM(32)
+ +------------------------------------------------------+ 257 = INVENTORY_MAX_NUM(225) + WEAR_MAX_NUM(32)
| 용혼석 장비 창 (착용중인 용혼석) 12칸 |
- +------------------------------------------------------+ 134 = 122 + DS_SLOT_MAX(6) * DRAGON_SOUL_DECK_MAX_NUM(2)
+ +------------------------------------------------------+ 269 = 257 + DS_SLOT_MAX(6) * DRAGON_SOUL_DECK_MAX_NUM(2)
| 용혼석 장비 창 예약 (아직 미사용) 18칸 |
- +------------------------------------------------------+ 152 = 134 + DS_SLOT_MAX(6) * DRAGON_SOUL_DECK_RESERVED_MAX_NUM(3)
+ +------------------------------------------------------+ 287 = 269 + DS_SLOT_MAX(6) * DRAGON_SOUL_DECK_RESERVED_MAX_NUM(3)
| 벨트 인벤토리 (벨트 착용시에만 벨트 레벨에 따라 활성)|
- +------------------------------------------------------+ 168 = 152 + BELT_INVENTORY_SLOT_COUNT(16) = INVENTORY_AND_EQUIP_CELL_MAX
+ +------------------------------------------------------+ 303 = 287 + BELT_INVENTORY_SLOT_COUNT(16) = INVENTORY_AND_EQUIP_CELL_MAX
| 미사용 |
+------------------------------------------------------+ ??
*/
diff --git a/server/common/tables.h b/server/common/tables.h
index 6f7adac..8767f3d 100755
--- a/server/common/tables.h
+++ b/server/common/tables.h
@@ -403,7 +403,7 @@ typedef struct SPlayerItem
typedef struct SQuickslot
{
BYTE type;
- BYTE pos;
+ WORD pos; // MT: BYTE in 40250; belt cells are >= 256 with 5 inventory pages
} TQuickslot;
typedef struct SPlayerSkill
diff --git a/server/game/src/char.h b/server/game/src/char.h
index 6eca64e..a6cba74 100755
--- a/server/game/src/char.h
+++ b/server/game/src/char.h
@@ -395,7 +395,7 @@ typedef struct character_point_instant
WORD parts[PART_MAX_NUM];
LPITEM pItems[INVENTORY_AND_EQUIP_SLOT_MAX];
- BYTE bItemGrid[INVENTORY_AND_EQUIP_SLOT_MAX];
+ WORD bItemGrid[INVENTORY_AND_EQUIP_SLOT_MAX]; // MT: BYTE in 40250; holds cell + 1, and cells reach 302
// 용혼석 인벤토리.
LPITEM pDSItems[DRAGON_SOUL_INVENTORY_MAX_NUM];
@@ -877,12 +877,12 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider
// Quickslot 관련
public:
- void SyncQuickslot(BYTE bType, BYTE bOldPos, BYTE bNewPos);
+ void SyncQuickslot(BYTE bType, WORD bOldPos, WORD bNewPos);
bool GetQuickslot(BYTE pos, TQuickslot ** ppSlot);
bool SetQuickslot(BYTE pos, TQuickslot & rSlot);
bool DelQuickslot(BYTE pos);
bool SwapQuickslot(BYTE a, BYTE b);
- void ChainQuickslotItem(LPITEM pItem, BYTE bType, BYTE bOldPos);
+ void ChainQuickslotItem(LPITEM pItem, BYTE bType, WORD bOldPos);
protected:
TQuickslot m_quickslot[QUICKSLOT_MAX_NUM];
@@ -1120,7 +1120,7 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider
// 착용중인 item을 벗을 수 있는 지 확인하고, 불가능 하다면 캐릭터에게 이유를 알려주는 함수
bool CanUnequipNow(const LPITEM item, const TItemPos& srcCell = NPOS, const TItemPos& destCell = NPOS);
- bool SwapItem(BYTE bCell, BYTE bDestCell);
+ bool SwapItem(WORD bCell, WORD bDestCell);
LPITEM AutoGiveItem(DWORD dwItemVnum, BYTE bCount=1, int iRarePct = -1, bool bMsg = true);
void AutoGiveItem(LPITEM item, bool longOwnerShip = false);
diff --git a/server/game/src/char_item.cpp b/server/game/src/char_item.cpp
index 0c2403f..a77696c 100755
--- a/server/game/src/char_item.cpp
+++ b/server/game/src/char_item.cpp
@@ -523,7 +523,7 @@ bool CHARACTER::IsEmptyItemGrid(TItemPos Cell, BYTE bSize, int iExceptionCell) c
{
case INVENTORY:
{
- BYTE bCell = Cell.cell;
+ WORD bCell = Cell.cell;
// bItemCell은 0이 false임을 나타내기 위해 + 1 해서 처리한다.
// 따라서 iExceptionCell에 1을 더해 비교한다.
@@ -562,16 +562,16 @@ bool CHARACTER::IsEmptyItemGrid(TItemPos Cell, BYTE bSize, int iExceptionCell) c
return true;
int j = 1;
- BYTE bPage = bCell / (INVENTORY_MAX_NUM / 2);
+ int bPage = bCell / INVENTORY_PAGE_SIZE;
do
{
- BYTE p = bCell + (5 * j);
+ int p = bCell + (5 * j);
if (p >= INVENTORY_MAX_NUM)
return false;
- if (p / (INVENTORY_MAX_NUM / 2) != bPage)
+ if (p / INVENTORY_PAGE_SIZE != bPage)
return false;
if (m_pointsInstant.bItemGrid[p])
@@ -592,16 +592,16 @@ bool CHARACTER::IsEmptyItemGrid(TItemPos Cell, BYTE bSize, int iExceptionCell) c
else
{
int j = 1;
- BYTE bPage = bCell / (INVENTORY_MAX_NUM / 2);
+ int bPage = bCell / INVENTORY_PAGE_SIZE;
do
{
- BYTE p = bCell + (5 * j);
+ int p = bCell + (5 * j);
if (p >= INVENTORY_MAX_NUM)
return false;
- if (p / (INVENTORY_MAX_NUM / 2) != bPage)
+ if (p / INVENTORY_PAGE_SIZE != bPage)
return false;
if (m_pointsInstant.bItemGrid[p])
@@ -924,7 +924,7 @@ bool CHARACTER::DoRefine(LPITEM item, bool bMoneyOnly)
ITEM_MANAGER::CopyAllAttrTo(item, pkNewItem);
LogManager::instance().ItemLog(this, pkNewItem, "REFINE SUCCESS", pkNewItem->GetName());
- BYTE bCell = item->GetCell();
+ WORD bCell = item->GetCell();
// DETAIL_REFINE_LOG
NotifyRefineSuccess(this, item, IsRefineThroughGuild() ? "GUILD" : "POWER");
@@ -1197,7 +1197,7 @@ bool CHARACTER::DoRefineWithScroll(LPITEM item)
ITEM_MANAGER::CopyAllAttrTo(item, pkNewItem);
LogManager::instance().ItemLog(this, pkNewItem, "REFINE SUCCESS", pkNewItem->GetName());
- BYTE bCell = item->GetCell();
+ WORD bCell = item->GetCell();
NotifyRefineSuccess(this, item, szRefineType);
DBManager::instance().SendMoneyLog(MONEY_LOG_REFINE, item->GetVnum(), -prt->cost);
@@ -1226,7 +1226,7 @@ bool CHARACTER::DoRefineWithScroll(LPITEM item)
ITEM_MANAGER::CopyAllAttrTo(item, pkNewItem);
LogManager::instance().ItemLog(this, pkNewItem, "REFINE FAIL", pkNewItem->GetName());
- BYTE bCell = item->GetCell();
+ WORD bCell = item->GetCell();
DBManager::instance().SendMoneyLog(MONEY_LOG_REFINE, item->GetVnum(), -prt->cost);
NotifyRefineFail(this, item, szRefineType, -1);
@@ -5982,7 +5982,7 @@ bool CHARACTER::PickupItem(DWORD dwVID)
return false;
}
-bool CHARACTER::SwapItem(BYTE bCell, BYTE bDestCell)
+bool CHARACTER::SwapItem(WORD bCell, WORD bDestCell)
{
if (!CanHandleItem())
return false;
@@ -6034,7 +6034,7 @@ bool CHARACTER::SwapItem(BYTE bCell, BYTE bDestCell)
if (TItemPos(EQUIPMENT, item2->GetCell()).IsEquipPosition())
{
BYTE bEquipCell = item2->GetCell() - INVENTORY_MAX_NUM;
- BYTE bInvenCell = item1->GetCell();
+ WORD bInvenCell = item1->GetCell();
// 착용중인 아이템을 벗을 수 있고, 착용 예정 아이템이 착용 가능한 상태여야만 진행
if (false == CanUnequipNow(item2) || false == CanEquipNow(item1))
@@ -6052,8 +6052,8 @@ bool CHARACTER::SwapItem(BYTE bCell, BYTE bDestCell)
}
else
{
- BYTE bCell1 = item1->GetCell();
- BYTE bCell2 = item2->GetCell();
+ WORD bCell1 = item1->GetCell();
+ WORD bCell2 = item2->GetCell();
item1->RemoveFromCharacter();
item2->RemoveFromCharacter();
@@ -6182,7 +6182,7 @@ bool CHARACTER::EquipItem(LPITEM item, int iCandidateCell)
}
else
{
- BYTE bOldCell = item->GetCell();
+ WORD bOldCell = item->GetCell();
if (item->EquipTo(this, iWearCell))
{
diff --git a/server/game/src/char_quickslot.cpp b/server/game/src/char_quickslot.cpp
index 884cd3b..4894fbb 100755
--- a/server/game/src/char_quickslot.cpp
+++ b/server/game/src/char_quickslot.cpp
@@ -9,7 +9,7 @@
/////////////////////////////////////////////////////////////////////////////
// QUICKSLOT HANDLING
/////////////////////////////////////////////////////////////////////////////
-void CHARACTER::SyncQuickslot(BYTE bType, BYTE bOldPos, BYTE bNewPos) // bNewPos == 255 면 DELETE
+void CHARACTER::SyncQuickslot(BYTE bType, WORD bOldPos, WORD bNewPos) // bNewPos == 255 면 DELETE
{
if (bOldPos == bNewPos)
return;
@@ -135,7 +135,7 @@ bool CHARACTER::SwapQuickslot(BYTE a, BYTE b)
return true;
}
-void CHARACTER::ChainQuickslotItem(LPITEM pItem, BYTE bType, BYTE bOldPos)
+void CHARACTER::ChainQuickslotItem(LPITEM pItem, BYTE bType, WORD bOldPos)
{
if (pItem->IsDragonSoul())
return;
diff --git a/server/game/src/exchange.cpp b/server/game/src/exchange.cpp
index a59452d..c3229f0 100755
--- a/server/game/src/exchange.cpp
+++ b/server/game/src/exchange.cpp
@@ -299,30 +299,27 @@ bool CExchange::Check(int * piItemCount)
bool CExchange::CheckSpace()
{
- static CGrid s_grid1(5, INVENTORY_MAX_NUM/5 / 2); // inven page 1
- static CGrid s_grid2(5, INVENTORY_MAX_NUM/5 / 2); // inven page 2
+ // MT: one grid per inventory page (40250 had s_grid1/s_grid2 for its 2 pages)
+ static CGrid * s_grids[INVENTORY_PAGE_COUNT];
- s_grid1.Clear();
- s_grid2.Clear();
+ for (int page = 0; page < INVENTORY_PAGE_COUNT; ++page)
+ {
+ if (!s_grids[page])
+ s_grids[page] = new CGrid(5, INVENTORY_PAGE_SIZE / 5);
+ s_grids[page]->Clear();
+ }
LPCHARACTER victim = GetCompany()->GetOwner();
LPITEM item;
int i;
- for (i = 0; i < INVENTORY_MAX_NUM / 2; ++i)
- {
- if (!(item = victim->GetInventoryItem(i)))
- continue;
-
- s_grid1.Put(i, 1, item->GetSize());
- }
- for (i = INVENTORY_MAX_NUM / 2; i < INVENTORY_MAX_NUM; ++i)
+ for (i = 0; i < INVENTORY_MAX_NUM; ++i)
{
if (!(item = victim->GetInventoryItem(i)))
continue;
- s_grid2.Put(i - INVENTORY_MAX_NUM / 2, 1, item->GetSize());
+ s_grids[i / INVENTORY_PAGE_SIZE]->Put(i % INVENTORY_PAGE_SIZE, 1, item->GetSize());
}
// 아... 뭔가 개병신 같지만... 용혼석 인벤을 노멀 인벤 보고 따라 만든 내 잘못이다 ㅠㅠ
@@ -387,25 +384,21 @@ bool CExchange::CheckSpace()
}
else
{
- int iPos = s_grid1.FindBlank(1, item->GetSize());
+ bool bPut = false;
- if (iPos >= 0)
+ for (int page = 0; page < INVENTORY_PAGE_COUNT && !bPut; ++page)
{
- s_grid1.Put(iPos, 1, item->GetSize());
- }
- else
- {
- iPos = s_grid2.FindBlank(1, item->GetSize());
+ int iPos = s_grids[page]->FindBlank(1, item->GetSize());
if (iPos >= 0)
{
- s_grid2.Put(iPos, 1, item->GetSize());
- }
- else
- {
- return false;
+ s_grids[page]->Put(iPos, 1, item->GetSize());
+ bPut = true;
}
}
+
+ if (!bPut)
+ return false;
}
}
diff --git a/server/game/src/fishing.cpp b/server/game/src/fishing.cpp
index b240401..20b2a21 100755
--- a/server/game/src/fishing.cpp
+++ b/server/game/src/fishing.cpp
@@ -877,7 +877,7 @@ int RealRefineRod(LPCHARACTER ch, LPITEM item)
if (pkNewItem)
{
- BYTE bCell = rod->GetCell();
+ WORD bCell = rod->GetCell();
// 낚시대 개량 성공
ITEM_MANAGER::instance().RemoveItem(rod, "REMOVE (REFINE FISH_ROD)");
pkNewItem->AddToCharacter(ch, TItemPos (INVENTORY, bCell));
@@ -895,7 +895,7 @@ int RealRefineRod(LPCHARACTER ch, LPITEM item)
LPITEM pkNewItem = ITEM_MANAGER::instance().CreateItem(rod->GetValue(4), 1);
if (pkNewItem)
{
- BYTE bCell = rod->GetCell();
+ WORD bCell = rod->GetCell();
// 낚시대 개량에 성공
ITEM_MANAGER::instance().RemoveItem(rod, "REMOVE (REFINE FISH_ROD)");
pkNewItem->AddToCharacter(ch, TItemPos(INVENTORY, bCell));
diff --git a/server/game/src/mining.cpp b/server/game/src/mining.cpp
index f1ea8a6..b6dd7b8 100755
--- a/server/game/src/mining.cpp
+++ b/server/game/src/mining.cpp
@@ -253,7 +253,7 @@ namespace mining
LPITEM pkNewPick = ITEM_MANAGER::instance().CreateItem(rkOldPick.GetRefinedVnum(), 1);
if (pkNewPick)
{
- BYTE bCell = rkOldPick.GetCell();
+ WORD bCell = rkOldPick.GetCell();
rkItemMgr.RemoveItem(item, "REMOVE (REFINE PICK)");
pkNewPick->AddToCharacter(ch, TItemPos(INVENTORY, bCell));
LogManager::instance().ItemLog(ch, pkNewPick, "REFINE PICK SUCCESS", pkNewPick->GetName());
@@ -270,7 +270,7 @@ namespace mining
if (pkNewPick)
{
- BYTE bCell = rkOldPick.GetCell();
+ WORD bCell = rkOldPick.GetCell();
rkItemMgr.RemoveItem(item, "REMOVE (REFINE PICK)");
pkNewPick->AddToCharacter(ch, TItemPos(INVENTORY, bCell));
rkLogMgr.ItemLog(ch, pkNewPick, "REFINE PICK FAIL", pkNewPick->GetName());
diff --git a/server/game/src/questlua_item.cpp b/server/game/src/questlua_item.cpp
index befc676..23200e8 100755
--- a/server/game/src/questlua_item.cpp
+++ b/server/game/src/questlua_item.cpp
@@ -436,7 +436,7 @@ namespace quest
ITEM_MANAGER::CopyAllAttrTo(pItem, pkNewItem);
LogManager::instance().ItemLog(pChar, pkNewItem, "COPY SUCCESS", pkNewItem->GetName());
- BYTE bCell = pItem->GetCell();
+ WORD bCell = pItem->GetCell();
ITEM_MANAGER::instance().RemoveItem(pItem, "REMOVE (COPY SUCCESS)");