Import the AVA2 drop items the server lacks as ordinary 40250 items
tools/server/ava2-items stages the 478 items referenced by the AVA2 drop tables that 203 does not have: server item_proto/item_names rows, a client item_proto/item_list/itemdesc patch over the biolog tables, and the AVA2 icons, ground models (BitKnit gr2 via sdk/gr2) and their textures, packed as ava2items ahead of biolog. Materials, quest items, chests, the guardian weapons and shoes keep their meaning; items of AVA2-only systems (pets, pendants, new rings, mounts, sashes, costume enchants, talisman items, new skill books) become plain ITEM_NONE items until those systems are ported. Names are translated in names_zh.tsv. item_proto_patch is the full-record successor of biolog_proto_patch, with --dump for checking conversions against the shipped table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d11d46c469
commit
90f213c4cc
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Install items-server.tsv (from make_items.py) into share/conf/item_proto.txt + item_names.txt.
|
||||
Rows with the same vnum are replaced, so a rerun is idempotent. The db reads the tables only at boot:
|
||||
restart db and every core afterwards. Usage: install_items.py <server root> <items-server.tsv> <fresh backup dir>
|
||||
"""
|
||||
import shutil,sys
|
||||
from pathlib import Path
|
||||
server,rows,backup=map(Path,sys.argv[1:4])
|
||||
if backup.exists():raise SystemExit('use a fresh backup directory')
|
||||
items={int(l.split(b'\t',1)[0]):l for l in rows.read_bytes().splitlines() if l}
|
||||
backup.mkdir(parents=True)
|
||||
conf=server/'share/conf'
|
||||
for filename,row in (('item_proto.txt',lambda v:items[v]),('item_names.txt',lambda v:str(v).encode()+b'\t'+items[v].split(b'\t')[1])):
|
||||
path=conf/filename;raw=path.read_bytes();shutil.copy2(path,backup/filename)
|
||||
newline=b'\r\n' if b'\r\n' in raw else b'\n'
|
||||
kept=[l for l in raw.replace(b'\r\n',b'\n').split(b'\n') if l and not (l.split(b'\t',1)[0].isdigit() and int(l.split(b'\t',1)[0]) in items)]
|
||||
tmp=path.with_name(filename+'.tmp');tmp.write_bytes(newline.join(kept+[row(v) for v in sorted(items)])+newline);tmp.replace(path)
|
||||
print('AVA2 items installed:',len(items),'backup:',backup)
|
||||
@@ -0,0 +1,147 @@
|
||||
#!/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, mounts, sashes, costume enchants, talisman bonus items, new skill books) become plain ITEM_NONE
|
||||
items until those systems are ported. Usage: see build_zh_locale.build_ava2_items and --help.
|
||||
"""
|
||||
import argparse,json,re,shutil,subprocess
|
||||
from pathlib import Path
|
||||
HERE=Path(__file__).resolve().parent
|
||||
RANGES=((54001,54027,'时装强化石'),(54028,54078,'发型强化石'),(54079,54123,'武器外观强化石'),
|
||||
(54124,54174,'坐骑外观强化石'),(54175,54219,'披风外观强化石'),(54220,54267,'特效强化石'))
|
||||
# Kept with their 40250 meaning; everything else becomes ITEM_NONE (see module doc).
|
||||
KEEP_TYPES={'ITEM_MATERIAL','ITEM_QUEST','ITEM_GIFTBOX','ITEM_NONE'}
|
||||
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')}
|
||||
DESC={'ITEM_MATERIAL':'制作与升级材料。','ITEM_QUEST':'任务物品。','ITEM_GIFTBOX':'打开后可获得物品。','ITEM_WEAPON':'','ITEM_ARMOR':''}
|
||||
LOCKED='该物品所属的系统尚未开放,暂时无法使用。'
|
||||
|
||||
def enums(proto_reader):
|
||||
src=proto_reader.read_text(encoding='latin1')
|
||||
def arr(name):
|
||||
m=re.search(r'(?:static\s+)?string\s+'+name+r'\[\]\s*=\s*\{(.*?)\};',src,re.S)
|
||||
return re.findall(r'"([^"]+)"',m.group(1))
|
||||
types=arr('arType');m=re.search(r'arSubType\[\]\s*=\s*\{(.*?)\};',src,re.S)
|
||||
subs=[arr(t) if t!='0' else [] for t in re.findall(r'\b(0|arSub\d+)\b',re.sub(r'//[^\n]*','',m.group(1)))]
|
||||
assert len(subs)==len(types),(len(subs),len(types))
|
||||
return dict(type=types,sub=subs,anti=arr('arAntiFlag'),flag=arr('arFlag'),wear=arr('arWearrFlag'),
|
||||
imm=arr('arImmune'),limit=arr('arLimitType'),apply=arr('arApplyType'))
|
||||
|
||||
def tokens(text):return [t.strip() for t in text.split('|') if t.strip() and t.strip() not in ('NONE','0')]
|
||||
def number(text):
|
||||
try:return int(text)
|
||||
except ValueError:return 0
|
||||
|
||||
def names():
|
||||
result={}
|
||||
for line in (HERE/'names_zh.tsv').read_text().splitlines():
|
||||
if line and not line.startswith('#'):
|
||||
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
|
||||
return result
|
||||
|
||||
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
|
||||
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']]
|
||||
if not kept:
|
||||
t,st='ITEM_NONE','0';wear=imm=[];applies=[('APPLY_NONE','0')]*3;values=['0']*6
|
||||
flags=[f for f in flags if f in ('ITEM_STACKABLE','LOG')]
|
||||
else:
|
||||
if t in ('ITEM_QUEST','ITEM_GIFTBOX','ITEM_NONE','ITEM_MATERIAL') and st not in E['sub'][E['type'].index(t)]:st='0'
|
||||
wear=[w for w in tokens(c[7]) if w in E['wear']] if t in ('ITEM_WEAPON','ITEM_ARMOR') else []
|
||||
imm=[i for i in tokens(c[8]) if i in E['imm']]
|
||||
applies=[(a,v) if a in E['apply'] else ('APPLY_NONE','0') for a,v in zip(c[18:24:2],c[19:24:2])]
|
||||
values=[str(number(v)) for v in c[24:30]]
|
||||
refine=(c[11],c[12]) if number(c[11]) in imported else ('0','0')
|
||||
limits=[(l if l in E['limit'] else 'LIMIT_NONE',str(number(v))) for l,v in zip(c[14:18:2],c[15:18:2])]
|
||||
j=lambda xs:' | '.join(xs) if xs else 'NONE'
|
||||
server=[c[0],c[1],t,st,c[4],j(anti),j(flags),j(wear),j(imm),c[9],c[10],refine[0],refine[1],c[13]]
|
||||
for l,v in limits:server+=[l,v]
|
||||
for a,v in applies:server+=[a,str(number(v))]
|
||||
server+=values+[str(number(c[30])),str(number(c[31])),str(number(c[32])) if len(c)>32 and c[32] else '0']
|
||||
bits=lambda xs,k:sum(1<<E[k].index(x) for x in xs)
|
||||
ti=E['type'].index(t);si=E['sub'][ti].index(st) if not st.isdigit() else int(st)
|
||||
client=[int(c[0]),ti,si,number(c[4]),bits(anti,'anti'),bits(flags,'flag'),bits(wear,'wear'),bits(imm,'imm'),
|
||||
number(c[9]),number(c[10]),number(refine[0]),number(refine[1]),number(c[13])]
|
||||
for l,v in limits:client+=[E['limit'].index(l),int(v)]
|
||||
for a,v in applies:client+=[E['apply'].index(a),number(v)]
|
||||
client+=[int(v) for v in values]+[number(c[30]),number(c[31])]
|
||||
return server,client,kept
|
||||
|
||||
def pack_index(pack_maker):
|
||||
"""ArchivedPath (lowercase) -> source file, from the AVA2 pack manifests."""
|
||||
index={}
|
||||
for xml in sorted(pack_maker.glob('*_create.xml')):
|
||||
for archived,source in re.findall(r'ArchivedPath="([^"]+)"><!\[CDATA\[([^\]]+)\]\]>',xml.read_text(encoding='latin1')):
|
||||
index.setdefault(archived.lower().replace('\\','/'),pack_maker.parent/source)
|
||||
return index
|
||||
|
||||
def prepare(ava,proto_reader,locale,output,tool,packer,wrap=lambda text:text):
|
||||
"""wrap: the locale's itemdesc line wrapping (build_zh_locale.wrap_description)."""
|
||||
E=enums(proto_reader);zh=names();wanted=set(zh)
|
||||
db=ava/'2. Backup SF+SRC and SQL/home/srv1/db';pack_maker=ava/'1. Client DEV/pack_maker'
|
||||
rows={}
|
||||
for line in (db/'item_proto.txt').read_bytes().splitlines():
|
||||
cols=line.split(b'\t')
|
||||
if cols[0].strip().isdigit() and int(cols[0]) in wanted:rows[int(cols[0])]=[x.decode('latin1') for x in cols]
|
||||
assert rows.keys()==wanted,sorted(wanted-rows.keys())
|
||||
listrows={}
|
||||
for line in (pack_maker/'locale/locale/es/item_list.txt').read_bytes().splitlines():
|
||||
c=[x.strip() for x in line.split(b'\t')]
|
||||
if c and c[0].isdigit() and int(c[0]) in wanted:listrows[int(c[0])]=[x.decode('latin1') for x in c if x]
|
||||
files=pack_index(pack_maker)
|
||||
for v in wanted-listrows.keys(): # 16 items have no AVA2 item_list row; their icons ship under the vnum
|
||||
listrows[v]=[str(v),'ETC','icon/item/%05d.tga'%v]
|
||||
output.mkdir(parents=True,exist_ok=True)
|
||||
assets=output/'assets'
|
||||
if assets.exists():shutil.rmtree(assets)
|
||||
server=[];client=[];report={'kept':[],'locked':[]}
|
||||
for v in sorted(wanted):
|
||||
s,n,kept=convert(rows[v],E,wanted)
|
||||
name=zh[v];s[1]=name
|
||||
server.append('\t'.join(s).encode('gbk'))
|
||||
client.append(('\t'.join(map(str,n))+'\t'+name).encode('gbk'))
|
||||
report['kept' if kept else 'locked'].append(v)
|
||||
copied=0
|
||||
for v,row in listrows.items():
|
||||
for path in row[2:4]:
|
||||
p=path.lower().replace('\\','/')
|
||||
sources=[p]
|
||||
if p.endswith('.gr2'):sources+=[s for s in MODEL_TEXTURES.get(p.rsplit('/',1)[1],())]
|
||||
for q in sources:
|
||||
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
|
||||
(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)
|
||||
subprocess.run([str(tool),str(locale/'item_proto'),str(output/'items-client.tsv'),str(target/'item_proto')],check=True)
|
||||
for filename in ('item_list.txt','itemdesc.txt'):
|
||||
result=[l for l in (locale/filename).read_bytes().splitlines() if not (l.split(b'\t',1)[0].isdigit() and int(l.split(b'\t',1)[0]) in wanted)]
|
||||
for v in sorted(wanted):
|
||||
if filename=='item_list.txt':result.append('\t'.join(listrows[v]).encode('gbk'))
|
||||
else:
|
||||
t=rows[v][2].strip()
|
||||
desc=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)
|
||||
report['assets']=copied
|
||||
(output/'items.json').write_text(json.dumps(report,indent=1)+'\n')
|
||||
return report
|
||||
|
||||
# Textures named inside the (BitKnit-compressed) AVA2 models, read with sdk/gr2.
|
||||
MODEL_TEXTURES={**{'%05d.gr2'%v:('d:/ymir work/item/weapon/weapon_7th_01.dds',) for v in (320,340,1190,2210,3230,5170,7310)},
|
||||
'boss_box.gr2':('d:/ymir work/item/etc/boss_box.dds',),
|
||||
'acce_01_85_004.gr2':('d:/ymir work/item/wing/acce_85_001-008.dds',),'acce_02_85_008.gr2':('d:/ymir work/item/wing/acce_85_001-008.dds',),
|
||||
'acce_03_85_014.gr2':('d:/ymir work/item/wing/acce_85_011-018.dds',),'acce_04_85_018.gr2':('d:/ymir work/item/wing/acce_85_011-018.dds',)}
|
||||
|
||||
if __name__=='__main__':
|
||||
p=argparse.ArgumentParser(description=__doc__)
|
||||
for a in ('ava','proto_reader','locale','output','tool','packer'):p.add_argument(a,type=Path)
|
||||
a=p.parse_args();print(json.dumps({k:len(v) if isinstance(v,list) else v for k,v in prepare(a.ava,a.proto_reader,a.locale,a.output,a.tool.resolve(),a.packer.resolve()).items()}))
|
||||
@@ -0,0 +1,212 @@
|
||||
# AVA2 drop items imported as 40250 items (vnum<TAB>zh name). 54001-54267 are named by range in make_items.py.
|
||||
320 守护者之剑+0
|
||||
340 守护者之刃+0
|
||||
1190 守护者匕首+0
|
||||
2210 守护者之弓+0
|
||||
3230 守护者大刀+0
|
||||
5170 守护者之铃+0
|
||||
6884 稀有度转换器
|
||||
7310 守护者之扇+0
|
||||
9600 火之护符+0
|
||||
9830 冰之护符+0
|
||||
10060 土之护符+0
|
||||
10290 暗之护符+0
|
||||
10520 风之护符+0
|
||||
10750 雷之护符+0
|
||||
15440 炎晶之鞋+0
|
||||
15450 海洋之鞋+0
|
||||
15460 锆石之鞋+0
|
||||
15470 孔雀石之鞋+0
|
||||
19500 红宝石戒指+0
|
||||
19520 石榴石戒指+0
|
||||
19540 祖母绿戒指+0
|
||||
19560 蓝宝石戒指+0
|
||||
21210 暗龙铠甲+0
|
||||
21230 风龙铠甲+0
|
||||
21250 魔龙铠甲+0
|
||||
21270 神龙铠甲+0
|
||||
25163 缩小的头颅
|
||||
25164 蜘蛛钥匙
|
||||
25165 螺旋钥匙
|
||||
25166 缩小的头颅+
|
||||
25167 拉扎多尔入场券
|
||||
25168 奈梅尔入场券
|
||||
25169 九头蛇护身符
|
||||
28444 破碑之石+4
|
||||
28445 克首领之石+4
|
||||
30400 盖亚宝石
|
||||
30600 图样
|
||||
30601 铠甲设计图
|
||||
30602 蓝色染料
|
||||
30603 红色染料
|
||||
30604 灰色染料
|
||||
30605 金板
|
||||
30606 银板
|
||||
30607 铜板
|
||||
30608 钢板
|
||||
30609 青铜板
|
||||
30610 秘银
|
||||
30611 坚韧绑带
|
||||
30612 结实绳索
|
||||
30614 黑色染料
|
||||
30615 白色染料
|
||||
30616 二氧化钛
|
||||
30617 玛瑙
|
||||
30618 月长石
|
||||
30620 红龙之角
|
||||
30621 红龙鳞片
|
||||
30623 智慧之书
|
||||
30624 魔法宝珠
|
||||
30625 帕德玛刚玉
|
||||
30626 蓝晶石
|
||||
30627 神之果实
|
||||
30628 神圣之花
|
||||
30629 众神灵药
|
||||
30630 龙之翼
|
||||
33031 吊坠宝石
|
||||
39070 灰色碎片
|
||||
39071 白色碎片
|
||||
39072 绿色碎片
|
||||
39073 黄色碎片
|
||||
50275 金色欧凯宝箱
|
||||
50276 银色欧凯宝箱
|
||||
50277 铜色欧凯宝箱
|
||||
50569 秘籍·地震
|
||||
50570 秘籍·星光
|
||||
50571 秘籍·风暴射击
|
||||
50572 秘籍·地狱打击
|
||||
50573 秘籍·致命波动
|
||||
50574 秘籍·流星
|
||||
50575 秘籍·灵气护盾
|
||||
50928 金王战利品
|
||||
50929 银王战利品
|
||||
50930 铜王战利品
|
||||
51110 阿拉多尔宝箱
|
||||
51111 奈梅尔宝箱
|
||||
51112 贝兰塞陶宝箱
|
||||
51115 远古守护者宝箱
|
||||
51116 红龙宝箱
|
||||
51117 远古巨龙宝箱
|
||||
53004 鲁弗斯封印(永久)
|
||||
53996 护符附魔道具
|
||||
53997 护符加持道具
|
||||
54705 九头蛇宝箱
|
||||
54706 65级武器宝箱
|
||||
54707 70级武器宝箱
|
||||
54708 75级武器宝箱
|
||||
54709 防具宝箱
|
||||
54710 装备宝箱
|
||||
54711 饰品宝箱
|
||||
54712 生物学家礼包
|
||||
54713 生物学家袋子
|
||||
54714 生物学家袋子+
|
||||
54715 强化药剂包(小)
|
||||
54716 强化药剂包(中)
|
||||
54717 强化药剂包(特大)
|
||||
55001 宠物蛋白补充剂
|
||||
55004 宠物秘籍·幼年
|
||||
55005 宠物秘籍·野性
|
||||
55006 宠物秘籍·勇敢
|
||||
55009 宠物秘籍宝箱
|
||||
55010 宠物书·石碑
|
||||
55011 宠物书·怪物
|
||||
55012 宠物书·首领
|
||||
55013 宠物书·力量
|
||||
55014 宠物书·智力
|
||||
55015 宠物书·敏捷
|
||||
55016 宠物书·体力
|
||||
55017 宠物书·野兽
|
||||
55018 宠物书·兽人
|
||||
55019 宠物书·秘法
|
||||
55020 宠物书·不死
|
||||
55021 宠物书·恶魔
|
||||
55022 宠物书·经验
|
||||
55023 宠物书·掉宝
|
||||
55024 宠物书·金币
|
||||
55025 宠物书·半兽人
|
||||
55026 宠物书·抗半兽人
|
||||
55027 宠物书·技能伤害
|
||||
55028 宠物书·抗技能伤害
|
||||
55029 宠物书·平均伤害
|
||||
55030 宠物书·抗平均伤害
|
||||
55031 宠物书·抗暴击
|
||||
55033 宠物技能清除剂
|
||||
55035 宠物经验增幅剂
|
||||
55036 宠物栏位解锁器
|
||||
55038 宠物书·抗穿透
|
||||
55039 宠物书·施法速度
|
||||
55040 宠物书·格挡
|
||||
55041 宠物书·抗毒
|
||||
55042 宠物书·冰
|
||||
55043 宠物书·土
|
||||
55044 宠物书·暗
|
||||
55101 活力药剂(小)
|
||||
55102 活力药剂(中)
|
||||
55103 活力药剂(大)
|
||||
55104 活力药剂(特大)
|
||||
55108 战斗药剂(小)
|
||||
55109 战斗药剂(中)
|
||||
55110 战斗药剂(大)
|
||||
55111 战斗药剂(特大)
|
||||
55115 力量药剂(小)
|
||||
55116 力量药剂(中)
|
||||
55117 力量药剂(大)
|
||||
55118 力量药剂(特大)
|
||||
55401 猴子蛋
|
||||
55404 奈梅尔蛋
|
||||
55407 阿兹雷尔蛋
|
||||
71097 炼金附魔道具
|
||||
71234 蓝色巴里
|
||||
71254 白色冬日羊驼
|
||||
71301 护符加持道具
|
||||
71302 护符强化道具
|
||||
71502 狮子封印(永久)
|
||||
71505 野猪封印(永久)
|
||||
71508 狼封印(永久)
|
||||
71511 老虎封印(永久)
|
||||
72064 小型扩充券
|
||||
72065 中型扩充券
|
||||
72066 大型扩充券
|
||||
72067 强效扩充券
|
||||
72068 天界扩充券
|
||||
72319 金钥匙
|
||||
72320 银钥匙
|
||||
72346 6-7属性附魔道具
|
||||
77900 药剂宝箱(小)
|
||||
77901 药剂宝箱(中)
|
||||
77902 药剂宝箱(大)
|
||||
77903 药剂宝箱(特大)
|
||||
77904 阅读宝箱
|
||||
77905 护符宝箱
|
||||
77906 耳环宝箱
|
||||
77907 手镯宝箱
|
||||
77908 项链宝箱
|
||||
77909 戒指宝箱
|
||||
77910 强化宝箱(小)
|
||||
77911 强化宝箱(中)
|
||||
77912 强化宝箱(大)
|
||||
77913 炼金宝箱(小)
|
||||
77914 炼金宝箱(中)
|
||||
77915 炼金宝箱(大)
|
||||
77916 战令宝箱
|
||||
77917 任务书宝箱
|
||||
77918 青铜宝箱
|
||||
77919 白银宝箱
|
||||
77920 黄金宝箱
|
||||
77921 青铜钥匙
|
||||
77922 白银钥匙
|
||||
77923 黄金钥匙
|
||||
77924 钥匙宝箱
|
||||
80019 精致布料
|
||||
80025 时装兑换券
|
||||
80030 男爵夫人的宝箱
|
||||
85004 领主披风(个人)
|
||||
85008 大师披风(个人)
|
||||
85014 王朝披风(个人)
|
||||
85018 皇家披风(个人)
|
||||
96001 火之护符碎片
|
||||
96002 冰之护符碎片
|
||||
96003 土之护符碎片
|
||||
96004 暗之护符碎片
|
||||
96005 风之护符碎片
|
||||
96006 雷之护符碎片
|
||||
|
@@ -0,0 +1,35 @@
|
||||
import sys,unittest
|
||||
from pathlib import Path
|
||||
HERE=Path(__file__).resolve().parents[1];sys.path.insert(0,str(HERE))
|
||||
import make_items as m
|
||||
READER=HERE.parents[3]/'metin2-server/src/server/db/src/ProtoReader.cpp'
|
||||
|
||||
def row(vnum,t,st,anti='NONE',flags='NONE',wear='NONE',applies=('APPLY_NONE','0')*3,values=('0',)*6,refine=('0','0')):
|
||||
return [str(vnum),'x',t,st,'1',anti,flags,wear,'NONE','10','20',refine[0],refine[1],'0','LEVEL','120','LIMIT_NONE','0',*applies,*values,'0','0','-1','']
|
||||
|
||||
@unittest.skipUnless(READER.is_file(),'needs ../metin2-server')
|
||||
class ConvertTests(unittest.TestCase):
|
||||
E=m.enums(READER) if READER.is_file() else None
|
||||
def test_weapon_kept_without_unknown_flags(self):
|
||||
s,c,kept=m.convert(row(320,'ITEM_WEAPON','WEAPON_SWORD','ANTI_MUDANG | ANTI_WOLFMAN','ITEM_TUNABLE','WEAR_WEAPON',
|
||||
('APPLY_ATT_SPEED','18','APPLY_ATTBONUS_BOSS','5','APPLY_NONE','0'),refine=('321','81')),self.E,{320})
|
||||
self.assertTrue(kept);self.assertEqual(s[5],'ANTI_MUDANG');self.assertEqual(s[7],'WEAR_WEAPON')
|
||||
self.assertEqual(s[18:22],['APPLY_ATT_SPEED','18','APPLY_NONE','0'])
|
||||
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_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'])
|
||||
def test_quest_subtype(self):
|
||||
s,c,kept=m.convert(row(30400,'ITEM_QUEST','QUEST_NONE',values=('APPLY_NONE','0','0','0','0','0')),self.E,set())
|
||||
self.assertTrue(kept);self.assertEqual(s[3],'0');self.assertEqual(s[24],'0')
|
||||
def test_names_cover_ranges(self):
|
||||
n=m.names();self.assertEqual(len(n),478);self.assertEqual(n[54100],'武器外观强化石')
|
||||
self.assertTrue(all(len(v.encode('gbk'))<=24 for v in n.values()))
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
Reference in New Issue
Block a user