Metin2 inventory expansion is one of the most requested server features: once the default bag fills up, farming, drops and storage management become painful. The key is to understand that the inventory isn't defined in a single place — the slot count lives both in the server source and on the client side, and these two values must match exactly. In this guide I walk through how to add bag slots and extra pages step by step, safely.
How the inventory works: slots, pages and the shared constant
In Metin2 the inventory is an array of slots. In the classic layout each page is made of 45 slots (9 columns × 5 rows), and the total slot count must be a multiple of the page size. The default value in most sources is 90, i.e. two pages. If you want four pages, you set the total to 180.
- Total slots = number of pages × page size (45).
- Slot positions (
pos) start at 0; equipment slots sit in a separate block above that range. - If the server and client don't share the same constant, item positions shift, causing item loss and crashes.
Server side: length.h and INVENTORY_MAX_NUM
The heart of the expansion is the constant in common/length.h. There you find the total slot count and the page size:
// common/length.h
enum EInventoryConstants
{
INVENTORY_MAX_NUM = 90, // 2 pages × 45 -> set 180 for 4 pages
INVENTORY_PAGE_SIZE = 45,
};
Change INVENTORY_MAX_NUM and make sure the value stays a clean multiple of 45. Then recompile the game binary:
cd Server/game/src
make clean && make -j$(nproc)
This constant is used in many places that validate items: IsValidItemPosition in char_item.cpp and the item move/place functions, plus the packet checks in input_main.cpp. Since they all work off INVENTORY_MAX_NUM, changing the single source is enough — you don't have to hunt down extra if statements.
Database: the item table and position checks
Items aren't stored on the player but in a separate item table; each row points to its place with the window (inventory/equipment) and pos columns. You don't need to change the schema for an expansion, because pos is already an integer field that can hold the new, larger values. Still, check two things:
- Back up first: dump the
playeranditemtables. A position mismatch can cause item loss. - Test with a test account: place items on the new pages and relog the character; confirm the new
posvalues (e.g. 90–179) are written correctly in theitemtable.
It's also wise to recompile the DB cache, because db and game share the same protocol headers.
Client source: keeping the constant in sync
The most critical rule: the client must know the same total slot count. In the client source, inventory arrays and packet structures are sized by their own constant in the UserInterface project. If you went from 90 to 180 on the server, update the same value on the client and recompile UserInterface:
// Client UserInterface (e.g. Packet.h / Locale_inc.h)
#define INVENTORY_MAX_NUM 180
#define INVENTORY_PAGE_SIZE 45
If you don't have the client source, enlarging the UI with python alone is not enough: the binary is still compiled with the old slot count, so items placed on the extra pages stay invisible or the connection gets dropped. That's why client source is mandatory for inventory expansion.
Client python: extra pages with uiInventory.py
Once the binary is compiled at the right size, it's the UI's turn. In root/uiInventory.py the inventory window is drawn with its page buttons and slot grid. Here's what to do:
- Increase the page count: update the loop and the
GetInventoryPageCountlogic so the window shows the extra page buttons/tabs. - Compute slot positions from the
player.INVENTORY_PAGE_SIZEconstant; clean up any hardcoded numbers like 90. - If the window is too small, adjust the width/height in the
.pyor edit the related.subimages.
The python side only draws; the real slot limit is set by the binary. So the page count you display in python must match the compiled INVENTORY_MAX_NUM — show more and the clicks fall through to nothing.
Testing and common mistakes
After the change, always test end to end:
- Server/client mismatch: the most common error. If the two sides are compiled with a different
INVENTORY_MAX_NUM, item positions shift. Update and compile both at the same time. - A value that isn't a multiple of 45: the page is left half-drawn and the last rows can't be clicked. Always keep the total divisible by 45.
- Exploit check: separate windows like pet or dragon soul also use positions; when you grow the inventory constant, verify their pos ranges don't collide.
- A stable backup: before going live, keep a DB dump and the old binaries so your rollback path stays open.
Frequently Asked Questions
Can I expand the inventory without the client source?
No. With python you only enlarge the visuals, but the binary is compiled with the old slot count; the extra slots won't work and the mismatch with the server causes crashes. You need both the server and client source.
Will existing players lose their items?
If you increase the total slot count, the existing positions (0–89) stay in place and only new empty slots are added; no item loss. But a wrong constant or shrinking can cause loss, so dump the DB first.
Why must it be a multiple of 45?
An inventory page is 9×5 = 45 slots. If the total isn't a multiple of that, the last page is drawn incomplete and the slot indices between the UI and the binary won't match.
Want a clean, exploit-free inventory expansion in your server project? I can help with system development on both the Metin2 source and client — let's talk through your project: get in touch with me.