背包扩展到 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>
This commit is contained in:
shenlei
2026-09-29 22:37:44 +09:00
co-authored by Claude Opus 5.5
parent 22d534c5eb
commit e33e41c12b
13 changed files with 724 additions and 15 deletions
+39
View File
@@ -0,0 +1,39 @@
# 背包 5 页(服务端)
40250 背包是 2 页 × 45 格(`INVENTORY_MAX_NUM = 90`)。本补丁改为 5 页 225 格,客户端对应改动见
`src/port/UserInterface/GameType.h`(`c_Inventory_Page_Count`)和 `src/platform/ScriptLib/InventoryPagesScript.inc`。
## 格子布局
| 区段 | 40250 | 5 页 |
| --- | --- | --- |
| 背包 | 0..89 | 0..224 |
| 穿戴(32) | 90..121 | 225..256 |
| 龙魂装备(12) | 122..133 | 257..268 |
| 龙魂预留(18) | 134..151 | 269..286 |
| 腰带背包(16) | 152..167 | 287..302 |
背包之后的格子整体 +135,超过 255,所以 `TQuickslot.pos` 从 BYTE 改成 WORD(快捷栏封包变成 5 字节,
`player.quickslot` 从 72 字节变成 108 字节)。**旧客户端不兼容**,必须和新客户端一起上线。
## 文件
- `server.patch`:对 40250 `src/server` 的改动(`common/length.h`、`common/tables.h`、`game/src` 里把格子号存成 BYTE 的地方、
`exchange.cpp` 的每页空间检查)。在 `/usr/metin2/src` 下 `patch -p1 < server.patch`。源码是 EUC-KR + CRLF,补丁按字节保存。
- `migrate_db.py`:DB 迁移,停服后运行一次(`python3.9 migrate_db.py [--dry-run] [mysql 参数]`,默认 `-uroot player`)。
腰带物品(`window='INVENTORY'` 且 `pos` 152..167)+135;`player`/`player_deleted` 的 `quickslot` 转成 WORD 布局,
指向腰带格的物品快捷栏同样 +135。结果记入 `mt_migration` 表,重复运行不会再改。穿戴/龙魂装备按 EQUIPMENT 窗口的相对位置存,不受影响。
## 构建与部署(203,FreeBSD 13.2 i386)
Makefile 默认把二进制移到 `share/bin`(线上正在用),构建时要改输出目录:
```sh
cd /usr/metin2/src/server/game/src && gmake -j2 GAMEDIR=/root/inv5-out/new
cd ../../db/src && gmake -j2 DBDIR=/root/inv5-out/new
```
部署顺序:`close.sh` → 备份 `player` 库 → `migrate_db.py` → 覆盖 `share/bin/{game,db}` → `echo 1 | sh start.sh`。
auth 用的也是 `game` 二进制;`TPlayerTable` 含快捷栏数组,所以 game 和 db 必须一起换。
2026-09-29 已在 203 部署。备份在 `/root/backup-inv5/`(源码 tgz、原 game/db、迁移前的 player 库)。回滚:停服,恢复这三样。
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""Migrate the 40250 player DB to the 5-page bag (server.patch). Run once, with db/game stopped.
python3 migrate_db.py [--dry-run] [mysql args...] (default mysql args: -uroot player)
- Belt-inventory items are stored as window INVENTORY at absolute cells 152..167; they move to 287..302
(every cell after the bag moves by +135). Worn and dragon-soul-equipped items are stored relative to
their EQUIPMENT window and do not move.
- player.quickslot / player_deleted.quickslot hold TQuickslot[36] raw: {BYTE type; BYTE pos} (72 bytes)
becomes {BYTE type; WORD pos} (108 bytes). Item quick slots (type 1) on a belt cell get +135 too.
A row in table mt_migration records the run; a second run changes nothing.
"""
import subprocess
import sys
NAME = "inventory-5-pages"
QUICKSLOT_MAX_NUM = 36
QUICKSLOT_TYPE_ITEM = 1
OLD_BELT = range(152, 168)
SHIFT = 135
def mysql(args, sql):
out = subprocess.run(["mysql", "-N", "-B"] + args, input=sql, capture_output=True, text=True)
if out.returncode:
sys.exit("mysql failed: " + out.stderr.strip())
return [line.split("\t") for line in out.stdout.splitlines()]
def convert(blob):
assert len(blob) == QUICKSLOT_MAX_NUM * 2, len(blob)
out = bytearray()
for i in range(QUICKSLOT_MAX_NUM):
kind, pos = blob[2 * i], blob[2 * i + 1]
if kind == QUICKSLOT_TYPE_ITEM and pos in OLD_BELT:
pos += SHIFT
out += bytes([kind, pos & 0xFF, pos >> 8])
return bytes(out)
def main():
argv = sys.argv[1:]
dry = "--dry-run" in argv
args = [a for a in argv if a != "--dry-run"] or ["-uroot", "player"]
mysql(args, "CREATE TABLE IF NOT EXISTS mt_migration (name VARCHAR(64) PRIMARY KEY, done DATETIME)")
if mysql(args, "SELECT name FROM mt_migration WHERE name='%s'" % NAME):
print("%s: already applied" % NAME)
return
sql = ["UPDATE item SET pos = pos + %d WHERE window = 'INVENTORY' AND pos BETWEEN %d AND %d;"
% (SHIFT, OLD_BELT[0], OLD_BELT[-1])]
belt = mysql(args, "SELECT COUNT(*) FROM item WHERE window='INVENTORY' AND pos BETWEEN %d AND %d"
% (OLD_BELT[0], OLD_BELT[-1]))[0][0]
bad = mysql(args, "SELECT COUNT(*) FROM item WHERE window='INVENTORY' AND pos BETWEEN 90 AND 151")[0][0]
if bad != "0":
sys.exit("%s INVENTORY items at cells 90..151, which 40250 does not use; stop" % bad)
rows = 0
for table in ("player", "player_deleted"):
for pid, blob in mysql(args, "SELECT id, HEX(quickslot) FROM %s" % table):
data = bytes.fromhex(blob)
if not data:
continue
if len(data) != QUICKSLOT_MAX_NUM * 2:
sys.exit("%s %s: quickslot is %d bytes, expected %d" % (table, pid, len(data), QUICKSLOT_MAX_NUM * 2))
sql.append("UPDATE %s SET quickslot = X'%s' WHERE id = %s;" % (table, convert(data).hex().upper(), pid))
rows += 1
sql.append("INSERT INTO mt_migration VALUES ('%s', NOW());" % NAME)
print("%s: %s belt item(s), %d quickslot row(s)" % (NAME, belt, rows))
if dry:
print("\n".join(sql))
return
mysql(args, "\n".join(sql))
print("%s: applied" % NAME)
if __name__ == "__main__":
main()
+362
View File
@@ -0,0 +1,362 @@
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)");