AVA2 unlock P3: dragon armor 21210-21279 (+0..+9) with armor shape 32

Keep ARMOR_BODY rows, import the whole refine chain, ship AVA2's <race>_7_1
models and register shape 32 after each race msm load (mt_shapes): the race
msm are read by bare name from the root pack and cannot be overridden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-10-16 11:13:41 +09:00
co-authored by Claude Opus 5.5
parent 178fcf48fe
commit 0d2a9ebe36
5 changed files with 68 additions and 6 deletions
+22 -1
View File
@@ -9,9 +9,15 @@ from pathlib import Path
HERE=Path(__file__).resolve().parent
RANGES=((54001,54027,'时装强化石'),(54028,54078,'发型强化石'),(54079,54123,'武器外观强化石'),
(54124,54174,'坐骑外观强化石'),(54175,54219,'披风外观强化石'),(54220,54267,'特效强化石'))
# Dragon armor: +0..+9 per class (the refine chain needs every step).
DRAGON={21210:'暗龙铠甲',21230:'风龙铠甲',21250:'魔龙铠甲',21270:'神龙铠甲'}
# Models of the armor shapes the 40250 race msm lack (registered by the client's mt_shapes script): shape ->
# model stem ('%s' = race); RACE_PATHS = the ShapeData PathName of each race msm.
ARMOR_SHAPES={32:'%s_7_1'}
RACE_PATHS={'warrior':('pc','pc2'),'assassin':('pc2','pc'),'sura':('pc','pc2'),'shaman':('pc2','pc')} # (male, female)
# Kept with their 40250 meaning; everything else becomes ITEM_NONE (see module doc).
KEEP_TYPES={'ITEM_MATERIAL','ITEM_QUEST','ITEM_GIFTBOX','ITEM_NONE','ITEM_TREASURE_BOX','ITEM_TREASURE_KEY'}
KEEP_SUBTYPES={('ITEM_WEAPON',s) for s in ('WEAPON_SWORD','WEAPON_DAGGER','WEAPON_BOW','WEAPON_TWO_HANDED','WEAPON_BELL','WEAPON_FAN')}|{('ITEM_ARMOR','ARMOR_FOOTS'),('ITEM_METIN','METIN_NORMAL')}
KEEP_SUBTYPES={('ITEM_WEAPON',s) for s in ('WEAPON_SWORD','WEAPON_DAGGER','WEAPON_BOW','WEAPON_TWO_HANDED','WEAPON_BELL','WEAPON_FAN')}|{('ITEM_ARMOR','ARMOR_FOOTS'),('ITEM_ARMOR','ARMOR_BODY'),('ITEM_METIN','METIN_NORMAL')}
DESC={'ITEM_METIN':'可镶嵌到武器的空孔中。','ITEM_MATERIAL':'制作与升级材料。','ITEM_QUEST':'任务物品。','ITEM_GIFTBOX':'打开后可获得物品。','ITEM_TREASURE_BOX':'需要对应的钥匙才能打开。','ITEM_TREASURE_KEY':'可以打开对应的宝箱。','ITEM_WEAPON':'','ITEM_ARMOR':''}
LOCKED='该物品所属的系统尚未开放,暂时无法使用。'
@@ -38,6 +44,8 @@ def names():
v,n=line.split('\t');result[int(v)]=n
for lo,hi,n in RANGES:
for v in range(lo,hi+1):result[v]=n
for base,n in DRAGON.items():
for k in range(10):result[base+k]='%s+%d'%(n,k)
return result
def convert(cols,E,imported):
@@ -73,6 +81,15 @@ def convert(cols,E,imported):
client+=[int(v) for v in values]+[number(c[30]),number(c[31])]
return server,client,kept
def shape_models():
"""Pack paths of the ARMOR_SHAPES models (+ LODs) and skins of every race."""
out=[]
for race,dirs in RACE_PATHS.items():
for d in dict.fromkeys(dirs):
for stem in ARMOR_SHAPES.values():
out+=['d:/ymir work/%s/%s/%s%s'%(d,race,stem%race,e) for e in ('.gr2','.dds','_lod_01.gr2','_lod_02.gr2','_lod_03.gr2')]
return out
def pack_index(pack_maker):
"""ArchivedPath (lowercase) -> source file, from the AVA2 pack manifests."""
index={}
@@ -117,6 +134,10 @@ def prepare(ava,proto_reader,locale,output,tool,packer,wrap=lambda text:text):
if q not in files:raise SystemExit('missing AVA2 asset %s for %d'%(q,v))
target=assets/q;target.parent.mkdir(parents=True,exist_ok=True)
if not target.exists():shutil.copy2(files[q],target);copied+=1
for q in shape_models():
if q not in files:raise SystemExit('missing AVA2 asset %s'%q)
target=assets/q;target.parent.mkdir(parents=True,exist_ok=True)
if not target.exists():shutil.copy2(files[q],target);copied+=1
(output/'items-server.tsv').write_bytes(b'\n'.join(server)+b'\n')
(output/'items-client.tsv').write_bytes(b'\n'.join(client)+b'\n')
target=assets/'locale/zh';target.mkdir(parents=True,exist_ok=True)
-4
View File
@@ -21,10 +21,6 @@
19520 石榴石戒指+0
19540 祖母绿戒指+0
19560 蓝宝石戒指+0
21210 暗龙铠甲+0
21230 风龙铠甲+0
21250 魔龙铠甲+0
21270 神龙铠甲+0
25163 缩小的头颅
25164 蜘蛛钥匙
25165 螺旋钥匙
1 # AVA2 drop items imported as 40250 items (vnum<TAB>zh name). 54001-54267 are named by range in make_items.py.
21 19520
22 19540
23 19560
21210
21230
21250
21270
24 25163
25 25164
26 25165
+10 -1
View File
@@ -39,8 +39,17 @@ class ConvertTests(unittest.TestCase):
self.assertTrue(kept);self.assertEqual(s[7],'WEAR_WEAPON');self.assertEqual(s[18:20],['APPLY_ATTBONUS_STONE','8'])
self.assertEqual(c[17:19],[139,8]) # AVA2 numbering, shared with the server and client enums
self.assertEqual(self.E['apply'].index('APPLY_ATTBONUS_BOSS'),140);self.assertEqual(s[29],'31')
def test_dragon_armor_kept_with_refine_chain(self):
s,c,kept=m.convert(row(21210,'ITEM_ARMOR','ARMOR_BODY','ANTI_ASSASSIN | ANTI_SURA | ANTI_MUDANG | ANTI_WOLFMAN','ITEM_TUNABLE','WEAR_BODY',
('APPLY_MOV_SPEED','5','APPLY_RESIST_MAGIC','25','APPLY_MAX_HP_PCT','20'),('0','210','0','32','0','0'),('21211','81')),self.E,{21210,21211})
self.assertTrue(kept);self.assertEqual(s[3],'ARMOR_BODY');self.assertEqual(s[7],'WEAR_BODY');self.assertEqual(s[11:13],['21211','81'])
self.assertEqual(s[5],'ANTI_ASSASSIN | ANTI_SURA | ANTI_MUDANG');self.assertEqual(s[27],'32') # value3 = armor shape
models=m.shape_models();self.assertEqual(len(models),40)
self.assertIn('d:/ymir work/pc2/warrior/warrior_7_1_lod_03.gr2',models)
def test_names_cover_ranges(self):
n=m.names();self.assertEqual(len(n),478);self.assertEqual(n[54100],'武器外观强化石')
n=m.names();self.assertEqual(len(n),514);self.assertEqual(n[54100],'武器外观强化石')
self.assertEqual([n[v] for v in (21210,21219,21279)],['暗龙铠甲+0','暗龙铠甲+9','神龙铠甲+9'])
self.assertTrue(all(len(v.encode('gbk'))<=24 for v in n.values()))
if __name__=='__main__':unittest.main()