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:
co-authored by
Claude Opus 5.5
parent
178fcf48fe
commit
0d2a9ebe36
@@ -0,0 +1,34 @@
|
||||
// PORT: armor shapes added after 40250 (AVA2's dragon armor 21210+, item value3 = 32). The race .msm files are
|
||||
// read by bare name, which CEterPackManager always serves from the root pack, so a patch pack cannot replace
|
||||
// them; register the extra shapes after each LoadLocalRaceData instead, as the AVA2 msm groups would. Models
|
||||
// ship in the ava2items pack (metin2-client tools/server/ava2-items, ARMOR_SHAPES / RACE_PATHS).
|
||||
static const char c_szArmorShapesScript[] = R"PY(# -*- coding: utf-8 -*-
|
||||
import chrmgr
|
||||
|
||||
SHAPES = {32: "%s_7_1"}
|
||||
# ShapeData PathName of each race msm (male, female).
|
||||
PATHS = {"warrior": ("pc", "pc2"), "assassin": ("pc2", "pc"), "sura": ("pc", "pc2"), "shaman": ("pc2", "pc")}
|
||||
|
||||
_load = chrmgr.LoadLocalRaceData
|
||||
|
||||
|
||||
def Register(name):
|
||||
stem = name.lower().split(".")[0]
|
||||
race, _, sex = stem.partition("_")
|
||||
if race not in PATHS or sex not in ("m", "w"):
|
||||
return
|
||||
path = "d:/ymir work/%s/%s/" % (PATHS[race][sex == "w"], race)
|
||||
for shape, model in SHAPES.items():
|
||||
model = path + model % race
|
||||
chrmgr.SetShapeModel(shape, model + ".gr2")
|
||||
chrmgr.AppendShapeSkin(shape, 0, model + ".dds", model + ".dds")
|
||||
|
||||
|
||||
def LoadLocalRaceData(name):
|
||||
result = _load(name)
|
||||
Register(name)
|
||||
return result
|
||||
|
||||
|
||||
chrmgr.LoadLocalRaceData = LoadLocalRaceData
|
||||
)PY";
|
||||
@@ -1012,6 +1012,7 @@ namespace PythonBoot
|
||||
#include "ModelPreviewScript.inc"
|
||||
#include "BattlePassScript.inc"
|
||||
#include "AppliesScript.inc"
|
||||
#include "ArmorShapesScript.inc"
|
||||
|
||||
static void install_script_module(const char* c_szModule, const char* c_szScript)
|
||||
{
|
||||
@@ -1095,6 +1096,7 @@ bool RunMainScript(const char* lpCmdLine, std::string* error)
|
||||
install_script_module("mt_preview", c_szModelPreviewScript);
|
||||
install_script_module("mt_battlepass", c_szBattlePassScript);
|
||||
install_script_module("mt_applies", c_szAppliesScript);
|
||||
install_script_module("mt_shapes", c_szArmorShapesScript);
|
||||
if (g_touch_input)
|
||||
install_script_module("mt_mobile_hud", c_szMobileHUDScript);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -21,10 +21,6 @@
|
||||
19520 石榴石戒指+0
|
||||
19540 祖母绿戒指+0
|
||||
19560 蓝宝石戒指+0
|
||||
21210 暗龙铠甲+0
|
||||
21230 风龙铠甲+0
|
||||
21250 魔龙铠甲+0
|
||||
21270 神龙铠甲+0
|
||||
25163 缩小的头颅
|
||||
25164 蜘蛛钥匙
|
||||
25165 螺旋钥匙
|
||||
|
||||
|
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user