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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1412806ee9
commit
a1b63c0afb
@@ -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 <code>" 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<<target[0]):return False
|
||||
attrs=[player.GetItemAttribute(dstSlot,i) for i in xrange(player.ATTRIBUTE_SLOT_MAX_NUM)]
|
||||
used=[a for a in attrs if a[0]]
|
||||
return len(used)<MAX_ATTR and enchant[0] not in [a[0] for a in used]
|
||||
|
||||
|
||||
_canUse=uiInventory.InventoryWindow._InventoryWindow__CanUseSrcItemToDstItem
|
||||
|
||||
def CanUseSrcItemToDstItem(self,srcItemVNum,srcSlotPos,dstSlotPos):
|
||||
enchant=Enchant(srcItemVNum)
|
||||
if enchant is None:return _canUse(self,srcItemVNum,srcSlotPos,dstSlotPos)
|
||||
return srcSlotPos!=dstSlotPos and CanEnchant(enchant,dstSlotPos)
|
||||
|
||||
uiInventory.InventoryWindow._InventoryWindow__CanUseSrcItemToDstItem=CanUseSrcItemToDstItem
|
||||
|
||||
|
||||
Tip=uiToolTip.ItemToolTip
|
||||
_addItemData=Tip.AddItemData
|
||||
|
||||
def AddItemData(self,itemVnum,metinSlot,attrSlot=0,flags=0,unbindTime=0):
|
||||
result=_addItemData(self,itemVnum,metinSlot,attrSlot,flags,unbindTime)
|
||||
enchant=Enchant(itemVnum)
|
||||
if enchant:
|
||||
self.AppendSpace(5)
|
||||
affect=self._ItemToolTip__GetAffectString(enchant[0],enchant[1])
|
||||
if affect:self.AppendTextLine(affect,self.POSITIVE_COLOR)
|
||||
names=[name for bit,name in sorted(TARGETS.values()) if enchant[2]&(1<<bit)]
|
||||
if names:self.AppendTextLine(L('适用:'+'、'.join(names)),self.CONDITION_COLOR)
|
||||
self.ResizeToolTip()
|
||||
return result
|
||||
|
||||
Tip.AddItemData=AddItemData
|
||||
|
||||
|
||||
_command=game.GameWindow.BINARY_ServerCommand_Run
|
||||
|
||||
def Command(self,line):
|
||||
if line.startswith('MTCATTR '):
|
||||
words=line.split()
|
||||
text=MESSAGES.get(words[1] if len(words)>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";
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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']]
|
||||
|
||||
@@ -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<<self.E['anti'].index('ANTI_MUDANG'))
|
||||
self.assertEqual(len(s),33);self.assertEqual(len(c),31)
|
||||
def test_costume_enchant_is_locked(self):
|
||||
s,c,kept=m.convert(row(54001,'ITEM_USE','USE_CHANGE_ATTRIBUTE',flags='ITEM_STACKABLE',values=('23','10','2','0','0','0')),self.E,set())
|
||||
self.assertFalse(kept);self.assertEqual(s[2:4],['ITEM_NONE','0']);self.assertEqual(s[24:30],['0']*6)
|
||||
self.assertEqual(c[1:3],[0,0]);self.assertEqual(c[5],1<<self.E['flag'].index('ITEM_STACKABLE'))
|
||||
def test_costume_enchant_kept_only_for_ported_targets(self):
|
||||
for vnum,mask in ((54001,'2'),(54078,'4'),(54130,'16')): # body / hair / mount skin (port_costume_attr.cpp)
|
||||
s,c,kept=m.convert(row(vnum,'ITEM_USE','USE_CHANGE_ATTRIBUTE',flags='ITEM_STACKABLE',values=('23','10',mask,'0','0','0')),self.E,set())
|
||||
self.assertTrue(kept);self.assertEqual(s[2:4],['ITEM_USE','USE_CHANGE_ATTRIBUTE']);self.assertEqual(s[24:27],['23','10',mask])
|
||||
for vnum,mask in ((54100,'8'),(54200,'32'),(54250,'64'),(53996,'0')): # weapon costume / sash skin / effect / talisman
|
||||
s,c,kept=m.convert(row(vnum,'ITEM_USE','USE_CHANGE_ATTRIBUTE',flags='ITEM_STACKABLE',values=('23','10',mask,'0','0','0')),self.E,set())
|
||||
self.assertFalse(kept);self.assertEqual(s[2:4],['ITEM_NONE','0']);self.assertEqual(s[24:30],['0']*6)
|
||||
self.assertEqual(c[1:3],[0,0]);self.assertEqual(c[5],1<<self.E['flag'].index('ITEM_STACKABLE'))
|
||||
def test_unknown_type_and_stackable_anti(self):
|
||||
s,_,kept=m.convert(row(55010,'ITEM_PET','PET_SKILL','ITEM_STACKABLE','LOG','WEAR_PENDANT'),self.E,set())
|
||||
self.assertFalse(kept);self.assertEqual(s[5:8],['NONE','LOG | ITEM_STACKABLE','NONE'])
|
||||
|
||||
Reference in New Issue
Block a user