AVA2 unlock P7: talismans (护符) and their attribute items
- ARMOR_PENDANT / APPLY_ENCHANT_* 123-128 / c_Equipment_Pendant (server WEAR_PENDANT 27) constants - mt_pendant: equipment slot under the earring, drop targets for 71302/53996 (reroll) and 71301/53997 (add, max 5), MTPENDANT messages; enchant apply texts in mt_applies - switchbot ignores talismans and talisman rerolls - make_items keeps the six +0..+200 chains (1206 items) and the four attribute items - tools/server/pendant: refine sets 440-499 (AVA2 rows) + install.sh Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a1b63c0afb
commit
b9af78ef64
@@ -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, weapon/sash-skin/effect costume enchants, talisman bonus items, new skill books) become plain ITEM_NONE items until those
|
||||
new rings, weapon/sash-skin/effect costume enchants, 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
|
||||
@@ -11,13 +11,18 @@ RANGES=((54001,54027,'时装强化石'),(54028,54078,'发型强化石'),(54079,5
|
||||
(54124,54174,'坐骑外观强化石'),(54175,54219,'披风外观强化石'),(54220,54267,'特效强化石'))
|
||||
# Dragon armor: +0..+9 per class (the refine chain needs every step).
|
||||
DRAGON={21210:'暗龙铠甲',21230:'风龙铠甲',21250:'魔龙铠甲',21270:'神龙铠甲'}
|
||||
# Talismans (P7, server port_pendant.cpp): +0..+200 per element, refine sets 440-499 (../pendant/refine.sql).
|
||||
PENDANT={9600:'火之护符',9830:'冰之护符',10060:'土之护符',10290:'暗之护符',10520:'风之护符',10750:'雷之护符'}
|
||||
PENDANT_STEPS=201
|
||||
# Talisman attribute items: 71302 / 53996 reroll, 71301 / 53997 add (53996 / 53997 are generic on AVA2).
|
||||
PENDANT_SCROLLS={53996,53997,71301,71302}
|
||||
# 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_ARMOR','ARMOR_BODY'),('ITEM_METIN','METIN_NORMAL')}|{('ITEM_COSTUME',s) for s in ('COSTUME_SASH','COSTUME_MOUNT','COSTUME_MOUNT_SKIN')}
|
||||
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_ARMOR','ARMOR_PENDANT'),('ITEM_METIN','METIN_NORMAL')}|{('ITEM_COSTUME',s) for s in ('COSTUME_SASH','COSTUME_MOUNT','COSTUME_MOUNT_SKIN')}
|
||||
DESC={'ITEM_METIN':'可镶嵌到武器的空孔中。','ITEM_MATERIAL':'制作与升级材料。','ITEM_QUEST':'任务物品。','ITEM_GIFTBOX':'打开后可获得物品。','ITEM_TREASURE_BOX':'需要对应的钥匙才能打开。','ITEM_TREASURE_KEY':'可以打开对应的宝箱。','ITEM_WEAPON':'','ITEM_ARMOR':'','ITEM_COSTUME':'披风。可在披风工匠处合成升级,或吸收武器、铠甲的部分属性。'}
|
||||
# Sash system (metin2-server docs/FEATURE-AVA2-UNLOCK.md P4): effects registered by the client's mt_sash script
|
||||
# (files they reference are staged too), the window images, and item_scale.txt (model scale per job/sex; a row
|
||||
@@ -32,7 +37,11 @@ SASH_LOCALE={'locale/en/ui/acce/acce_combine.tga':'locale/zh/ui/acce/acce_combin
|
||||
ENCHANT_KEEP=((54001,54078),(54124,54174))
|
||||
DESC_SUB={('ITEM_USE','USE_CHANGE_ATTRIBUTE'):'拖到未穿戴的对应时装上,附加一条固定属性(每件时装最多 3 条,不能重复)。',
|
||||
('ITEM_COSTUME','COSTUME_MOUNT'):'坐骑时装。装备后召唤该坐骑(需要已拥有马匹),装备期间属性生效。',
|
||||
('ITEM_COSTUME','COSTUME_MOUNT_SKIN'):'坐骑外观。需先装备坐骑时装,装备后改变坐骑外观。'}
|
||||
('ITEM_COSTUME','COSTUME_MOUNT_SKIN'):'坐骑外观。需先装备坐骑时装,装备后改变坐骑外观。',
|
||||
('ITEM_ARMOR','ARMOR_PENDANT'):'护符。装备在护符栏,对相应属性的怪物伤害提高(强化等级 +1 = +1%)。可用护符强化道具重洗属性,用护符加持道具追加属性(最多 5 条)。'}
|
||||
# Per-vnum descriptions (override DESC_SUB).
|
||||
DESC_VNUM={53996:'拖到未穿戴的护符上,重新随机全部属性。',71302:'拖到未穿戴的护符上,重新随机全部属性。',
|
||||
53997:'拖到未穿戴的护符上,追加一条属性(最多 5 条)。',71301:'拖到未穿戴的护符上,追加一条属性(最多 5 条)。'}
|
||||
# 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
|
||||
# script registers the race names (npclist.txt is read from the root pack only).
|
||||
@@ -64,6 +73,8 @@ def names():
|
||||
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)
|
||||
for base,n in PENDANT.items():
|
||||
for k in range(PENDANT_STEPS):result[base+k]='%s+%d'%(n,k)
|
||||
return result
|
||||
|
||||
def convert(cols,E,imported):
|
||||
@@ -71,7 +82,8 @@ def convert(cols,E,imported):
|
||||
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 or ((t,st)==('ITEM_USE','USE_CHANGE_ATTRIBUTE') and
|
||||
any(lo<=int(c[0])<=hi for lo,hi in ENCHANT_KEEP))
|
||||
any(lo<=int(c[0])<=hi for lo,hi in ENCHANT_KEEP)) or (
|
||||
(t,st) in (('ITEM_USE','USE_CHANGE_ATTRIBUTE'),('ITEM_USE','USE_ADD_ATTRIBUTE')) and int(c[0]) in PENDANT_SCROLLS)
|
||||
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']]
|
||||
@@ -196,7 +208,7 @@ def prepare(ava,proto_reader,locale,output,tool,packer,wrap=lambda text:text):
|
||||
if filename=='item_list.txt':result.append('\t'.join(listrows[v]).encode('gbk'))
|
||||
else:
|
||||
t=rows[v][2].strip()
|
||||
desc=DESC_SUB.get((t,rows[v][3].strip()),DESC.get(t,'')) if v in report['kept'] else LOCKED
|
||||
desc=DESC_VNUM.get(v) or DESC_SUB.get((t,rows[v][3].strip()),DESC.get(t,'')) if v in report['kept'] else LOCKED
|
||||
result.append(('%d\t%s\t%s'%(v,zh[v],wrap(desc) if desc else '')).encode('gbk'))
|
||||
(target/filename).write_bytes(b'\n'.join(result)+b'\n')
|
||||
subprocess.run([str(packer),str(output/'ava2items'),str(assets),''],check=True)
|
||||
|
||||
@@ -22,7 +22,7 @@ class ConvertTests(unittest.TestCase):
|
||||
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
|
||||
for vnum,mask in ((54100,'8'),(54200,'32'),(54250,'64')): # weapon costume / sash skin / effect
|
||||
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'))
|
||||
@@ -63,6 +63,17 @@ class ConvertTests(unittest.TestCase):
|
||||
s,c,kept=m.convert(row(71234,'ITEM_COSTUME','COSTUME_MOUNT_SKIN',values=('32012','0','0','0','0','0')),self.E,{71234})
|
||||
self.assertTrue(kept);self.assertEqual(s[3],'COSTUME_MOUNT_SKIN');self.assertEqual(c[2],4)
|
||||
self.assertEqual(set(m.MOUNT_MOBS),{32012,32028})
|
||||
def test_pendant_and_scrolls_kept(self):
|
||||
s,c,kept=m.convert(row(9601,'ITEM_ARMOR','ARMOR_PENDANT',flags='ITEM_TUNABLE',wear='WEAR_PENDANT',
|
||||
applies=('APPLY_ENCHANT_FIRE','1','APPLY_NONE','0','APPLY_NONE','0'),refine=('9602','441')),self.E,{9601,9602})
|
||||
self.assertTrue(kept);self.assertEqual(s[3],'ARMOR_PENDANT');self.assertEqual(s[7],'NONE') # no WEAR_PENDANT flag: FindEquipCell
|
||||
self.assertEqual(s[11:13],['9602','441']);self.assertEqual(s[18:20],['APPLY_ENCHANT_FIRE','1']);self.assertEqual(c[17],124)
|
||||
self.assertEqual(c[2],self.E['sub'][self.E['type'].index('ITEM_ARMOR')].index('ARMOR_PENDANT'))
|
||||
for vnum,st in ((53996,'USE_CHANGE_ATTRIBUTE'),(71302,'USE_CHANGE_ATTRIBUTE'),(53997,'USE_ADD_ATTRIBUTE'),(71301,'USE_ADD_ATTRIBUTE')):
|
||||
s,c,kept=m.convert(row(vnum,'ITEM_USE',st,flags='ITEM_STACKABLE'),self.E,set())
|
||||
self.assertTrue(kept);self.assertEqual(s[2:4],['ITEM_USE',st])
|
||||
s,c,kept=m.convert(row(71303,'ITEM_USE','USE_ADD_ATTRIBUTE'),self.E,set());self.assertFalse(kept)
|
||||
n=m.names();self.assertEqual([n[v] for v in (9600,9800,10950)],['火之护符+0','火之护符+200','雷之护符+200'])
|
||||
def test_install_items_with_mobs(self):
|
||||
import subprocess,tempfile
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
@@ -91,7 +102,7 @@ class ConvertTests(unittest.TestCase):
|
||||
with self.assertRaises(SystemExit):m.effect_files(files)
|
||||
|
||||
def test_names_cover_ranges(self):
|
||||
n=m.names();self.assertEqual(len(n),514);self.assertEqual(n[54100],'武器外观强化石')
|
||||
n=m.names();self.assertEqual(len(n),514+6*200);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()))
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Talisman refine sets
|
||||
|
||||
AVA2 talismans (护符, metin2-server docs/FEATURE-AVA2-UNLOCK.md P7) refine +0..+200 through 201 items per element. Every step uses one of ten refine sets per element: fragment ×1 + 30031 ×1 + 33031 ×10, 50000 yang, 100 % (AVA2's own rows).
|
||||
|
||||
| Element | Items | Sets | Fragment |
|
||||
|---|---|---|---|
|
||||
| fire | 9600–9800 | 440–449 | 96001 |
|
||||
| ice | 9830–10030 | 460–469 | 96002 |
|
||||
| earth | 10060–10260 | 480–489 | 96003 |
|
||||
| dark | 10290–10490 | 490–499 | 96004 |
|
||||
| wind | 10520–10720 | 470–479 | 96005 |
|
||||
| elect | 10750–10950 | 450–459 | 96006 |
|
||||
|
||||
`install.sh` (on the server) backs up rows 440–499, replaces them from `refine.sql`, and checks 60 rows. The db loads refine_proto at boot, so restart the db afterwards. The items themselves come from `../ava2-items`.
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
# Installs the talisman refine sets (README.md) into player.refine_proto; the db reads them at boot, so restart the db.
|
||||
set -eu
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
MYSQL=${MYSQL:-mysql -umetin2 -ppassword}
|
||||
BACKUP=/root/backup-pendant/$(date +%Y%m%d-%H%M%S)
|
||||
mkdir -p "$BACKUP"
|
||||
$MYSQL player -e 'SELECT * FROM refine_proto WHERE id BETWEEN 440 AND 499' > "$BACKUP/refine_440_499.tsv"
|
||||
echo "backup: $BACKUP"
|
||||
$MYSQL player < "$HERE/refine.sql"
|
||||
[ "$($MYSQL -N player -e 'SELECT COUNT(*) FROM refine_proto WHERE id BETWEEN 440 AND 499 AND cost=50000')" -eq 60 ] || { echo "refine rows missing" >&2; exit 1; }
|
||||
echo "done; restart the db"
|
||||
@@ -0,0 +1,63 @@
|
||||
-- AVA2 talisman refine sets 440-499 (AVA2 player.refine_proto): element fragment x1 + 30031 x1 + 33031 x10, 50000 yang, 100 %.
|
||||
-- Step n of a talisman chain uses set base + n % 10 (fire 440, elect 450, ice 460, wind 470, earth 480, dark 490).
|
||||
REPLACE INTO refine_proto (id,vnum0,vnum1,vnum2,vnum3,vnum4,count0,count1,count2,count3,count4,cost,src_vnum,result_vnum,prob) VALUES
|
||||
(440,96001,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(441,96001,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(442,96001,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(443,96001,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(444,96001,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(445,96001,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(446,96001,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(447,96001,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(448,96001,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(449,96001,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(450,96006,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(451,96006,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(452,96006,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(453,96006,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(454,96006,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(455,96006,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(456,96006,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(457,96006,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(458,96006,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(459,96006,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(460,96002,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(461,96002,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(462,96002,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(463,96002,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(464,96002,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(465,96002,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(466,96002,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(467,96002,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(468,96002,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(469,96002,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(470,96005,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(471,96005,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(472,96005,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(473,96005,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(474,96005,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(475,96005,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(476,96005,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(477,96005,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(478,96005,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(479,96005,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(480,96003,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(481,96003,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(482,96003,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(483,96003,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(484,96003,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(485,96003,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(486,96003,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(487,96003,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(488,96003,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(489,96003,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(490,96004,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(491,96004,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(492,96004,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(493,96004,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(494,96004,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(495,96004,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(496,96004,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(497,96004,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(498,96004,30031,33031,0,0,1,1,10,0,0,50000,0,0,100),
|
||||
(499,96004,30031,33031,0,0,1,1,10,0,0,50000,0,0,100);
|
||||
Reference in New Issue
Block a user