背包扩展到 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:
co-authored by
Claude Opus 5.5
parent
22d534c5eb
commit
e33e41c12b
+4
-2
@@ -42,6 +42,7 @@ metin2-client 是 40250 Windows 客户端的跨平台版本。**首要目标是
|
|||||||
| 2026-09-28 | Android 原生客户端优先,iOS 延后 | 目标用户在手机上 |
|
| 2026-09-28 | Android 原生客户端优先,iOS 延后 | 目标用户在手机上 |
|
||||||
| 2026-09-28 | 代码页转换用内置 WindowsBestFit 表(`mt_codepage`),不再用 iconv | Android API 24 没有可用的 iconv;CP949/GBK 结果与 Windows 一致 |
|
| 2026-09-28 | 代码页转换用内置 WindowsBestFit 表(`mt_codepage`),不再用 iconv | Android API 24 没有可用的 iconv;CP949/GBK 结果与 Windows 一致 |
|
||||||
| 2026-09-29 | 移动端专有功能(触控、自动狩猎、账号注册、帧率设置)以 PORT 模块形式注入,不改 40250 脚本 | 40250 脚本字节保持不变,账本 `RUN_AS_IS` 仍成立 |
|
| 2026-09-29 | 移动端专有功能(触控、自动狩猎、账号注册、帧率设置)以 PORT 模块形式注入,不改 40250 脚本 | 40250 脚本字节保持不变,账本 `RUN_AS_IS` 仍成立 |
|
||||||
|
| 2026-09-29 | **背包扩到 5 页**(225 格,40250 为 2 页 90 格):服务端补丁 + DB 迁移在 `tools/server/inventory-5-pages/`,客户端 `c_Inventory_Page_Count = 5`、`TQuickSlot.Position` 改 WORD、`mt_inventory` 模块补 5 个页签和写死 2 页的脚本;装备/龙魂/腰带格整体后移 135,快捷栏封包长度变化,旧客户端不再兼容 | 用户要求的玩法扩展;203 已部署并实测 |
|
||||||
|
|
||||||
## 3. 三个层和代码位置
|
## 3. 三个层和代码位置
|
||||||
|
|
||||||
@@ -112,10 +113,11 @@ SDL3 宿主主循环(src/host/live_client.cpp run_live_client)
|
|||||||
的原写法改成 `uintptr_t`,在 port-map 的 `note` 里记录适配不变式。
|
的原写法改成 `uintptr_t`,在 port-map 的 `note` 里记录适配不变式。
|
||||||
- 单位保持 40250 的(`TPixelPosition` 用 cm,时间是 `ELTimer_GetMSec` 的毫秒,角度用度),只在适配层换算。
|
- 单位保持 40250 的(`TPixelPosition` 用 cm,时间是 `ELTimer_GetMSec` 的毫秒,角度用度),只在适配层换算。
|
||||||
|
|
||||||
PORT 扩展(40250 没有对应物,只为移动端/平台而加;不能改 40250 脚本和玩法语义):
|
PORT 扩展(40250 没有对应物,只为移动端/平台而加;不能改 40250 脚本;玩法语义只在用户明确要求时扩展,如 5 页背包):
|
||||||
|
|
||||||
- 由 `PythonBoot::RunMainScript` 注入的 Python 模块:`mt_autohunt`(自动狩猎,`AutoHuntScript.inc`)、`mt_register`
|
- 由 `PythonBoot::RunMainScript` 注入的 Python 模块:`mt_autohunt`(自动狩猎,`AutoHuntScript.inc`)、`mt_register`
|
||||||
(客户端内账号注册)、`mt_framerate`(系统选项里的"画面帧率"行)。它们包装 40250 窗口类的方法,不修改脚本文件。
|
(客户端内账号注册)、`mt_framerate`(系统选项里的"画面帧率"行)、`mt_inventory`(5 页背包:改写 `InventoryWindow.py` 的装备格起点和页签,
|
||||||
|
泛化翻页、自动药水高亮和选宝石窗口的格子扫描)。它们包装 40250 窗口类的方法,不修改脚本文件。
|
||||||
- 触控控制(`src/host/touch_controller.h`):摇杆、攻击/技能按钮,转成 40250 的输入接口。
|
- 触控控制(`src/host/touch_controller.h`):摇杆、攻击/技能按钮,转成 40250 的输入接口。
|
||||||
- 帧率与功耗(`platform/EterBase/FrameRateMode.h` + `src/host/frame_policy.cpp` 的 `FrameRatePolicy`):30 / 60(默认)/ 显示器最高三档,
|
- 帧率与功耗(`platform/EterBase/FrameRateMode.h` + `src/host/frame_policy.cpp` 的 `FrameRatePolicy`):30 / 60(默认)/ 显示器最高三档,
|
||||||
无输入一段时间降到 30 fps(自动狩猎时除外),Android 上配合 `setFrameRate` 和 ADPF。
|
无输入一段时间降到 30 fps(自动狩猎时除外),Android 上配合 `setFrameRate` 和 ADPF。
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
// The 5-page bag (PORT; 40250 has 2 pages of 45 cells), run by PythonBoot::RunMainScript as module
|
||||||
|
// mt_inventory. GameType.h's c_Inventory_Page_Count = 5 matches the 203 server patch
|
||||||
|
// (tools/server/inventory-5-pages), which moves every cell after the bag from 90 to 225. The 40250 scripts
|
||||||
|
// stay unchanged; this module patches what they hard-code for 2 pages:
|
||||||
|
// - InventoryWindow.py: EQUIPMENT_START_INDEX = 90 is remapped to player.EQUIPMENT_SLOT_START, and the two
|
||||||
|
// large page tabs become five small ones (the equipment tabs' image) across the 5x32 grid;
|
||||||
|
// - uiInventory.InventoryWindow: five tab events, SetInventoryPage's `inventoryTab[1-page]`, and the
|
||||||
|
// auto-potion highlight in RefreshBagSlotWindow, which only subtracts one page;
|
||||||
|
// - uiselectitem.SelectItemWindow.RefreshSlot: scans INVENTORY_PAGE_SIZE*2 cells.
|
||||||
|
static const char c_szInventoryPagesScript[] = R"PY(# -*- coding: utf-8 -*-
|
||||||
|
import constInfo
|
||||||
|
import player
|
||||||
|
import ui
|
||||||
|
import uiInventory
|
||||||
|
import uiselectitem
|
||||||
|
import item
|
||||||
|
|
||||||
|
OLD_EQUIPMENT_START = 90
|
||||||
|
OLD_EQUIPMENT_END = OLD_EQUIPMENT_START + 32
|
||||||
|
PAGE_COUNT = player.INVENTORY_PAGE_COUNT
|
||||||
|
PAGE_TEXTS = ("I", "II", "III", "IV", "V")
|
||||||
|
SMALL_TAB = "d:/ymir work/ui/game/windows/tab_button_small_%02d.sub"
|
||||||
|
TAB_X = 8
|
||||||
|
TAB_W = 32
|
||||||
|
|
||||||
|
|
||||||
|
def _Children(node):
|
||||||
|
return node.get("children", ())
|
||||||
|
|
||||||
|
|
||||||
|
def _Find(node, name):
|
||||||
|
if node.get("name") == name:
|
||||||
|
return node
|
||||||
|
for child in _Children(node):
|
||||||
|
found = _Find(child, name)
|
||||||
|
if found:
|
||||||
|
return found
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _PageTab(page, x, y, tooltip):
|
||||||
|
tab = {
|
||||||
|
"name" : "Inventory_Tab_%02d" % (page + 1),
|
||||||
|
"type" : "radio_button",
|
||||||
|
"x" : x,
|
||||||
|
"y" : y,
|
||||||
|
"default_image" : SMALL_TAB % 1,
|
||||||
|
"over_image" : SMALL_TAB % 2,
|
||||||
|
"down_image" : SMALL_TAB % 3,
|
||||||
|
"children" : (
|
||||||
|
{
|
||||||
|
"name" : "Inventory_Tab_%02d_Print" % (page + 1),
|
||||||
|
"type" : "text",
|
||||||
|
"x" : 0,
|
||||||
|
"y" : 0,
|
||||||
|
"all_align" : "center",
|
||||||
|
"text" : PAGE_TEXTS[page],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
if tooltip:
|
||||||
|
tab["tooltip_text"] = tooltip
|
||||||
|
return tab
|
||||||
|
|
||||||
|
|
||||||
|
def _PatchInventoryWindow(window):
|
||||||
|
delta = player.EQUIPMENT_SLOT_START - OLD_EQUIPMENT_START
|
||||||
|
equip = _Find(window, "EquipmentSlot")
|
||||||
|
if equip and delta:
|
||||||
|
slots = []
|
||||||
|
for slot in equip["slot"]:
|
||||||
|
if OLD_EQUIPMENT_START <= slot["index"] < OLD_EQUIPMENT_END:
|
||||||
|
slot = dict(slot)
|
||||||
|
slot["index"] += delta
|
||||||
|
slots.append(slot)
|
||||||
|
equip["slot"] = tuple(slots)
|
||||||
|
|
||||||
|
board = _Find(window, "board")
|
||||||
|
first = _Find(window, "Inventory_Tab_01")
|
||||||
|
if not board or not first:
|
||||||
|
return
|
||||||
|
tooltips = [first.get("tooltip_text")]
|
||||||
|
second = _Find(window, "Inventory_Tab_02")
|
||||||
|
tooltips.append(second.get("tooltip_text") if second else None)
|
||||||
|
children = []
|
||||||
|
for child in board["children"]:
|
||||||
|
name = child.get("name", "")
|
||||||
|
if name.startswith("Inventory_Tab_"):
|
||||||
|
if name == "Inventory_Tab_01":
|
||||||
|
for page in xrange(PAGE_COUNT):
|
||||||
|
tip = tooltips[page] if page < len(tooltips) else None
|
||||||
|
children.append(_PageTab(page, TAB_X + TAB_W * page, first["y"], tip))
|
||||||
|
continue
|
||||||
|
children.append(child)
|
||||||
|
board["children"] = tuple(children)
|
||||||
|
|
||||||
|
|
||||||
|
def _InstallScriptPatch():
|
||||||
|
execfile = ui.execfile
|
||||||
|
|
||||||
|
def ExecFile(fileName, dict):
|
||||||
|
execfile(fileName, dict)
|
||||||
|
if fileName.replace("\\", "/").split("/")[-1].lower() == "inventorywindow.py" and "window" in dict:
|
||||||
|
_PatchInventoryWindow(dict["window"])
|
||||||
|
|
||||||
|
ui.execfile = ExecFile
|
||||||
|
|
||||||
|
|
||||||
|
def _InstallInventoryWindow():
|
||||||
|
cls = uiInventory.InventoryWindow
|
||||||
|
load = cls._InventoryWindow__LoadWindow
|
||||||
|
refresh = cls.RefreshBagSlotWindow
|
||||||
|
|
||||||
|
def LoadWindow(self):
|
||||||
|
loaded = self.isLoaded
|
||||||
|
load(self)
|
||||||
|
if loaded:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.inventoryTab = [self.GetChild("Inventory_Tab_%02d" % (page + 1)) for page in xrange(PAGE_COUNT)]
|
||||||
|
for page in xrange(PAGE_COUNT):
|
||||||
|
self.inventoryTab[page].SetEvent(lambda arg=page: self.SetInventoryPage(arg))
|
||||||
|
self.inventoryTab[0].Down()
|
||||||
|
except:
|
||||||
|
import dbg
|
||||||
|
dbg.TraceError("mt_inventory: cannot bind the page tabs")
|
||||||
|
|
||||||
|
def SetInventoryPage(self, page):
|
||||||
|
self.inventoryPageIndex = page
|
||||||
|
for i in xrange(len(self.inventoryTab)):
|
||||||
|
if i != page:
|
||||||
|
self.inventoryTab[i].SetUp()
|
||||||
|
self.RefreshBagSlotWindow()
|
||||||
|
|
||||||
|
def RefreshBagSlotWindow(self):
|
||||||
|
refresh(self)
|
||||||
|
page = getattr(self, "inventoryPageIndex", 0)
|
||||||
|
if page < 2:
|
||||||
|
return
|
||||||
|
# 40250 turns a cell into a slot by subtracting one page, so pages 3-5 highlight nothing.
|
||||||
|
for i in xrange(player.INVENTORY_PAGE_SIZE):
|
||||||
|
slotNumber = page * player.INVENTORY_PAGE_SIZE + i
|
||||||
|
if not player.GetItemCount(slotNumber):
|
||||||
|
continue
|
||||||
|
if not constInfo.IS_AUTO_POTION(player.GetItemIndex(slotNumber)):
|
||||||
|
continue
|
||||||
|
if player.GetItemMetinSocket(slotNumber, 0):
|
||||||
|
self.wndItem.ActivateSlot(i)
|
||||||
|
else:
|
||||||
|
self.wndItem.DeactivateSlot(i)
|
||||||
|
self.wndItem.RefreshSlot()
|
||||||
|
|
||||||
|
cls._InventoryWindow__LoadWindow = LoadWindow
|
||||||
|
cls.SetInventoryPage = SetInventoryPage
|
||||||
|
cls.RefreshBagSlotWindow = RefreshBagSlotWindow
|
||||||
|
|
||||||
|
|
||||||
|
def _InstallSelectItem():
|
||||||
|
# SelectItemWindow.RefreshSlot with the cell loop over every page instead of INVENTORY_PAGE_SIZE*2.
|
||||||
|
def RefreshSlot(self):
|
||||||
|
slotPos = 0
|
||||||
|
self.inventorySlotPosDict = {}
|
||||||
|
|
||||||
|
getItemVNum=player.GetItemIndex
|
||||||
|
getItemCount=player.GetItemCount
|
||||||
|
setItemVNum=self.itemSlot.SetItemSlot
|
||||||
|
|
||||||
|
for i in xrange(player.INVENTORY_PAGE_SIZE*PAGE_COUNT):
|
||||||
|
slotNumber = i
|
||||||
|
|
||||||
|
itemVNum = getItemVNum(slotNumber)
|
||||||
|
if 0 == itemVNum:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not item.IsMetin(itemVNum):
|
||||||
|
continue
|
||||||
|
|
||||||
|
itemGrade = player.GetItemGrade(slotNumber)
|
||||||
|
if itemGrade > 2:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.inventorySlotPosDict[slotPos] = i
|
||||||
|
|
||||||
|
slotPos += 1
|
||||||
|
|
||||||
|
if slotPos > 54:
|
||||||
|
break
|
||||||
|
|
||||||
|
itemCount = len(self.inventorySlotPosDict)
|
||||||
|
if itemCount < 15:
|
||||||
|
self.SetTableSize(3)
|
||||||
|
|
||||||
|
else:
|
||||||
|
lineCount = 3
|
||||||
|
lineCount += (itemCount - 15) / 5
|
||||||
|
if itemCount % 5:
|
||||||
|
lineCount += 1
|
||||||
|
self.SetTableSize(lineCount)
|
||||||
|
|
||||||
|
for selectWndPos, inventoryPos in self.inventorySlotPosDict.items():
|
||||||
|
itemVNum = getItemVNum(inventoryPos)
|
||||||
|
itemCount = getItemCount(inventoryPos)
|
||||||
|
|
||||||
|
if itemCount <= 1:
|
||||||
|
itemCount = 0
|
||||||
|
|
||||||
|
setItemVNum(selectWndPos, itemVNum, itemCount)
|
||||||
|
|
||||||
|
self.itemSlot.RefreshSlot()
|
||||||
|
|
||||||
|
uiselectitem.SelectItemWindow.RefreshSlot = RefreshSlot
|
||||||
|
|
||||||
|
|
||||||
|
def __Install():
|
||||||
|
if PAGE_COUNT <= 2:
|
||||||
|
return
|
||||||
|
for install in (_InstallScriptPatch, _InstallInventoryWindow, _InstallSelectItem):
|
||||||
|
try:
|
||||||
|
install()
|
||||||
|
except:
|
||||||
|
import dbg
|
||||||
|
dbg.TraceError("mt_inventory: %s failed" % install.__name__)
|
||||||
|
|
||||||
|
__Install()
|
||||||
|
)PY";
|
||||||
@@ -877,6 +877,7 @@ namespace PythonBoot
|
|||||||
#include "AutoHuntScript.inc"
|
#include "AutoHuntScript.inc"
|
||||||
#include "RegisterScript.inc"
|
#include "RegisterScript.inc"
|
||||||
#include "FrameRateOptionScript.inc"
|
#include "FrameRateOptionScript.inc"
|
||||||
|
#include "InventoryPagesScript.inc"
|
||||||
|
|
||||||
static void install_script_module(const char* c_szModule, const char* c_szScript)
|
static void install_script_module(const char* c_szModule, const char* c_szScript)
|
||||||
{
|
{
|
||||||
@@ -933,6 +934,7 @@ bool RunMainScript(const char* lpCmdLine, std::string* error)
|
|||||||
install_script_module("mt_autohunt", c_szAutoHuntScript);
|
install_script_module("mt_autohunt", c_szAutoHuntScript);
|
||||||
install_script_module("mt_register", c_szRegisterScript);
|
install_script_module("mt_register", c_szRegisterScript);
|
||||||
install_script_module("mt_framerate", c_szFrameRateOptionScript);
|
install_script_module("mt_framerate", c_szFrameRateOptionScript);
|
||||||
|
install_script_module("mt_inventory", c_szInventoryPagesScript);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class IAbstractPlayer : public TAbstractSingleton<IAbstractPlayer>
|
|||||||
|
|
||||||
virtual bool IsEquipItemInSlot(TItemPos itemPos) = 0;
|
virtual bool IsEquipItemInSlot(TItemPos itemPos) = 0;
|
||||||
|
|
||||||
virtual void AddQuickSlot(int QuickslotIndex, char IconType, char IconPosition) = 0;
|
virtual void AddQuickSlot(int QuickslotIndex, char IconType, WORD IconPosition) = 0;
|
||||||
virtual void DeleteQuickSlot(int QuickslotIndex) = 0;
|
virtual void DeleteQuickSlot(int QuickslotIndex) = 0;
|
||||||
virtual void MoveQuickSlot(int Source, int Target) = 0;
|
virtual void MoveQuickSlot(int Source, int Target) = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const DWORD c_FileName_Max_Length = 128;
|
|||||||
const DWORD c_Short_Name_Max_Length = 32;
|
const DWORD c_Short_Name_Max_Length = 32;
|
||||||
|
|
||||||
const DWORD c_Inventory_Page_Size = 5*9; // x*y
|
const DWORD c_Inventory_Page_Size = 5*9; // x*y
|
||||||
const DWORD c_Inventory_Page_Count = 2;
|
const DWORD c_Inventory_Page_Count = 5; // PORT: 2 in 40250; the 203 server patch widens the bag to 5 pages (tools/server/inventory-5-pages)
|
||||||
const DWORD c_ItemSlot_Count = c_Inventory_Page_Size * c_Inventory_Page_Count;
|
const DWORD c_ItemSlot_Count = c_Inventory_Page_Size * c_Inventory_Page_Count;
|
||||||
const DWORD c_Equipment_Count = 12;
|
const DWORD c_Equipment_Count = 12;
|
||||||
|
|
||||||
@@ -306,7 +306,7 @@ enum
|
|||||||
typedef struct SQuickSlot
|
typedef struct SQuickSlot
|
||||||
{
|
{
|
||||||
BYTE Type;
|
BYTE Type;
|
||||||
BYTE Position;
|
WORD Position; // PORT: BYTE in 40250; cells past 255 once the bag has 5 pages
|
||||||
} TQuickSlot;
|
} TQuickSlot;
|
||||||
|
|
||||||
typedef struct TPlayerItemAttribute
|
typedef struct TPlayerItemAttribute
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ class CPythonNetworkStream : public CNetworkStream, public CSingleton<CPythonNet
|
|||||||
bool SendItemMovePacket(TItemPos pos, TItemPos change_pos, BYTE num);
|
bool SendItemMovePacket(TItemPos pos, TItemPos change_pos, BYTE num);
|
||||||
bool SendItemPickUpPacket(DWORD vid);
|
bool SendItemPickUpPacket(DWORD vid);
|
||||||
|
|
||||||
bool SendQuickSlotAddPacket(BYTE wpos, BYTE type, BYTE pos);
|
bool SendQuickSlotAddPacket(BYTE wpos, BYTE type, WORD pos);
|
||||||
bool SendQuickSlotDelPacket(BYTE wpos);
|
bool SendQuickSlotDelPacket(BYTE wpos);
|
||||||
bool SendQuickSlotMovePacket(BYTE wpos, BYTE change_pos);
|
bool SendQuickSlotMovePacket(BYTE wpos, BYTE change_pos);
|
||||||
|
|
||||||
|
|||||||
@@ -705,7 +705,7 @@ bool CPythonNetworkStream::SendItemPickUpPacket(DWORD vid)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool CPythonNetworkStream::SendQuickSlotAddPacket(BYTE wpos, BYTE type, BYTE pos)
|
bool CPythonNetworkStream::SendQuickSlotAddPacket(BYTE wpos, BYTE type, WORD pos)
|
||||||
{
|
{
|
||||||
if (!__CanActMainInstance())
|
if (!__CanActMainInstance())
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -781,7 +781,7 @@ void CPythonPlayer::RequestAddLocalQuickSlot(DWORD dwLocalSlotIndex, DWORD dwWnd
|
|||||||
DWORD dwGlobalSlotIndex=LocalQuickSlotIndexToGlobalQuickSlotIndex(dwLocalSlotIndex);
|
DWORD dwGlobalSlotIndex=LocalQuickSlotIndexToGlobalQuickSlotIndex(dwLocalSlotIndex);
|
||||||
|
|
||||||
CPythonNetworkStream& rkNetStream=CPythonNetworkStream::Instance();
|
CPythonNetworkStream& rkNetStream=CPythonNetworkStream::Instance();
|
||||||
rkNetStream.SendQuickSlotAddPacket((BYTE)dwGlobalSlotIndex, (BYTE)dwWndType, (BYTE)dwWndItemPos);
|
rkNetStream.SendQuickSlotAddPacket((BYTE)dwGlobalSlotIndex, (BYTE)dwWndType, (WORD)dwWndItemPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPythonPlayer::RequestAddToEmptyLocalQuickSlot(DWORD dwWndType, DWORD dwWndItemPos)
|
void CPythonPlayer::RequestAddToEmptyLocalQuickSlot(DWORD dwWndType, DWORD dwWndItemPos)
|
||||||
@@ -794,7 +794,7 @@ void CPythonPlayer::RequestAddToEmptyLocalQuickSlot(DWORD dwWndType, DWORD dwWnd
|
|||||||
{
|
{
|
||||||
DWORD dwGlobalQuickSlotIndex=LocalQuickSlotIndexToGlobalQuickSlotIndex(i);
|
DWORD dwGlobalQuickSlotIndex=LocalQuickSlotIndexToGlobalQuickSlotIndex(i);
|
||||||
CPythonNetworkStream& rkNetStream=CPythonNetworkStream::Instance();
|
CPythonNetworkStream& rkNetStream=CPythonNetworkStream::Instance();
|
||||||
rkNetStream.SendQuickSlotAddPacket((BYTE)dwGlobalQuickSlotIndex, (BYTE)dwWndType, (BYTE)dwWndItemPos);
|
rkNetStream.SendQuickSlotAddPacket((BYTE)dwGlobalQuickSlotIndex, (BYTE)dwWndType, (WORD)dwWndItemPos);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -845,7 +845,7 @@ void CPythonPlayer::RequestUseLocalQuickSlot(DWORD dwLocalSlotIndex)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPythonPlayer::AddQuickSlot(int QuickSlotIndex, char IconType, char IconPosition)
|
void CPythonPlayer::AddQuickSlot(int QuickSlotIndex, char IconType, WORD IconPosition)
|
||||||
{
|
{
|
||||||
if (QuickSlotIndex < 0 || QuickSlotIndex >= QUICKSLOT_MAX_NUM)
|
if (QuickSlotIndex < 0 || QuickSlotIndex >= QUICKSLOT_MAX_NUM)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ class CPythonPlayer : public CSingleton<CPythonPlayer>, public IAbstractPlayer
|
|||||||
// Quickslot
|
// Quickslot
|
||||||
int GetQuickPage();
|
int GetQuickPage();
|
||||||
void SetQuickPage(int nPageIndex);
|
void SetQuickPage(int nPageIndex);
|
||||||
void AddQuickSlot(int QuickslotIndex, char IconType, char IconPosition);
|
void AddQuickSlot(int QuickslotIndex, char IconType, WORD IconPosition);
|
||||||
void DeleteQuickSlot(int QuickslotIndex);
|
void DeleteQuickSlot(int QuickslotIndex);
|
||||||
void MoveQuickSlot(int Source, int Target);
|
void MoveQuickSlot(int Source, int Target);
|
||||||
|
|
||||||
|
|||||||
@@ -728,10 +728,10 @@ struct CGItemUseToItem { uint8_t header; ItemPos pos; ItemPos target_pos; };
|
|||||||
static_assert(sizeof(CGItemUseToItem) == 7);
|
static_assert(sizeof(CGItemUseToItem) == 7);
|
||||||
struct CGGiveItem { uint8_t header; uint32_t target_vid; ItemPos pos; uint8_t count; };
|
struct CGGiveItem { uint8_t header; uint32_t target_vid; ItemPos pos; uint8_t count; };
|
||||||
static_assert(sizeof(CGGiveItem) == 9);
|
static_assert(sizeof(CGGiveItem) == 9);
|
||||||
struct CGQuickSlotAdd { uint8_t header; uint8_t pos; uint8_t type; uint8_t position; };
|
struct CGQuickSlotAdd { uint8_t header; uint8_t pos; uint8_t type; uint16_t position; }; // position WORD: 5-page bag
|
||||||
struct CGQuickSlotDel { uint8_t header; uint8_t pos; };
|
struct CGQuickSlotDel { uint8_t header; uint8_t pos; };
|
||||||
struct CGQuickSlotSwap { uint8_t header; uint8_t pos; uint8_t pos_to; };
|
struct CGQuickSlotSwap { uint8_t header; uint8_t pos; uint8_t pos_to; };
|
||||||
static_assert(sizeof(CGQuickSlotAdd) == 4 && sizeof(CGQuickSlotDel) == 2 &&
|
static_assert(sizeof(CGQuickSlotAdd) == 5 && sizeof(CGQuickSlotDel) == 2 &&
|
||||||
sizeof(CGQuickSlotSwap) == 3);
|
sizeof(CGQuickSlotSwap) == 3);
|
||||||
|
|
||||||
// --- P8 social / exchange / storage intents -------------------------------
|
// --- P8 social / exchange / storage intents -------------------------------
|
||||||
@@ -1049,11 +1049,11 @@ static_assert(sizeof(PlayerSkill3) == 6);
|
|||||||
struct GCSkillLevel { uint8_t header; PlayerSkill3 skills[SKILL_MAX_NUM]; }; // header 76
|
struct GCSkillLevel { uint8_t header; PlayerSkill3 skills[SKILL_MAX_NUM]; }; // header 76
|
||||||
static_assert(sizeof(GCSkillLevel) == 1 + 255 * 6); // 1531
|
static_assert(sizeof(GCSkillLevel) == 1 + 255 * 6); // 1531
|
||||||
|
|
||||||
struct QuickSlot2 { uint8_t type; uint8_t position; }; // TQuickSlot / TQuickslot
|
struct QuickSlot2 { uint8_t type; uint16_t position; }; // TQuickSlot / TQuickslot (position WORD: 5-page bag)
|
||||||
struct GCQuickSlotAdd { uint8_t header; uint8_t pos; QuickSlot2 slot; }; // header 28
|
struct GCQuickSlotAdd { uint8_t header; uint8_t pos; QuickSlot2 slot; }; // header 28
|
||||||
struct GCQuickSlotDel { uint8_t header; uint8_t pos; }; // header 29
|
struct GCQuickSlotDel { uint8_t header; uint8_t pos; }; // header 29
|
||||||
struct GCQuickSlotSwap { uint8_t header; uint8_t pos; uint8_t pos_to; }; // header 30
|
struct GCQuickSlotSwap { uint8_t header; uint8_t pos; uint8_t pos_to; }; // header 30
|
||||||
static_assert(sizeof(GCQuickSlotAdd) == 4 && sizeof(GCQuickSlotDel) == 2 &&
|
static_assert(sizeof(GCQuickSlotAdd) == 5 && sizeof(GCQuickSlotDel) == 2 &&
|
||||||
sizeof(GCQuickSlotSwap) == 3);
|
sizeof(GCQuickSlotSwap) == 3);
|
||||||
|
|
||||||
// --- GC affects ---
|
// --- GC affects ---
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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)");
|
||||||
|
|
||||||
Reference in New Issue
Block a user