Un système de ceinture Metin2 ajoute un nouvel emplacement d'équipement (le belt slot) à la fenêtre d'équipement du joueur, ainsi qu'un petit inventaire séparé (le belt inventory) qui s'ouvre lorsqu'une ceinture est équipée. On peut y glisser des consommables comme des potions ou des fioles de vie pour un accès rapide en jeu, et le nombre de cases utilisables augmente à mesure que la ceinture est améliorée. Dans ce guide, j'explique comment je rends une ceinture fonctionnelle sur trois couches : item_proto, la source serveur et le client (interface Python).
L'architecture du système de ceinture
La ceinture n'est pas un simple script ; elle ne fonctionne que si les trois couches restent synchronisées. Elles doivent toutes partager les mêmes constantes (la plage de slots), sinon les objets « disparaissent » ou le client plante :
- item_proto : définit le type de l'objet ceinture, l'emplacement où il s'équipe et le nombre de cases qu'il débloque à chaque grade.
- Source serveur : une plage de slots dédiée au belt inventory, la validation du placement des objets et la logique de persistance.
- Client (root/Python) : l'emplacement de ceinture dans la fenêtre d'équipement et l'interface en grille qui s'ouvre quand une ceinture est portée.
Dans la plupart des sources open source, le système se trouve derrière un drapeau de compilation, par exemple ENABLE_BELT_INVENTORY_SYSTEM. Si vous ne compilez pas serveur et client avec cette définition activée, les structures de paquets ne correspondront pas et les joueurs seront déconnectés à la connexion.
Ajouter un objet ceinture à item_proto
La ceinture est un type d'objet à part entière et s'équipe dans un emplacement dédié. Trois champs de l'entrée item_proto sont essentiels : le type (ITEM_BELT), le drapeau de port (WEARABLE_BELT) et l'emplacement où il s'installe (WEAR_BELT). Le grade (le nombre de cases débloquées) est généralement stocké dans un champ de valeur (value0) :
-- si vous éditez le proto directement dans MySQL, la logique est :
-- type = ITEM_BELT (type ceinture)
-- subtype = 0
-- wearflags = WEARABLE_BELT (portable dans le belt slot)
-- value0 = grade ceinture (1,2,3,4 → palier de cases débloquées)
UPDATE item_proto
SET type = 'ITEM_BELT',
wearflags = 'WEARABLE_BELT',
value0 = 1 -- grade de départ
WHERE vnum = 18000;
Dans les builds turcs/multi-sources, les champs peuvent aussi se gérer via item_names.txt et item_proto.txt. Le point clé : le type de la ceinture n'est ni arme ni armure mais un type ceinture distinct, afin qu'elle entre dans l'emplacement WEAR_BELT sans occuper l'inventaire normal. Après l'ajout, recompilez le proto depuis item_proto.txt (ou mettez à jour la table en direct) et redémarrez le game core.
Source serveur : la plage de slots du belt inventory
Le belt inventory vit dans une plage d'adresses séparée de l'inventaire normal et des emplacements d'équipement. Dans la source, cette plage est définie par des constantes (fichier et noms variant selon la source, mais la logique reste la même) :
// dans un en-tête comme service.h / item_length
#define BELT_INVENTORY_SLOT_START (INVENTORY_AND_EQUIP_SLOT_END)
#define BELT_INVENTORY_SLOT_COUNT 16
#define BELT_INVENTORY_SLOT_END (BELT_INVENTORY_SLOT_START + BELT_INVENTORY_SLOT_COUNT)
Une fonction auxiliaire qui reconnaît cette plage est utilisée lors de la validation du placement. Quand le game core décide dans quelle case un objet peut aller, il vérifie si le slot tombe dans la plage de la ceinture :
bool CHARACTER::IsBeltInventorySlot(WORD wCell) const
{
return (wCell >= BELT_INVENTORY_SLOT_START &&
wCell < BELT_INVENTORY_SLOT_END);
}
// dans la validation de déplacement (style char_item.cpp) :
if (IsBeltInventorySlot(wDestCell))
{
// 1) Le joueur a-t-il une ceinture équipée ?
LPITEM belt = GetWear(WEAR_BELT);
if (!belt)
return false; // pas de ceinture → cases inutilisables
// 2) La case cible est-elle débloquée par le grade ?
if (!IsValidBeltCell(belt, wDestCell))
return false; // impossible dans une case verrouillée
}
Les deux vérifications sont essentielles : les objets ne doivent pas entrer dans les cases de ceinture quand aucune ceinture n'est portée, et seules les cases débloquées par le grade value0 de la ceinture sont utilisables. Sans cette logique, les joueurs exploitent les cases verrouillées pour obtenir un inventaire gratuit. Au retrait de la ceinture, le game core doit ramener ses objets vers l'inventaire normal (ou refuser l'action s'il n'y a pas de place), sinon les objets deviennent inaccessibles.
Base de données et persistance
Les objets du belt inventory sont stockés comme n'importe quel autre objet dans la table player.item via les champs window et pos ; seule la valeur pos tombe dans la plage de la ceinture. Attention au piège classique de Metin2 : tant que le joueur est en ligne, l'inventaire est conservé en mémoire dans la couche DB-cache. Si vous écrivez les objets de ceinture directement dans MySQL, le joueur les écrase avec sa copie en mémoire à la déconnexion et les objets sont perdus. Les déplacements d'objets doivent toujours passer par l'API du game core, jamais par du SQL brut. Le schéma de stockage ne change pas ; la seule différence est que les slots de ceinture s'écrivent dans la nouvelle plage pos.
Client : l'emplacement de ceinture et l'interface en grille
Côté client, deux ajouts. D'abord l'emplacement de ceinture dans la fenêtre d'équipement ; ensuite la grille du belt inventory qui s'ouvre quand une ceinture est portée. Dans les fichiers d'interface Python (uiInventory.py et le script de fenêtre associé), la plage de slots doit être définie à l'identique du serveur :
# dans playerSettingModule / player.py
BELT_INVENTORY_SLOT_START = 0
BELT_INVENTORY_SLOT_COUNT = 16
BELT_INVENTORY_SLOT_END = BELT_INVENTORY_SLOT_START + BELT_INVENTORY_SLOT_COUNT
# afficher/masquer la grille quand la ceinture est équipée
def OnEquipBelt(self, isEquipped):
if isEquipped:
self.beltInventoryGrid.Show()
else:
self.beltInventoryGrid.Hide()
Une règle économique pour les cases de la grille : affichez autant de cases « actives » que le grade de la ceinture (la valeur du serveur) en débloque, et le reste « verrouillé ». Le glisser-déposer dans les cases verrouillées doit être bloqué. N'oubliez pas non plus d'ajouter les graphismes .tga de l'interface et les coordonnées des cases ; la plupart des sources les gardent dans un fichier belt_inventory_window.py. Enfin, l'animation d'ouverture/fermeture de la grille est purement cosmétique — la validation des objets se fait toujours sur le serveur, ne faites jamais confiance au client.
Améliorer la ceinture : débloquer des cases
L'attrait de la ceinture, c'est qu'elle est améliorable. L'approche générale consiste à augmenter le grade value0 de la ceinture via un PNJ d'amélioration ou une quête. Une simple montée de grade côté quête ressemble à ceci :
quest belt_upgrade begin
state start begin
when 18000.use begin
local belt = item.get_value(0) -- grade actuel
if belt >= 4 then
say("Ta ceinture est déjà au grade maximum.")
return
end
-- contrôle du matériau / yang ici
item.set_value(0, belt + 1)
say("Ta ceinture est passée au grade "..(belt+1).." !")
end
end
end
Quand le grade monte, le serveur compte davantage de cases de ceinture comme « débloquées » selon la nouvelle valeur value0 de la ceinture équipée et met à jour le client. Gardez le plafond de grade (4 dans l'exemple) cohérent à la fois dans la quête et dans la logique IsValidBeltCell de la source ; s'ils divergent, vous laissez des cases inutilement verrouillées ou vous ouvrez une faille exploitable.
Questions fréquentes
La grille s'ouvre quand j'équipe une ceinture, mais les objets disparaissent à la déconnexion — pourquoi ?
Le piège classique du DB-cache. Si vous écrivez les objets de ceinture directement dans MySQL, le joueur les écrase avec sa copie mémoire à la déconnexion. Déplacez toujours les objets via l'API du game core et assurez-vous que la valeur pos est correctement écrite dans la plage de la ceinture.
Les joueurs peuvent utiliser les cases de ceinture sans en porter une.
Le contrôle GetWear(WEAR_BELT) côté serveur est absent ou faible. À chaque placement dans la plage de ceinture, vérifiez d'abord qu'une ceinture est équipée, puis que la case cible est débloquée par le grade. Le verrou côté client n'est que cosmétique.
J'ai activé le système mais les joueurs se déconnectent à la connexion.
Presque toujours un décalage du drapeau ENABLE_BELT_INVENTORY_SYSTEM ou des constantes de slots entre serveur et client. Recompilez les deux avec les mêmes définitions ; si les tailles de paquets ne correspondent pas, la session saute.
Vous voulez installer proprement le système de ceinture sur votre serveur ? Je mets en place le belt inventory de bout en bout — de l'item_proto à l'intégration source et client, déblocage progressif des cases inclus. Parlons de votre projet — contactez-moi.