리뉴얼
This commit is contained in:
107
분석도구/legacy/analyze_character_stats.py
Normal file
107
분석도구/legacy/analyze_character_stats.py
Normal file
@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
캐릭터 스탯 분석 스크립트
|
||||
|
||||
DT_CharacterStat 테이블에서 스토커들의 기본 스탯을 추출하고 비교 분석합니다.
|
||||
|
||||
사용법:
|
||||
python analyze_character_stats.py <DataTable.json 경로>
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def find_character_stat_table(datatables):
|
||||
"""DT_CharacterStat 테이블 찾기"""
|
||||
for dt in datatables:
|
||||
if dt.get('AssetName') == 'DT_CharacterStat':
|
||||
return dt
|
||||
return None
|
||||
|
||||
|
||||
def analyze_stats(json_path):
|
||||
"""캐릭터 스탯 분석"""
|
||||
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
datatables = json.load(f)
|
||||
|
||||
char_stat_table = find_character_stat_table(datatables)
|
||||
|
||||
if not char_stat_table:
|
||||
print("오류: DT_CharacterStat 테이블을 찾을 수 없습니다.")
|
||||
return
|
||||
|
||||
stalkers = []
|
||||
|
||||
for row in char_stat_table.get('Rows', []):
|
||||
data = row['Data']
|
||||
stalker_info = {
|
||||
'id': row['RowName'],
|
||||
'name': data.get('name', ''),
|
||||
'job': data.get('jobName', ''),
|
||||
'str': data.get('str', 0),
|
||||
'dex': data.get('dex', 0),
|
||||
'int': data.get('int', 0),
|
||||
'con': data.get('con', 0),
|
||||
'wis': data.get('wis', 0),
|
||||
'hp': data.get('hP', 0),
|
||||
'mp': data.get('mP', 0)
|
||||
}
|
||||
stalkers.append(stalker_info)
|
||||
|
||||
return stalkers
|
||||
|
||||
|
||||
def print_stat_table(stalkers):
|
||||
"""스탯 테이블 출력"""
|
||||
print("\n스토커별 기본 스탯")
|
||||
print("=" * 100)
|
||||
print(f"{'이름':<10} {'직업':<10} {'STR':>5} {'DEX':>5} {'INT':>5} {'CON':>5} {'WIS':>5} {'HP':>5} {'MP':>5}")
|
||||
print("-" * 100)
|
||||
|
||||
for s in stalkers:
|
||||
print(f"{s['name']:<10} {s['job']:<10} {s['str']:>5} {s['dex']:>5} {s['int']:>5} {s['con']:>5} {s['wis']:>5} {s['hp']:>5} {s['mp']:>5}")
|
||||
|
||||
|
||||
def print_stat_rankings(stalkers):
|
||||
"""스탯별 랭킹 출력"""
|
||||
print("\n\n스탯별 랭킹")
|
||||
print("=" * 100)
|
||||
|
||||
stats = ['str', 'dex', 'int', 'con', 'wis']
|
||||
stat_names = {'str': 'STR', 'dex': 'DEX', 'int': 'INT', 'con': 'CON', 'wis': 'WIS'}
|
||||
|
||||
for stat in stats:
|
||||
sorted_stalkers = sorted(stalkers, key=lambda x: x[stat], reverse=True)
|
||||
top3 = sorted_stalkers[:3]
|
||||
|
||||
print(f"\n{stat_names[stat]} 순위:")
|
||||
for i, s in enumerate(top3, 1):
|
||||
print(f" {i}위: {s['name']} ({s[stat]})")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("사용법: python analyze_character_stats.py <DataTable.json 경로>")
|
||||
sys.exit(1)
|
||||
|
||||
json_path = Path(sys.argv[1])
|
||||
|
||||
if not json_path.exists():
|
||||
print(f"오류: 파일을 찾을 수 없습니다: {json_path}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"분석 중: {json_path}")
|
||||
|
||||
stalkers = analyze_stats(json_path)
|
||||
|
||||
if stalkers:
|
||||
print(f"\n총 {len(stalkers)}명의 스토커 발견")
|
||||
print_stat_table(stalkers)
|
||||
print_stat_rankings(stalkers)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
104
분석도구/legacy/analyze_ge_blueprints.py
Normal file
104
분석도구/legacy/analyze_ge_blueprints.py
Normal file
@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
GameplayEffect Blueprint 분석
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
def extract_ge_name(ge_path):
|
||||
"""GE 경로에서 이름 추출"""
|
||||
return ge_path.split('/')[-1].split('.')[0]
|
||||
|
||||
def analyze_ge_blueprint(ge_asset):
|
||||
"""GE Blueprint에서 중요 정보 추출"""
|
||||
asset_name = ge_asset.get('AssetName', '')
|
||||
|
||||
# Variables 섹션 찾기
|
||||
variables = []
|
||||
if 'Variables' in ge_asset:
|
||||
for var in ge_asset['Variables']:
|
||||
var_name = var.get('Name', '')
|
||||
var_value = var.get('Value', '')
|
||||
var_type = var.get('Type', '')
|
||||
variables.append({
|
||||
'name': var_name,
|
||||
'value': var_value,
|
||||
'type': var_type
|
||||
})
|
||||
|
||||
# EventGraph 섹션에서 로직 확인
|
||||
event_graphs = []
|
||||
if 'EventGraphs' in ge_asset:
|
||||
for graph in ge_asset['EventGraphs']:
|
||||
graph_name = graph.get('Name', '')
|
||||
event_graphs.append(graph_name)
|
||||
|
||||
return {
|
||||
'name': asset_name,
|
||||
'variables': variables,
|
||||
'event_graphs': event_graphs
|
||||
}
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("사용법: python analyze_ge_blueprints.py <Blueprint.json> <ultimate_ge_list.json>")
|
||||
sys.exit(1)
|
||||
|
||||
bp_path = Path(sys.argv[1])
|
||||
ge_list_path = Path(sys.argv[2])
|
||||
|
||||
# GE 목록 로드
|
||||
with open(ge_list_path, 'r', encoding='utf-8') as f:
|
||||
ge_data = json.load(f)
|
||||
|
||||
target_ge_classes = ge_data['all_ge_classes']
|
||||
target_ge_names = [extract_ge_name(ge) for ge in target_ge_classes]
|
||||
|
||||
print(f"Blueprint 로딩 중: {bp_path} (24MB, 시간 소요)")
|
||||
with open(bp_path, 'r', encoding='utf-8') as f:
|
||||
bp_data = json.load(f)
|
||||
|
||||
assets = bp_data.get('Assets', [])
|
||||
print(f"총 {len(assets)}개 Blueprint Assets 로드 완료")
|
||||
|
||||
print("\n" + "=" * 100)
|
||||
print("GameplayEffect Blueprint 상세 분석")
|
||||
print("=" * 100)
|
||||
|
||||
results = {}
|
||||
|
||||
for target_name in target_ge_names:
|
||||
# Blueprint에서 GE 찾기
|
||||
ge_asset = next((a for a in assets if a.get('AssetName', '') == target_name), None)
|
||||
|
||||
if not ge_asset:
|
||||
print(f"\n【{target_name}】")
|
||||
print(f" → Blueprint에서 찾을 수 없음")
|
||||
continue
|
||||
|
||||
analysis = analyze_ge_blueprint(ge_asset)
|
||||
results[target_name] = analysis
|
||||
|
||||
print(f"\n【{target_name}】")
|
||||
|
||||
if analysis['variables']:
|
||||
print(f" Variables: {len(analysis['variables'])}개")
|
||||
for var in analysis['variables']:
|
||||
print(f" - {var['name']} ({var['type']}): {var['value']}")
|
||||
else:
|
||||
print(f" Variables: 없음")
|
||||
|
||||
if analysis['event_graphs']:
|
||||
print(f" EventGraphs: {', '.join(analysis['event_graphs'])}")
|
||||
|
||||
# 결과 저장
|
||||
output_file = ge_list_path.parent / "ge_blueprint_analysis.json"
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(results, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\n\n분석 결과 저장: {output_file}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
65
분석도구/legacy/check_ultimate_effects.py
Normal file
65
분석도구/legacy/check_ultimate_effects.py
Normal file
@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
궁극기 GameplayEffectSet 확인
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
STALKERS = ['hilda', 'urud', 'nave', 'baran', 'rio', 'clad', 'rene', 'sinobu', 'lian', 'cazimord']
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("사용법: python check_ultimate_effects.py <DataTable.json 경로>")
|
||||
sys.exit(1)
|
||||
|
||||
json_path = Path(sys.argv[1])
|
||||
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
assets = data.get('Assets', [])
|
||||
|
||||
# DT_CharacterStat에서 궁극기 ID 추출
|
||||
dt_char_stat = next((dt for dt in assets if dt.get('AssetName') == 'DT_CharacterStat'), None)
|
||||
stalker_ultimates = {}
|
||||
for row in dt_char_stat.get('Rows', []):
|
||||
row_name = row['RowName']
|
||||
if row_name in STALKERS:
|
||||
stalker_ultimates[row_name] = row['Data'].get('ultimateSkill', '')
|
||||
|
||||
# DT_Skill에서 궁극기 정보 확인
|
||||
dt_skill = next((dt for dt in assets if dt.get('AssetName') == 'DT_Skill'), None)
|
||||
|
||||
print("=" * 100)
|
||||
print("궁극기 GameplayEffectSet 확인")
|
||||
print("=" * 100)
|
||||
|
||||
for stalker in STALKERS:
|
||||
ult_id = stalker_ultimates.get(stalker, '')
|
||||
if not ult_id:
|
||||
continue
|
||||
|
||||
skill_row = next((row for row in dt_skill['Rows'] if row['RowName'] == ult_id), None)
|
||||
if not skill_row:
|
||||
continue
|
||||
|
||||
data_field = skill_row['Data']
|
||||
|
||||
print(f"\n【{stalker.upper()}】 {ult_id}")
|
||||
print(f" 이름: {data_field.get('name', 'N/A')}")
|
||||
print(f" 간단 설명: {data_field.get('simpleDesc', 'N/A')}")
|
||||
print(f" skillDamageRate: {data_field.get('skillDamageRate', 0)}")
|
||||
print(f" skillAttackType: {data_field.get('skillAttackType', 'N/A')}")
|
||||
|
||||
effect_set = data_field.get('gameplayEffectSet', [])
|
||||
if effect_set:
|
||||
print(f" gameplayEffectSet: {len(effect_set)}개 효과")
|
||||
for i, effect in enumerate(effect_set, 1):
|
||||
print(f" [{i}] {effect}")
|
||||
else:
|
||||
print(f" gameplayEffectSet: 비어있음 → Blueprint 확인 필요")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
115
분석도구/legacy/extract_activation_order_groups.py
Normal file
115
분석도구/legacy/extract_activation_order_groups.py
Normal file
@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Activation Order Group 추출 스크립트
|
||||
|
||||
Blueprint.json에서 스토커별 스킬의 ActivationOrderGroup 값을 추출합니다.
|
||||
|
||||
사용법:
|
||||
python extract_activation_order_groups.py <Blueprint.json 경로>
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def extract_activation_order_groups(json_path):
|
||||
"""Blueprint.json에서 ActivationOrderGroup 추출"""
|
||||
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
blueprints = json.load(f)
|
||||
|
||||
# 스토커별 스킬 그룹화
|
||||
stalker_skills = defaultdict(list)
|
||||
|
||||
stalkers = ['Hilda', 'Urud', 'Nave', 'Baran', 'Rio', 'Clad', 'Rene', 'Sinobu', 'Lian', 'Cazimord']
|
||||
|
||||
for bp in blueprints:
|
||||
asset_name = bp.get('AssetName', '')
|
||||
|
||||
# GA_Skill_{Stalker}_ 패턴 찾기
|
||||
if asset_name.startswith('GA_Skill_'):
|
||||
for stalker in stalkers:
|
||||
if f'_{stalker}_' in asset_name:
|
||||
# ActivationOrderGroup 찾기
|
||||
activation_order = None
|
||||
for var in bp.get('Variables', []):
|
||||
if var.get('Name') == 'ActivationOrderGroup':
|
||||
activation_order = var.get('DefaultValue', '0')
|
||||
break
|
||||
|
||||
skill_name = asset_name.replace(f'GA_Skill_{stalker}_', '')
|
||||
|
||||
stalker_skills[stalker].append({
|
||||
'skill': skill_name,
|
||||
'order_group': int(activation_order) if activation_order else 0,
|
||||
'full_name': asset_name
|
||||
})
|
||||
|
||||
return stalker_skills
|
||||
|
||||
|
||||
def print_stalker_skills(stalker_skills):
|
||||
"""스토커별 스킬과 ActivationOrderGroup 출력"""
|
||||
|
||||
print("\n스토커별 Activation Order Group")
|
||||
print("=" * 100)
|
||||
|
||||
for stalker, skills in sorted(stalker_skills.items()):
|
||||
print(f"\n{stalker}:")
|
||||
|
||||
# Order Group별로 정렬
|
||||
skills_by_group = defaultdict(list)
|
||||
for skill in skills:
|
||||
skills_by_group[skill['order_group']].append(skill['skill'])
|
||||
|
||||
for group in sorted(skills_by_group.keys(), reverse=True):
|
||||
print(f" Group {group}: {', '.join(sorted(skills_by_group[group]))}")
|
||||
|
||||
|
||||
def print_statistics(stalker_skills):
|
||||
"""통계 정보 출력"""
|
||||
|
||||
print("\n\n통계")
|
||||
print("=" * 100)
|
||||
|
||||
# 각 Group별 사용 빈도
|
||||
group_count = defaultdict(int)
|
||||
for stalker, skills in stalker_skills.items():
|
||||
for skill in skills:
|
||||
group_count[skill['order_group']] += 1
|
||||
|
||||
print("\nGroup별 스킬 수:")
|
||||
for group in sorted(group_count.keys(), reverse=True):
|
||||
print(f" Group {group}: {group_count[group]}개")
|
||||
|
||||
# 스토커별 스킬 수
|
||||
print("\n스토커별 스킬 수:")
|
||||
for stalker, skills in sorted(stalker_skills.items(), key=lambda x: len(x[1]), reverse=True):
|
||||
print(f" {stalker}: {len(skills)}개")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("사용법: python extract_activation_order_groups.py <Blueprint.json 경로>")
|
||||
sys.exit(1)
|
||||
|
||||
json_path = Path(sys.argv[1])
|
||||
|
||||
if not json_path.exists():
|
||||
print(f"오류: 파일을 찾을 수 없습니다: {json_path}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"분석 중: {json_path}")
|
||||
|
||||
stalker_skills = extract_activation_order_groups(json_path)
|
||||
|
||||
print(f"\n총 {sum(len(skills) for skills in stalker_skills.values())}개의 스킬 발견")
|
||||
|
||||
print_stalker_skills(stalker_skills)
|
||||
print_statistics(stalker_skills)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
89
분석도구/legacy/extract_all_ultimates_detailed.py
Normal file
89
분석도구/legacy/extract_all_ultimates_detailed.py
Normal file
@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
모든 궁극기 상세 정보 추출 (desc 포함)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
STALKERS = ['hilda', 'urud', 'nave', 'baran', 'rio', 'clad', 'rene', 'sinobu', 'lian', 'cazimord']
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("사용법: python extract_all_ultimates_detailed.py <DataTable.json 경로>")
|
||||
sys.exit(1)
|
||||
|
||||
json_path = Path(sys.argv[1])
|
||||
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
assets = data.get('Assets', [])
|
||||
|
||||
# DT_CharacterStat에서 궁극기 ID 추출
|
||||
dt_char_stat = next((dt for dt in assets if dt.get('AssetName') == 'DT_CharacterStat'), None)
|
||||
stalker_ultimates = {}
|
||||
for row in dt_char_stat.get('Rows', []):
|
||||
row_name = row['RowName']
|
||||
if row_name in STALKERS:
|
||||
stalker_ultimates[row_name] = row['Data'].get('ultimateSkill', '')
|
||||
|
||||
# DT_Skill에서 궁극기 정보 확인
|
||||
dt_skill = next((dt for dt in assets if dt.get('AssetName') == 'DT_Skill'), None)
|
||||
|
||||
print("=" * 120)
|
||||
print("모든 궁극기 상세 정보")
|
||||
print("=" * 120)
|
||||
|
||||
for stalker in STALKERS:
|
||||
ult_id = stalker_ultimates.get(stalker, '')
|
||||
if not ult_id:
|
||||
continue
|
||||
|
||||
skill_row = next((row for row in dt_skill['Rows'] if row['RowName'] == ult_id), None)
|
||||
if not skill_row:
|
||||
continue
|
||||
|
||||
d = skill_row['Data']
|
||||
|
||||
print(f"\n{'='*120}")
|
||||
print(f"【{stalker.upper()}】 {ult_id}")
|
||||
print(f"{'='*120}")
|
||||
print(f"이름: {d.get('name', 'N/A')}")
|
||||
print(f"설명: {d.get('desc', 'N/A')}")
|
||||
print(f"간단 설명: {d.get('simpleDesc', 'N/A')}")
|
||||
print(f"\n기본 정보:")
|
||||
print(f" - bIsUltimate: {d.get('bIsUltimate', False)}")
|
||||
print(f" - skillDamageRate: {d.get('skillDamageRate', 0)}")
|
||||
print(f" - skillAttackType: {d.get('skillAttackType', 'N/A')}")
|
||||
print(f" - skillElementType: {d.get('skillElementType', 'N/A')}")
|
||||
print(f" - castingTime: {d.get('castingTime', 0)}초")
|
||||
print(f" - activeDuration: {d.get('activeDuration', 0)}초")
|
||||
print(f" - manaCost: {d.get('manaCost', 0)}")
|
||||
print(f" - coolTime: {d.get('coolTime', 0)}")
|
||||
|
||||
effect_set = d.get('gameplayEffectSet', [])
|
||||
if effect_set:
|
||||
print(f"\ngameplayEffectSet: {len(effect_set)}개 효과")
|
||||
for i, effect in enumerate(effect_set, 1):
|
||||
ge_class = effect.get('gEClass', '')
|
||||
trigger = effect.get('trigger', '')
|
||||
tag_values = effect.get('gETagValues', [])
|
||||
|
||||
# Ignore 효과는 간단히 표시
|
||||
if 'Ignore' in ge_class:
|
||||
print(f" [{i}] {trigger}: {ge_class.split('/')[-1].split('.')[0]} (면역 효과)")
|
||||
else:
|
||||
ge_name = ge_class.split('/')[-1].split('.')[0]
|
||||
print(f" [{i}] {trigger}: {ge_name}")
|
||||
if tag_values:
|
||||
for tv in tag_values:
|
||||
tag_name = tv.get('tag', {}).get('tagName', 'Unknown')
|
||||
value = tv.get('value', 0)
|
||||
print(f" → {tag_name}: {value}")
|
||||
else:
|
||||
print(f"\ngameplayEffectSet: 비어있음")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
106
분석도구/legacy/extract_ge_from_ultimates.py
Normal file
106
분석도구/legacy/extract_ge_from_ultimates.py
Normal file
@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
궁극기에서 사용하는 GameplayEffect 추출
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
STALKERS = ['hilda', 'urud', 'nave', 'baran', 'rio', 'clad', 'rene', 'sinobu', 'lian', 'cazimord']
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("사용법: python extract_ge_from_ultimates.py <DataTable.json 경로>")
|
||||
sys.exit(1)
|
||||
|
||||
json_path = Path(sys.argv[1])
|
||||
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
assets = data.get('Assets', [])
|
||||
|
||||
# DT_CharacterStat에서 궁극기 ID 추출
|
||||
dt_char_stat = next((dt for dt in assets if dt.get('AssetName') == 'DT_CharacterStat'), None)
|
||||
stalker_ultimates = {}
|
||||
for row in dt_char_stat.get('Rows', []):
|
||||
row_name = row['RowName']
|
||||
if row_name in STALKERS:
|
||||
stalker_ultimates[row_name] = row['Data'].get('ultimateSkill', '')
|
||||
|
||||
# DT_Skill에서 궁극기 gameplayEffectSet 추출
|
||||
dt_skill = next((dt for dt in assets if dt.get('AssetName') == 'DT_Skill'), None)
|
||||
|
||||
all_ge_classes = set()
|
||||
stalker_ges = {}
|
||||
|
||||
print("=" * 100)
|
||||
print("궁극기에서 사용하는 GameplayEffect 목록")
|
||||
print("=" * 100)
|
||||
|
||||
for stalker in STALKERS:
|
||||
ult_id = stalker_ultimates.get(stalker, '')
|
||||
if not ult_id:
|
||||
continue
|
||||
|
||||
skill_row = next((row for row in dt_skill['Rows'] if row['RowName'] == ult_id), None)
|
||||
if not skill_row:
|
||||
continue
|
||||
|
||||
data_field = skill_row['Data']
|
||||
effect_set = data_field.get('gameplayEffectSet', [])
|
||||
|
||||
stalker_ges[stalker] = []
|
||||
|
||||
print(f"\n【{stalker.upper()}】 {ult_id} - {data_field.get('name', 'N/A')}")
|
||||
|
||||
if not effect_set:
|
||||
print(f" → gameplayEffectSet 비어있음")
|
||||
continue
|
||||
|
||||
for effect in effect_set:
|
||||
ge_class = effect.get('gEClass', '')
|
||||
trigger = effect.get('trigger', '')
|
||||
tag_values = effect.get('gETagValues', [])
|
||||
|
||||
# Ignore 효과는 스킵
|
||||
if 'Ignore' in ge_class:
|
||||
continue
|
||||
|
||||
# GE 클래스 이름 추출
|
||||
if ge_class:
|
||||
ge_name = ge_class.split('/')[-1].replace('.', '_').replace('_C', '')
|
||||
all_ge_classes.add(ge_class)
|
||||
stalker_ges[stalker].append({
|
||||
'name': ge_name,
|
||||
'class': ge_class,
|
||||
'trigger': trigger,
|
||||
'tagValues': tag_values
|
||||
})
|
||||
|
||||
print(f" [{trigger}] {ge_name}")
|
||||
if tag_values:
|
||||
for tv in tag_values:
|
||||
tag_name = tv.get('tag', {}).get('tagName', 'Unknown')
|
||||
value = tv.get('value', 0)
|
||||
print(f" - {tag_name}: {value}")
|
||||
|
||||
print(f"\n\n총 {len(all_ge_classes)}개의 고유한 GE 클래스 발견")
|
||||
print("\nGE 클래스 목록:")
|
||||
for ge in sorted(all_ge_classes):
|
||||
ge_name = ge.split('/')[-1].replace('.', '_').replace('_C', '')
|
||||
print(f" - {ge_name}")
|
||||
|
||||
# JSON 파일로 저장
|
||||
output_file = Path(sys.argv[1]).parent / "ultimate_ge_list.json"
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump({
|
||||
'stalker_ges': stalker_ges,
|
||||
'all_ge_classes': list(all_ge_classes)
|
||||
}, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\n\n결과 저장: {output_file}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
69
분석도구/legacy/extract_skill_cancel_windows.py
Normal file
69
분석도구/legacy/extract_skill_cancel_windows.py
Normal file
@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
스킬 캔슬 윈도우 추출 스크립트
|
||||
|
||||
AnimMontage.json에서 ANS_SkillCancel_C 노티파이를 가진 몽타주를 찾아
|
||||
캔슬 가능 시간 구간을 추출합니다.
|
||||
|
||||
사용법:
|
||||
python extract_skill_cancel_windows.py <AnimMontage.json 경로>
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def extract_cancel_windows(json_path):
|
||||
"""AnimMontage.json에서 스킬 캔슬 윈도우 추출"""
|
||||
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
montages = json.load(f)
|
||||
|
||||
cancel_montages = []
|
||||
|
||||
for montage in montages:
|
||||
asset_name = montage.get('AssetName', '')
|
||||
|
||||
# ANS_SkillCancel_C 노티파이 찾기
|
||||
for notify in montage.get('AnimNotifies', []):
|
||||
if notify.get('NotifyStateClass') == 'ANS_SkillCancel_C':
|
||||
trigger_time = notify['TriggerTime']
|
||||
duration = notify['Duration']
|
||||
|
||||
cancel_montages.append({
|
||||
'montage': asset_name,
|
||||
'start': trigger_time,
|
||||
'end': trigger_time + duration,
|
||||
'duration': duration
|
||||
})
|
||||
|
||||
return cancel_montages
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("사용법: python extract_skill_cancel_windows.py <AnimMontage.json 경로>")
|
||||
sys.exit(1)
|
||||
|
||||
json_path = Path(sys.argv[1])
|
||||
|
||||
if not json_path.exists():
|
||||
print(f"오류: 파일을 찾을 수 없습니다: {json_path}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"분석 중: {json_path}")
|
||||
print("-" * 80)
|
||||
|
||||
cancel_windows = extract_cancel_windows(json_path)
|
||||
|
||||
print(f"\n총 {len(cancel_windows)}개의 스킬 캔슬 윈도우 발견\n")
|
||||
|
||||
for item in cancel_windows:
|
||||
print(f"{item['montage']}")
|
||||
print(f" 캔슬 구간: {item['start']:.3f}s ~ {item['end']:.3f}s (지속: {item['duration']:.3f}s)")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
199
분석도구/legacy/extract_stalker_data.py
Normal file
199
분석도구/legacy/extract_stalker_data.py
Normal file
@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
스토커 기본 데이터 추출 스크립트
|
||||
|
||||
DT_CharacterStat, DT_CharacterAbility, DT_Skill에서
|
||||
10명 스토커의 모든 정보를 추출합니다.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
STALKERS = ['hilda', 'urud', 'nave', 'baran', 'rio', 'clad', 'rene', 'sinobu', 'lian', 'cazimord']
|
||||
|
||||
def load_json(file_path):
|
||||
"""JSON 파일 로드"""
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
# Assets 배열 반환
|
||||
return data.get('Assets', [])
|
||||
|
||||
def find_table(datatables, table_name):
|
||||
"""특정 테이블 찾기"""
|
||||
for dt in datatables:
|
||||
if dt.get('AssetName') == table_name:
|
||||
return dt
|
||||
return None
|
||||
|
||||
def extract_character_stats(datatables):
|
||||
"""DT_CharacterStat에서 스토커 기본 정보 추출"""
|
||||
char_stat_table = find_table(datatables, 'DT_CharacterStat')
|
||||
if not char_stat_table:
|
||||
return {}
|
||||
|
||||
stalker_data = {}
|
||||
for row in char_stat_table.get('Rows', []):
|
||||
row_name = row['RowName']
|
||||
if row_name in STALKERS:
|
||||
data = row['Data']
|
||||
stalker_data[row_name] = {
|
||||
'name': data.get('name', ''),
|
||||
'jobName': data.get('jobName', ''),
|
||||
'str': data.get('str', 0),
|
||||
'dex': data.get('dex', 0),
|
||||
'int': data.get('int', 0),
|
||||
'con': data.get('con', 0),
|
||||
'wis': data.get('wis', 0),
|
||||
'hP': data.get('hP', 0),
|
||||
'mP': data.get('mP', 0),
|
||||
'manaRegen': data.get('manaRegen', 0),
|
||||
'physicalDamage': data.get('physicalDamage', 0),
|
||||
'magicalDamage': data.get('magicalDamage', 0),
|
||||
'defaultSkills': data.get('defaultSkills', []),
|
||||
'subSkill': data.get('subSkill', ''),
|
||||
'ultimateSkill': data.get('ultimateSkill', ''),
|
||||
'equipableTypes': data.get('equipableTypes', []),
|
||||
'ultimatePoint': data.get('ultimatePoint', 0)
|
||||
}
|
||||
return stalker_data
|
||||
|
||||
def extract_character_abilities(datatables):
|
||||
"""DT_CharacterAbility에서 평타 몽타주 추출"""
|
||||
char_ability_table = find_table(datatables, 'DT_CharacterAbility')
|
||||
if not char_ability_table:
|
||||
return {}
|
||||
|
||||
stalker_abilities = {}
|
||||
for row in char_ability_table.get('Rows', []):
|
||||
row_name = row['RowName']
|
||||
if row_name in STALKERS:
|
||||
data = row['Data']
|
||||
attack_montage_map = data.get('attackMontageMap', {})
|
||||
stalker_abilities[row_name] = {
|
||||
'attackMontageMap': attack_montage_map,
|
||||
'abilities': data.get('abilities', [])
|
||||
}
|
||||
return stalker_abilities
|
||||
|
||||
def extract_skills(datatables, stalker_stats):
|
||||
"""DT_Skill에서 스토커별 스킬 정보 추출"""
|
||||
skill_table = find_table(datatables, 'DT_Skill')
|
||||
if not skill_table:
|
||||
return {}
|
||||
|
||||
# 모든 스킬을 ID로 매핑
|
||||
all_skills = {}
|
||||
for row in skill_table.get('Rows', []):
|
||||
skill_id = row['RowName']
|
||||
data = row['Data']
|
||||
all_skills[skill_id] = {
|
||||
'skillId': skill_id,
|
||||
'stalkerName': data.get('stalkerName', ''),
|
||||
'name': data.get('name', ''),
|
||||
'bIsUltimate': data.get('bIsUltimate', False),
|
||||
'bIsStackable': data.get('bIsStackable', False),
|
||||
'maxStackCount': data.get('maxStackCount', 0),
|
||||
'skillDamageRate': data.get('skillDamageRate', 0),
|
||||
'skillAttackType': data.get('skillAttackType', ''),
|
||||
'skillElementType': data.get('skillElementType', ''),
|
||||
'manaCost': data.get('manaCost', 0),
|
||||
'coolTime': data.get('coolTime', 0),
|
||||
'useMontages': data.get('useMontages', []),
|
||||
'abilityClass': data.get('abilityClass', '')
|
||||
}
|
||||
|
||||
# 스토커별로 스킬 그룹화
|
||||
stalker_skills = {}
|
||||
for stalker_id, stats in stalker_stats.items():
|
||||
skills = {
|
||||
'defaultSkills': [],
|
||||
'subSkill': None,
|
||||
'ultimateSkill': None
|
||||
}
|
||||
|
||||
# 기본 스킬
|
||||
for skill_id in stats['defaultSkills']:
|
||||
if skill_id in all_skills:
|
||||
skills['defaultSkills'].append(all_skills[skill_id])
|
||||
|
||||
# 서브 스킬
|
||||
if stats['subSkill'] in all_skills:
|
||||
skills['subSkill'] = all_skills[stats['subSkill']]
|
||||
|
||||
# 궁극기
|
||||
if stats['ultimateSkill'] in all_skills:
|
||||
skills['ultimateSkill'] = all_skills[stats['ultimateSkill']]
|
||||
|
||||
stalker_skills[stalker_id] = skills
|
||||
|
||||
return stalker_skills
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("사용법: python extract_stalker_data.py <DataTable.json 경로>")
|
||||
sys.exit(1)
|
||||
|
||||
json_path = Path(sys.argv[1])
|
||||
if not json_path.exists():
|
||||
print(f"오류: 파일을 찾을 수 없습니다: {json_path}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"분석 중: {json_path}")
|
||||
datatables = load_json(json_path)
|
||||
|
||||
print("\n=== 스토커 기본 스탯 추출 ===")
|
||||
stalker_stats = extract_character_stats(datatables)
|
||||
print(f"추출 완료: {len(stalker_stats)}명")
|
||||
|
||||
print("\n=== 스토커 평타 몽타주 추출 ===")
|
||||
stalker_abilities = extract_character_abilities(datatables)
|
||||
print(f"추출 완료: {len(stalker_abilities)}명")
|
||||
|
||||
print("\n=== 스토커 스킬 정보 추출 ===")
|
||||
stalker_skills = extract_skills(datatables, stalker_stats)
|
||||
print(f"추출 완료: {len(stalker_skills)}명")
|
||||
|
||||
# 결과 출력
|
||||
for stalker_id in STALKERS:
|
||||
if stalker_id not in stalker_stats:
|
||||
continue
|
||||
|
||||
stats = stalker_stats[stalker_id]
|
||||
print(f"\n{'='*80}")
|
||||
print(f"【{stats['name']}】 ({stats['jobName']})")
|
||||
print(f"{'='*80}")
|
||||
print(f"STR: {stats['str']:2d} | DEX: {stats['dex']:2d} | INT: {stats['int']:2d} | CON: {stats['con']:2d} | WIS: {stats['wis']:2d}")
|
||||
print(f"HP: {stats['hP']} | MP: {stats['mP']} | Mana Regen: {stats['manaRegen']}")
|
||||
print(f"장착 가능: {', '.join(stats['equipableTypes'])}")
|
||||
print(f"궁극기 포인트: {stats['ultimatePoint']}")
|
||||
|
||||
# 스킬 정보
|
||||
if stalker_id in stalker_skills:
|
||||
skills = stalker_skills[stalker_id]
|
||||
print(f"\n[기본 스킬]")
|
||||
for skill in skills['defaultSkills']:
|
||||
print(f" - {skill['name']} ({skill['skillId']}): {skill['skillAttackType']} | 쿨타임: {skill['coolTime']}초 | 마나: {skill['manaCost']}")
|
||||
|
||||
if skills['subSkill']:
|
||||
skill = skills['subSkill']
|
||||
print(f"\n[서브 스킬]")
|
||||
print(f" - {skill['name']} ({skill['skillId']}): {skill['skillAttackType']} | 쿨타임: {skill['coolTime']}초")
|
||||
|
||||
if skills['ultimateSkill']:
|
||||
skill = skills['ultimateSkill']
|
||||
print(f"\n[궁극기]")
|
||||
print(f" - {skill['name']} ({skill['skillId']}): {skill['skillAttackType']}")
|
||||
|
||||
# 평타 몽타주
|
||||
if stalker_id in stalker_abilities:
|
||||
abilities = stalker_abilities[stalker_id]
|
||||
attack_map = abilities.get('attackMontageMap', {})
|
||||
if attack_map:
|
||||
print(f"\n[평타 몽타주]")
|
||||
for weapon_type, montage_data in attack_map.items():
|
||||
montage_array = montage_data.get('montageArray', [])
|
||||
print(f" - {weapon_type}: {len(montage_array)}타 연속")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
77
분석도구/legacy/extract_ultimate_skills.py
Normal file
77
분석도구/legacy/extract_ultimate_skills.py
Normal file
@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
궁극기 정보 추출 스크립트
|
||||
|
||||
모든 스토커의 궁극기 정보를 DT_Skill에서 추출합니다.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
STALKERS = ['hilda', 'urud', 'nave', 'baran', 'rio', 'clad', 'rene', 'sinobu', 'lian', 'cazimord']
|
||||
|
||||
def load_json(file_path):
|
||||
"""JSON 파일 로드"""
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return data.get('Assets', [])
|
||||
|
||||
def find_table(datatables, table_name):
|
||||
"""특정 테이블 찾기"""
|
||||
for dt in datatables:
|
||||
if dt.get('AssetName') == table_name:
|
||||
return dt
|
||||
return None
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("사용법: python extract_ultimate_skills.py <DataTable.json 경로>")
|
||||
sys.exit(1)
|
||||
|
||||
json_path = Path(sys.argv[1])
|
||||
datatables = load_json(json_path)
|
||||
|
||||
# DT_CharacterStat에서 궁극기 ID 추출
|
||||
char_stat_table = find_table(datatables, 'DT_CharacterStat')
|
||||
stalker_ultimates = {}
|
||||
for row in char_stat_table.get('Rows', []):
|
||||
row_name = row['RowName']
|
||||
if row_name in STALKERS:
|
||||
stalker_ultimates[row_name] = row['Data'].get('ultimateSkill', '')
|
||||
|
||||
# DT_Skill에서 궁극기 상세 정보 추출
|
||||
skill_table = find_table(datatables, 'DT_Skill')
|
||||
|
||||
print("=" * 100)
|
||||
print("스토커별 궁극기 정보")
|
||||
print("=" * 100)
|
||||
|
||||
for stalker in STALKERS:
|
||||
ult_id = stalker_ultimates.get(stalker, '')
|
||||
if not ult_id:
|
||||
continue
|
||||
|
||||
# 스킬 정보 찾기
|
||||
skill_row = next((row for row in skill_table['Rows'] if row['RowName'] == ult_id), None)
|
||||
if not skill_row:
|
||||
print(f"\n{stalker}: 궁극기 {ult_id} 정보를 찾을 수 없습니다")
|
||||
continue
|
||||
|
||||
data = skill_row['Data']
|
||||
|
||||
print(f"\n【{stalker.upper()}】")
|
||||
print(f" ID: {ult_id}")
|
||||
print(f" 이름: {data.get('name', 'N/A')}")
|
||||
print(f" 간단 설명: {data.get('simpleDesc', 'N/A')}")
|
||||
print(f" bIsUltimate: {data.get('bIsUltimate', False)}")
|
||||
print(f" skillDamageRate: {data.get('skillDamageRate', 0)}")
|
||||
print(f" skillAttackType: {data.get('skillAttackType', 'N/A')}")
|
||||
print(f" skillElementType: {data.get('skillElementType', 'N/A')}")
|
||||
print(f" castingTime: {data.get('castingTime', 0)}초")
|
||||
print(f" activeDuration: {data.get('activeDuration', 0)}초")
|
||||
print(f" manaCost: {data.get('manaCost', 0)}")
|
||||
print(f" coolTime: {data.get('coolTime', 0)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
42
분석도구/legacy/find_ga_skills.py
Normal file
42
분석도구/legacy/find_ga_skills.py
Normal file
@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
GA_Skill Blueprint 찾기
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("사용법: python find_ga_skills.py <Blueprint.json 경로>")
|
||||
sys.exit(1)
|
||||
|
||||
json_path = Path(sys.argv[1])
|
||||
|
||||
print(f"로딩 중: {json_path}")
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
assets = data.get('Assets', [])
|
||||
print(f"총 Blueprint Assets: {len(assets)}")
|
||||
|
||||
# GA_Skill 찾기 (대소문자 무시)
|
||||
ga_skills = [a for a in assets if 'ga_skill' in a.get('AssetName', '').lower()]
|
||||
print(f"GA_Skill Assets: {len(ga_skills)}")
|
||||
|
||||
# 스토커별 분류
|
||||
stalkers = ['Hilda', 'Urud', 'Nave', 'Baran', 'Rio', 'Clad', 'Rene', 'Sinobu', 'Lian', 'Cazimord']
|
||||
|
||||
for stalker in stalkers:
|
||||
stalker_gas = [a for a in ga_skills if stalker.lower() in a.get('AssetName', '').lower()]
|
||||
if stalker_gas:
|
||||
print(f"\n{stalker} GA_Skills: {len(stalker_gas)}개")
|
||||
for ga in stalker_gas[:10]: # 최대 10개만 표시
|
||||
asset_name = ga.get('AssetName', 'UNKNOWN')
|
||||
print(f" - {asset_name}")
|
||||
if len(stalker_gas) > 10:
|
||||
print(f" ... 외 {len(stalker_gas) - 10}개 더")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
86
분석도구/legacy/verify_skills_detailed.py
Normal file
86
분석도구/legacy/verify_skills_detailed.py
Normal file
@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
특정 스토커들의 스킬 정보를 DT_Skill에서 정확히 추출
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 문제가 있는 스토커들
|
||||
STALKERS = ['nave', 'baran', 'rio', 'clad', 'rene', 'sinobu', 'lian']
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("사용법: python verify_skills_detailed.py <DataTable.json 경로>")
|
||||
sys.exit(1)
|
||||
|
||||
json_path = Path(sys.argv[1])
|
||||
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
assets = data.get('Assets', [])
|
||||
|
||||
# DT_CharacterStat에서 스킬 ID 추출
|
||||
dt_char_stat = next((dt for dt in assets if dt.get('AssetName') == 'DT_CharacterStat'), None)
|
||||
stalker_skills = {}
|
||||
for row in dt_char_stat.get('Rows', []):
|
||||
row_name = row['RowName']
|
||||
if row_name in STALKERS:
|
||||
data_field = row['Data']
|
||||
stalker_skills[row_name] = {
|
||||
'defaultSkills': data_field.get('defaultSkills', []),
|
||||
'subSkill': data_field.get('subSkill', ''),
|
||||
'ultimateSkill': data_field.get('ultimateSkill', '')
|
||||
}
|
||||
|
||||
# DT_Skill에서 스킬 상세 정보 추출
|
||||
dt_skill = next((dt for dt in assets if dt.get('AssetName') == 'DT_Skill'), None)
|
||||
|
||||
for stalker in STALKERS:
|
||||
skill_ids = stalker_skills.get(stalker, {})
|
||||
|
||||
print(f"\n{'='*120}")
|
||||
print(f"【{stalker.upper()}】")
|
||||
print(f"{'='*120}")
|
||||
|
||||
# 기본 스킬들
|
||||
print(f"\n[기본 스킬]")
|
||||
for skill_id in skill_ids.get('defaultSkills', []):
|
||||
skill_row = next((row for row in dt_skill['Rows'] if row['RowName'] == skill_id), None)
|
||||
if skill_row:
|
||||
d = skill_row['Data']
|
||||
print(f"\n {skill_id} - {d.get('name', 'N/A')}")
|
||||
print(f" 설명: {d.get('simpleDesc', 'N/A')}")
|
||||
print(f" 타입: {d.get('skillAttackType', 'N/A')}")
|
||||
print(f" 속성: {d.get('skillElementType', 'N/A')}")
|
||||
print(f" 피해배율: {d.get('skillDamageRate', 0)}")
|
||||
print(f" 쿨타임: {d.get('coolTime', 0)}초")
|
||||
print(f" 마나: {d.get('manaCost', 0)}")
|
||||
|
||||
# 서브 스킬
|
||||
sub_skill_id = skill_ids.get('subSkill', '')
|
||||
if sub_skill_id:
|
||||
skill_row = next((row for row in dt_skill['Rows'] if row['RowName'] == sub_skill_id), None)
|
||||
if skill_row:
|
||||
d = skill_row['Data']
|
||||
print(f"\n[서브 스킬]")
|
||||
print(f"\n {sub_skill_id} - {d.get('name', 'N/A')}")
|
||||
print(f" 설명: {d.get('simpleDesc', 'N/A')}")
|
||||
print(f" 타입: {d.get('skillAttackType', 'N/A')}")
|
||||
print(f" 쿨타임: {d.get('coolTime', 0)}초")
|
||||
|
||||
# 궁극기
|
||||
ult_skill_id = skill_ids.get('ultimateSkill', '')
|
||||
if ult_skill_id:
|
||||
skill_row = next((row for row in dt_skill['Rows'] if row['RowName'] == ult_skill_id), None)
|
||||
if skill_row:
|
||||
d = skill_row['Data']
|
||||
print(f"\n[궁극기]")
|
||||
print(f"\n {ult_skill_id} - {d.get('name', 'N/A')}")
|
||||
print(f" 설명: {d.get('simpleDesc', 'N/A')}")
|
||||
print(f" 타입: {d.get('skillAttackType', 'N/A')}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user