From a1b63c0afbb8ecbf89c3daf30102975809137fdb Mon Sep 17 00:00:00 2001 From: shenlei Date: Fri, 16 Oct 2026 14:07:07 +0900 Subject: [PATCH] AVA2 unlock P6: costume enchants (client) Keep 54001-54078 (body/hair) and 54124-54174 (mount skin) enchants as USE_CHANGE_ATTRIBUTE; mt_costume_attr marks matching costumes as drop targets, shows the attribute in the tooltip and renders MTCATTR; the switchbot no longer lists enchants as scrolls. Co-Authored-By: Claude Opus 5.5 --- src/platform/ScriptLib/CostumeAttrScript.inc | 83 ++++++++++++++++++++ src/platform/ScriptLib/PythonBoot.cpp | 2 + src/platform/ScriptLib/SwitchbotScript.inc | 3 +- tools/server/ava2-items/make_items.py | 11 ++- tools/server/ava2-items/tests/test_items.py | 12 ++- 5 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 src/platform/ScriptLib/CostumeAttrScript.inc diff --git a/src/platform/ScriptLib/CostumeAttrScript.inc b/src/platform/ScriptLib/CostumeAttrScript.inc new file mode 100644 index 00000000..a591409e --- /dev/null +++ b/src/platform/ScriptLib/CostumeAttrScript.inc @@ -0,0 +1,83 @@ +// PORT: AVA2 costume enchants (ENABLE_SPECIAL_COSTUME_ATTR), as module mt_costume_attr. Items 54001-54267 are +// USE_CHANGE_ATTRIBUTE rows with value0 = apply, value1 = value, value2 = target mask (1 << bonus flag); the server +// (metin2-server port_costume_attr.cpp) adds that attribute to a body / hair / mount-skin costume. The 40250 +// inventory already sends every USE_CHANGE_ATTRIBUTE drop to the server; this script only marks the matching +// costumes as usable targets, shows the attribute in the enchant's tooltip and renders the "MTCATTR " server +// messages that replace AVA2's locale strings 1056 / 1057 / 1061 / 1062 / 1063. +static const char c_szCostumeAttrScript[] = R"PY(# -*- coding: utf-8 -*- +import chat +import game +import item +import player +import uiInventory +import uiToolTip + +L=player.AutoHuntLocalize +VNUMS=(54001,54267) +MAX_ATTR=3 +# AVA2 CItem::GetBonusFlag of the costume kinds the server maps -> (mask bit, target name). +TARGETS={item.COSTUME_TYPE_BODY:(1,'时装'),item.COSTUME_TYPE_HAIR:(2,'发型'),item.COSTUME_TYPE_MOUNT_SKIN:(4,'坐骑外观')} +MESSAGES={'target':'该强化石不能用于这件物品。','none':'该强化石没有可附加的属性。','equipped':'请先卸下时装再附加属性。', + 'full':'这件时装的附加属性已满(最多 3 条)。','has':'这件时装已有该属性。','done':'附加属性成功。'} + + +def Enchant(vnum): + """(apply, value, mask) of a costume enchant, else None (selects vnum).""" + if not VNUMS[0]<=vnum<=VNUMS[1]:return None + item.SelectItem(vnum) + if item.GetItemType()!=item.ITEM_TYPE_USE or item.GetUseType(vnum)!="USE_CHANGE_ATTRIBUTE" or not item.GetValue(2):return None + return (item.GetValue(0),item.GetValue(1),item.GetValue(2)) + + +def CanEnchant(enchant,dstSlot): + dstVnum=player.GetItemIndex(dstSlot) + if not dstVnum or player.IsEquipmentSlot(dstSlot):return False + item.SelectItem(dstVnum) + if item.GetItemType()!=item.ITEM_TYPE_COSTUME:return False + target=TARGETS.get(item.GetItemSubType()) + if not target or not enchant[2]&(1<1 else '') + if text:chat.AppendChat(chat.CHAT_TYPE_INFO,L(text)) + return 1 + return _command(self,line) + +game.GameWindow.BINARY_ServerCommand_Run=Command +)PY"; diff --git a/src/platform/ScriptLib/PythonBoot.cpp b/src/platform/ScriptLib/PythonBoot.cpp index 1e260ac4..67e0aa7e 100644 --- a/src/platform/ScriptLib/PythonBoot.cpp +++ b/src/platform/ScriptLib/PythonBoot.cpp @@ -1018,6 +1018,7 @@ namespace PythonBoot #include "ArmorShapesScript.inc" #include "SashScript.inc" #include "MountScript.inc" +#include "CostumeAttrScript.inc" static void install_script_module(const char* c_szModule, const char* c_szScript) { @@ -1104,6 +1105,7 @@ bool RunMainScript(const char* lpCmdLine, std::string* error) install_script_module("mt_shapes", c_szArmorShapesScript); install_script_module("mt_sash", c_szSashScript); install_script_module("mt_mount", c_szMountScript); + install_script_module("mt_costume_attr", c_szCostumeAttrScript); if (g_touch_input) install_script_module("mt_mobile_hud", c_szMobileHUDScript); } diff --git a/src/platform/ScriptLib/SwitchbotScript.inc b/src/platform/ScriptLib/SwitchbotScript.inc index 3b454720..41722ae3 100644 --- a/src/platform/ScriptLib/SwitchbotScript.inc +++ b/src/platform/ScriptLib/SwitchbotScript.inc @@ -188,7 +188,8 @@ class SwitchbotWindow(ui.Window): vnum=player.GetItemIndex(player.INVENTORY,cell) if not vnum:continue item.SelectItem(vnum);kind=item.GetItemType() - if kind==item.ITEM_TYPE_USE and item.GetItemSubType()==USE_CHANGE_ATTRIBUTE: + # 54001-54267 with a value2 mask are AVA2 costume enchants (mt_costume_attr), not switchers. + if kind==item.ITEM_TYPE_USE and item.GetItemSubType()==USE_CHANGE_ATTRIBUTE and not (54001<=vnum<=54267 and item.GetValue(2)): scrolls[vnum]=scrolls.get(vnum,0)+player.GetItemCount(player.INVENTORY,cell) elif kind in (item.ITEM_TYPE_WEAPON,item.ITEM_TYPE_ARMOR) and any(player.GetItemAttribute(player.INVENTORY,cell,i)[0] for i in range(5)): items.append((cell,vnum)) diff --git a/tools/server/ava2-items/make_items.py b/tools/server/ava2-items/make_items.py index 1a5370b2..0638a31f 100644 --- a/tools/server/ava2-items/make_items.py +++ b/tools/server/ava2-items/make_items.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """Stage the AVA2 drop items that the 203 server lacks as ordinary 40250 items (server rows + client patch pack). Only types/subtypes/flags the 40250 server understands survive; items of AVA2-only systems (pets, pendants, -new rings, costume enchants, talisman bonus items, new skill books) become plain ITEM_NONE items until those +new rings, weapon/sash-skin/effect costume enchants, talisman bonus items, new skill books) become plain ITEM_NONE items until those systems are ported. Also writes mobs-server.tsv (MOUNT_MOBS) for install_items.py. Usage: see build_zh_locale.build_ava2_items and --help. """ import argparse,json,re,shutil,subprocess @@ -27,7 +27,11 @@ SASH_EFFECTS=('d:/ymir work/effect/etc/buff/buff_item6.mse','d:/ymir work/effect SASH_LOCALE={'locale/en/ui/acce/acce_combine.tga':'locale/zh/ui/acce/acce_combine.tga', 'locale/en/ui/acce/acce_absorb.tga':'locale/zh/ui/acce/acce_absorb.tga'} # Per-subtype descriptions (override DESC). -DESC_SUB={('ITEM_COSTUME','COSTUME_MOUNT'):'坐骑时装。装备后召唤该坐骑(需要已拥有马匹),装备期间属性生效。', +# Costume enchants (P6, server port_costume_attr.cpp): only the body / hair / mount-skin ranges have a 40250 +# target; AVA2 apply numbers in value0 equal 203's (P2 aligned 139/140). The rest stay locked. +ENCHANT_KEEP=((54001,54078),(54124,54174)) +DESC_SUB={('ITEM_USE','USE_CHANGE_ATTRIBUTE'):'拖到未穿戴的对应时装上,附加一条固定属性(每件时装最多 3 条,不能重复)。', + ('ITEM_COSTUME','COSTUME_MOUNT'):'坐骑时装。装备后召唤该坐骑(需要已拥有马匹),装备期间属性生效。', ('ITEM_COSTUME','COSTUME_MOUNT_SKIN'):'坐骑外观。需先装备坐骑时装,装备后改变坐骑外观。'} # Mount system (P5): races of the mount skins that 203 and the 40250 client lack -> (zh name, npc_mount folder). # Their AVA2 mob_proto rows go to mobs-server.tsv, the whole model folder into the pack; the client's mt_mount @@ -66,7 +70,8 @@ def convert(cols,E,imported): """AVA2 item_proto.txt row (str columns) -> (server row columns, client numeric fields, kept?).""" c=[x.strip() for x in cols[:33]] t,st=c[2],c[3] - kept=t in KEEP_TYPES or (t,st) in KEEP_SUBTYPES + kept=t in KEEP_TYPES or (t,st) in KEEP_SUBTYPES or ((t,st)==('ITEM_USE','USE_CHANGE_ATTRIBUTE') and + any(lo<=int(c[0])<=hi for lo,hi in ENCHANT_KEEP)) anti=tokens(c[5]);flags=tokens(c[6]) if 'ITEM_STACKABLE' in anti:anti.remove('ITEM_STACKABLE');flags.append('ITEM_STACKABLE') anti=[a for a in dict.fromkeys(anti) if a in E['anti']];flags=[f for f in dict.fromkeys(flags) if f in E['flag']] diff --git a/tools/server/ava2-items/tests/test_items.py b/tools/server/ava2-items/tests/test_items.py index 3044e3af..682be6a8 100644 --- a/tools/server/ava2-items/tests/test_items.py +++ b/tools/server/ava2-items/tests/test_items.py @@ -18,10 +18,14 @@ class ConvertTests(unittest.TestCase): self.assertEqual(s[11:13],['0','0']) # the +1 item is not imported self.assertEqual(c[1:3],[self.E['type'].index('ITEM_WEAPON'),0]);self.assertEqual(c[4],1<