背包扩展到 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
@@ -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 "RegisterScript.inc"
|
||||
#include "FrameRateOptionScript.inc"
|
||||
#include "InventoryPagesScript.inc"
|
||||
|
||||
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_register", c_szRegisterScript);
|
||||
install_script_module("mt_framerate", c_szFrameRateOptionScript);
|
||||
install_script_module("mt_inventory", c_szInventoryPagesScript);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class IAbstractPlayer : public TAbstractSingleton<IAbstractPlayer>
|
||||
|
||||
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 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_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_Equipment_Count = 12;
|
||||
|
||||
@@ -306,7 +306,7 @@ enum
|
||||
typedef struct SQuickSlot
|
||||
{
|
||||
BYTE Type;
|
||||
BYTE Position;
|
||||
WORD Position; // PORT: BYTE in 40250; cells past 255 once the bag has 5 pages
|
||||
} TQuickSlot;
|
||||
|
||||
typedef struct TPlayerItemAttribute
|
||||
|
||||
@@ -155,7 +155,7 @@ class CPythonNetworkStream : public CNetworkStream, public CSingleton<CPythonNet
|
||||
bool SendItemMovePacket(TItemPos pos, TItemPos change_pos, BYTE num);
|
||||
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 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())
|
||||
return true;
|
||||
|
||||
@@ -781,7 +781,7 @@ void CPythonPlayer::RequestAddLocalQuickSlot(DWORD dwLocalSlotIndex, DWORD dwWnd
|
||||
DWORD dwGlobalSlotIndex=LocalQuickSlotIndexToGlobalQuickSlotIndex(dwLocalSlotIndex);
|
||||
|
||||
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)
|
||||
@@ -794,7 +794,7 @@ void CPythonPlayer::RequestAddToEmptyLocalQuickSlot(DWORD dwWndType, DWORD dwWnd
|
||||
{
|
||||
DWORD dwGlobalQuickSlotIndex=LocalQuickSlotIndexToGlobalQuickSlotIndex(i);
|
||||
CPythonNetworkStream& rkNetStream=CPythonNetworkStream::Instance();
|
||||
rkNetStream.SendQuickSlotAddPacket((BYTE)dwGlobalQuickSlotIndex, (BYTE)dwWndType, (BYTE)dwWndItemPos);
|
||||
rkNetStream.SendQuickSlotAddPacket((BYTE)dwGlobalQuickSlotIndex, (BYTE)dwWndType, (WORD)dwWndItemPos);
|
||||
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)
|
||||
return;
|
||||
|
||||
@@ -317,7 +317,7 @@ class CPythonPlayer : public CSingleton<CPythonPlayer>, public IAbstractPlayer
|
||||
// Quickslot
|
||||
int GetQuickPage();
|
||||
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 MoveQuickSlot(int Source, int Target);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user