v2 폐기하고 v3로 새출발
This commit is contained in:
565
legacy/분석도구/v2/calculate_dps_scenarios_v2.py
Normal file
565
legacy/분석도구/v2/calculate_dps_scenarios_v2.py
Normal file
@ -0,0 +1,565 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
스토커 DPS 시나리오 계산 v2
|
||||
- 3개 시나리오 계산: 평타, 로테이션 (30초), 버스트 (10초)
|
||||
- 특수 상황 분석: DoT, 소환체, 패링
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Tuple, Any
|
||||
from config import (
|
||||
get_output_dir, STALKERS, STALKER_INFO,
|
||||
BASE_DAMAGE_FORMULA, DOT_SKILLS, DOT_DAMAGE,
|
||||
SUMMON_SKILLS, UTILITY_SKILLS, ANALYSIS_BASELINE
|
||||
)
|
||||
|
||||
|
||||
def load_validated_data(output_dir: Path) -> Dict:
|
||||
"""validated_data.json 또는 intermediate_data.json 로드"""
|
||||
validated_file = output_dir / "validated_data.json"
|
||||
intermediate_file = output_dir / "intermediate_data.json"
|
||||
|
||||
if validated_file.exists():
|
||||
data_file = validated_file
|
||||
print(f"Using validated_data.json")
|
||||
elif intermediate_file.exists():
|
||||
data_file = intermediate_file
|
||||
print(f"Using intermediate_data.json")
|
||||
else:
|
||||
raise FileNotFoundError(f"No data file found in {output_dir}")
|
||||
|
||||
with open(data_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def calculate_base_damage(stalker_id: str, stats: Dict) -> float:
|
||||
"""BaseDamage 계산 (Level 20, GearScore 400 기준)"""
|
||||
role = STALKER_INFO[stalker_id]['role']
|
||||
|
||||
# 주 스탯 결정 (실제 높은 스탯 또는 역할 기준)
|
||||
if stalker_id == 'hilda':
|
||||
# Hilda: STR 20 → Physical STR
|
||||
damage_type = 'physical_str'
|
||||
elif stalker_id == 'urud':
|
||||
# Urud: DEX 20 → Physical DEX
|
||||
damage_type = 'physical_dex'
|
||||
elif stalker_id == 'nave':
|
||||
# Nave: INT 25 → Magical
|
||||
damage_type = 'magical'
|
||||
elif stalker_id == 'baran':
|
||||
# Baran: STR 25 → Physical STR
|
||||
damage_type = 'physical_str'
|
||||
elif stalker_id == 'rio':
|
||||
# Rio: DEX 25 → Physical DEX
|
||||
damage_type = 'physical_dex'
|
||||
elif stalker_id == 'clad':
|
||||
# Clad: STR 15 (not WIS!) → Support
|
||||
damage_type = 'support'
|
||||
elif stalker_id == 'rene':
|
||||
# Rene: INT 20 → Magical
|
||||
damage_type = 'magical'
|
||||
elif stalker_id == 'sinobu':
|
||||
# Sinobu: DEX 25 → Physical DEX
|
||||
damage_type = 'physical_dex'
|
||||
elif stalker_id == 'lian':
|
||||
# Lian: DEX 20 → Physical DEX
|
||||
damage_type = 'physical_dex'
|
||||
elif stalker_id == 'cazimord':
|
||||
# Cazimord: DEX 25, STR 15 → Physical DEX
|
||||
damage_type = 'physical_dex'
|
||||
else:
|
||||
# Default fallback
|
||||
damage_type = 'physical_str'
|
||||
|
||||
return BASE_DAMAGE_FORMULA[damage_type](stats)
|
||||
|
||||
|
||||
def calculate_basic_attack_dps(stalker_id: str, stalker_data: Dict, base_damage: float) -> Dict:
|
||||
"""시나리오 1: 평타 DPS 계산"""
|
||||
basic_attacks = stalker_data.get('basicAttacks', {})
|
||||
|
||||
# 첫 번째 무기 타입의 평타 사용 (대부분 한 가지 무기만 사용)
|
||||
weapon_type = list(basic_attacks.keys())[0] if basic_attacks else None
|
||||
if not weapon_type:
|
||||
return {
|
||||
'dps': 0,
|
||||
'combo_time': 0,
|
||||
'total_multiplier': 0,
|
||||
'attacks': [],
|
||||
'notes': '평타 데이터 없음'
|
||||
}
|
||||
|
||||
attacks = basic_attacks[weapon_type]
|
||||
|
||||
# 총 콤보 시간 및 평타 배율 합계 계산
|
||||
combo_time = sum(atk['actualDuration'] for atk in attacks)
|
||||
|
||||
# attackMultiplier는 AddNormalAttackPer 값 (음수는 감소, 양수는 증가)
|
||||
# 실제 배율 = 1.0 + (attackMultiplier / 100)
|
||||
total_multiplier = sum(1.0 + (atk['attackMultiplier'] / 100.0) for atk in attacks)
|
||||
|
||||
# 평타 DPS 계산
|
||||
if combo_time > 0:
|
||||
basic_dps = (base_damage * total_multiplier) / combo_time
|
||||
else:
|
||||
basic_dps = 0
|
||||
|
||||
return {
|
||||
'dps': round(basic_dps, 2),
|
||||
'combo_time': round(combo_time, 2),
|
||||
'total_multiplier': round(total_multiplier, 2),
|
||||
'base_damage': round(base_damage, 2),
|
||||
'attacks': [
|
||||
{
|
||||
'index': atk['index'],
|
||||
'name': atk['montageName'],
|
||||
'duration': round(atk['actualDuration'], 2),
|
||||
'multiplier': round(1.0 + (atk['attackMultiplier'] / 100.0), 2)
|
||||
}
|
||||
for atk in attacks
|
||||
],
|
||||
'notes': ''
|
||||
}
|
||||
|
||||
|
||||
def calculate_skill_rotation_dps(stalker_id: str, stalker_data: Dict, base_damage: float, duration: float = 30.0) -> Dict:
|
||||
"""시나리오 2: 스킬 로테이션 DPS (30초 기본)"""
|
||||
skills = stalker_data.get('skills', {})
|
||||
stats = stalker_data['stats']
|
||||
|
||||
# 마나 회복 (0.2/초 + 룬 +70% = 0.34/초)
|
||||
mana_regen_rate = stats.get('manaRegen', 0.2) * (1.0 + ANALYSIS_BASELINE['rune_effect']['cooltime_reduction'])
|
||||
|
||||
# 쿨타임 감소 (왜곡 룬 -25%)
|
||||
cooltime_reduction = ANALYSIS_BASELINE['rune_effect']['cooltime_reduction']
|
||||
|
||||
# 공격 스킬만 필터링 (유틸리티 제외, 궁극기 제외)
|
||||
attack_skills = []
|
||||
for skill_id, skill in skills.items():
|
||||
if skill_id in UTILITY_SKILLS:
|
||||
continue
|
||||
if skill.get('bIsUltimate', False):
|
||||
continue
|
||||
if not skill.get('montageData'):
|
||||
continue
|
||||
|
||||
# 시퀀스 길이 계산 (Ready, Equipment 제외)
|
||||
sequence_length = sum(
|
||||
m['actualDuration']
|
||||
for m in skill['montageData']
|
||||
if 'Ready' not in m['assetName'] and 'Equipment' not in m['assetName']
|
||||
)
|
||||
|
||||
if sequence_length > 0:
|
||||
attack_skills.append({
|
||||
'id': skill_id,
|
||||
'name': skill['name'],
|
||||
'damage_rate': skill['skillDamageRate'],
|
||||
'cooltime': skill['coolTime'] * (1.0 - cooltime_reduction),
|
||||
'casting_time': skill.get('castingTime', 0),
|
||||
'sequence_length': sequence_length,
|
||||
'mana_cost': skill['manaCost'],
|
||||
'skill_type': skill.get('skillAttackType', 'Physical')
|
||||
})
|
||||
|
||||
# 쿨타임 짧은 순서로 정렬
|
||||
attack_skills.sort(key=lambda x: x['cooltime'])
|
||||
|
||||
# 로테이션 시뮬레이션
|
||||
current_time = 0.0
|
||||
current_mana = stats.get('mp', 50)
|
||||
skill_usage = {s['id']: {'count': 0, 'damage': 0, 'next_available': 0} for s in attack_skills}
|
||||
basic_attack_time = 0.0
|
||||
|
||||
# 평타 DPS (필러로 사용)
|
||||
basic_result = calculate_basic_attack_dps(stalker_id, stalker_data, base_damage)
|
||||
basic_dps = basic_result['dps']
|
||||
|
||||
# 30초 동안 스킬 사용
|
||||
time_step = 0.1 # 0.1초 단위로 시뮬레이션
|
||||
|
||||
while current_time < duration:
|
||||
# 현재 사용 가능한 스킬 찾기
|
||||
skill_used = False
|
||||
|
||||
for skill in attack_skills:
|
||||
# 쿨타임 확인
|
||||
if skill_usage[skill['id']]['next_available'] > current_time:
|
||||
continue
|
||||
|
||||
# 마나 확인
|
||||
if current_mana < skill['mana_cost']:
|
||||
continue
|
||||
|
||||
# 스킬 사용
|
||||
skill_time = skill['casting_time'] + skill['sequence_length']
|
||||
if current_time + skill_time > duration:
|
||||
break # 시간 초과
|
||||
|
||||
# 피해 계산
|
||||
if 'Magical' in skill['skill_type']:
|
||||
skill_damage = base_damage * skill['damage_rate']
|
||||
else:
|
||||
skill_damage = base_damage * skill['damage_rate']
|
||||
|
||||
skill_usage[skill['id']]['count'] += 1
|
||||
skill_usage[skill['id']]['damage'] += skill_damage
|
||||
skill_usage[skill['id']]['next_available'] = current_time + skill_time + skill['cooltime']
|
||||
|
||||
current_time += skill_time
|
||||
current_mana -= skill['mana_cost']
|
||||
skill_used = True
|
||||
break
|
||||
|
||||
if not skill_used:
|
||||
# 스킬 사용 불가 시 평타 사용
|
||||
basic_attack_time += time_step
|
||||
current_time += time_step
|
||||
|
||||
# 마나 회복
|
||||
current_mana = min(current_mana + mana_regen_rate * time_step, stats.get('mp', 50))
|
||||
|
||||
# 총 피해 계산
|
||||
total_skill_damage = sum(usage['damage'] for usage in skill_usage.values())
|
||||
basic_damage = basic_dps * basic_attack_time
|
||||
total_damage = total_skill_damage + basic_damage
|
||||
|
||||
rotation_dps = total_damage / duration
|
||||
|
||||
return {
|
||||
'dps': round(rotation_dps, 2),
|
||||
'duration': duration,
|
||||
'base_damage': round(base_damage, 2),
|
||||
'skill_damage': round(total_skill_damage, 2),
|
||||
'basic_damage': round(basic_damage, 2),
|
||||
'basic_attack_time': round(basic_attack_time, 2),
|
||||
'skill_usage': {
|
||||
skill_id: {
|
||||
'name': next((s['name'] for s in attack_skills if s['id'] == skill_id), ''),
|
||||
'count': usage['count'],
|
||||
'damage': round(usage['damage'], 2)
|
||||
}
|
||||
for skill_id, usage in skill_usage.items() if usage['count'] > 0
|
||||
},
|
||||
'notes': ''
|
||||
}
|
||||
|
||||
|
||||
def calculate_burst_dps(stalker_id: str, stalker_data: Dict, base_damage: float, duration: float = 10.0) -> Dict:
|
||||
"""시나리오 3: 버스트 DPS (10초)"""
|
||||
skills = stalker_data.get('skills', {})
|
||||
stats = stalker_data['stats']
|
||||
|
||||
# 궁극기 찾기 (유틸리티 제외)
|
||||
ultimate_skill = None
|
||||
ultimate_id = None
|
||||
for skill_id, skill in skills.items():
|
||||
if skill.get('bIsUltimate', False) and skill_id not in UTILITY_SKILLS:
|
||||
ultimate_skill = skill
|
||||
ultimate_id = skill_id
|
||||
break
|
||||
|
||||
# 모든 공격 스킬 (유틸리티 제외)
|
||||
attack_skills = []
|
||||
for skill_id, skill in skills.items():
|
||||
if skill_id in UTILITY_SKILLS:
|
||||
continue
|
||||
if skill.get('bIsUltimate', False):
|
||||
continue
|
||||
if not skill.get('montageData'):
|
||||
continue
|
||||
|
||||
# 시퀀스 길이 계산
|
||||
sequence_length = sum(
|
||||
m['actualDuration']
|
||||
for m in skill['montageData']
|
||||
if 'Ready' not in m['assetName'] and 'Equipment' not in m['assetName']
|
||||
)
|
||||
|
||||
if sequence_length > 0:
|
||||
attack_skills.append({
|
||||
'id': skill_id,
|
||||
'name': skill['name'],
|
||||
'damage_rate': skill['skillDamageRate'],
|
||||
'casting_time': skill.get('castingTime', 0),
|
||||
'sequence_length': sequence_length,
|
||||
'mana_cost': skill['manaCost']
|
||||
})
|
||||
|
||||
# 버스트 시나리오: 궁극기 → 모든 스킬 → 평타
|
||||
current_time = 0.0
|
||||
total_damage = 0.0
|
||||
skill_order = []
|
||||
|
||||
# 1. 궁극기 사용 (있는 경우)
|
||||
if ultimate_skill:
|
||||
ult_sequence = sum(
|
||||
m['actualDuration']
|
||||
for m in ultimate_skill.get('montageData', [])
|
||||
if 'Ready' not in m['assetName'] and 'Equipment' not in m['assetName']
|
||||
)
|
||||
ult_time = ultimate_skill.get('castingTime', 0) + ult_sequence
|
||||
|
||||
if current_time + ult_time <= duration:
|
||||
ult_damage = base_damage * ultimate_skill['skillDamageRate']
|
||||
total_damage += ult_damage
|
||||
current_time += ult_time
|
||||
skill_order.append({
|
||||
'time': round(current_time, 2),
|
||||
'skill': ultimate_skill['name'],
|
||||
'damage': round(ult_damage, 2),
|
||||
'type': 'ultimate'
|
||||
})
|
||||
|
||||
# 2. 모든 스킬 한 번씩 사용 (피해량 높은 순서)
|
||||
attack_skills.sort(key=lambda x: x['damage_rate'], reverse=True)
|
||||
|
||||
for skill in attack_skills:
|
||||
skill_time = skill['casting_time'] + skill['sequence_length']
|
||||
if current_time + skill_time > duration:
|
||||
continue
|
||||
|
||||
skill_damage = base_damage * skill['damage_rate']
|
||||
total_damage += skill_damage
|
||||
current_time += skill_time
|
||||
skill_order.append({
|
||||
'time': round(current_time, 2),
|
||||
'skill': skill['name'],
|
||||
'damage': round(skill_damage, 2),
|
||||
'type': 'skill'
|
||||
})
|
||||
|
||||
# 3. 남은 시간 평타
|
||||
remaining_time = duration - current_time
|
||||
basic_result = calculate_basic_attack_dps(stalker_id, stalker_data, base_damage)
|
||||
basic_dps = basic_result['dps']
|
||||
basic_damage = basic_dps * remaining_time
|
||||
total_damage += basic_damage
|
||||
|
||||
burst_dps = total_damage / duration
|
||||
|
||||
return {
|
||||
'dps': round(burst_dps, 2),
|
||||
'duration': duration,
|
||||
'base_damage': round(base_damage, 2),
|
||||
'ultimate_damage': round(skill_order[0]['damage'], 2) if skill_order and skill_order[0]['type'] == 'ultimate' else 0,
|
||||
'skill_damage': round(sum(s['damage'] for s in skill_order if s['type'] == 'skill'), 2),
|
||||
'basic_damage': round(basic_damage, 2),
|
||||
'remaining_time': round(remaining_time, 2),
|
||||
'skill_order': skill_order,
|
||||
'has_ultimate': ultimate_skill is not None,
|
||||
'notes': ''
|
||||
}
|
||||
|
||||
|
||||
def calculate_dot_dps_by_hp(target_hp_list: List[int] = [100, 500, 1000]) -> Dict:
|
||||
"""DoT 스킬 DPS (대상 HP별)"""
|
||||
dot_results = {}
|
||||
|
||||
for skill_id, skill_info in DOT_SKILLS.items():
|
||||
dot_type = skill_info['dot_type']
|
||||
dot_config = DOT_DAMAGE[dot_type]
|
||||
|
||||
dot_results[skill_id] = {
|
||||
'stalker': skill_info['stalker'],
|
||||
'name': skill_info['name'],
|
||||
'dot_type': dot_type,
|
||||
'description': dot_config['description'],
|
||||
'dps_by_hp': {}
|
||||
}
|
||||
|
||||
for target_hp in target_hp_list:
|
||||
if 'rate' in dot_config:
|
||||
# 퍼센트 피해 (Poison, Burn)
|
||||
dot_damage = target_hp * dot_config['rate']
|
||||
dot_dps = dot_damage / dot_config['duration']
|
||||
else:
|
||||
# 고정 피해 (Bleed)
|
||||
dot_damage = dot_config['damage']
|
||||
dot_dps = dot_damage / dot_config['duration']
|
||||
|
||||
dot_results[skill_id]['dps_by_hp'][target_hp] = round(dot_dps, 2)
|
||||
|
||||
return dot_results
|
||||
|
||||
|
||||
def calculate_summon_independent_dps(stalker_data: Dict, base_damage: float) -> Dict:
|
||||
"""소환체 독립 DPS 계산"""
|
||||
summon_results = {}
|
||||
|
||||
for skill_id, summon_info in SUMMON_SKILLS.items():
|
||||
stalker_id = summon_info['stalker']
|
||||
|
||||
# 해당 스토커의 스킬인지 확인
|
||||
skills = stalker_data.get('skills', {})
|
||||
if skill_id not in skills:
|
||||
continue
|
||||
|
||||
skill = skills[skill_id]
|
||||
active_duration = skill.get('activeDuration', 0)
|
||||
|
||||
if summon_info['summon'] == 'Ifrit':
|
||||
# Ifrit: 3개 몽타주 순차 루프 (2.87 + 2.90 + 2.52 = 8.29초 사이클)
|
||||
# 20초 지속
|
||||
montage_data = skill.get('montageData', [])
|
||||
cycle_time = sum(m['actualDuration'] for m in montage_data if 'Ready' not in m['assetName'])
|
||||
attack_count = (active_duration / cycle_time) * len(montage_data)
|
||||
|
||||
# Ifrit 공격: BaseDamage × 1.2
|
||||
total_damage = base_damage * 1.2 * attack_count
|
||||
summon_dps = total_damage / active_duration if active_duration > 0 else 0
|
||||
|
||||
summon_results[skill_id] = {
|
||||
'name': summon_info['name'],
|
||||
'summon': summon_info['summon'],
|
||||
'active_duration': active_duration,
|
||||
'cycle_time': round(cycle_time, 2),
|
||||
'attack_count': round(attack_count, 2),
|
||||
'dps': round(summon_dps, 2),
|
||||
'notes': f'{len(montage_data)}개 몽타주 순차 루프'
|
||||
}
|
||||
|
||||
elif summon_info['summon'] == 'Shiva':
|
||||
# Shiva: 단일 몽타주 2.32초 반복
|
||||
# 60초 지속
|
||||
montage_name = summon_info.get('montage', '')
|
||||
# TODO: 실제 몽타주 시간 찾아서 계산
|
||||
cycle_time = 2.32 # 임시값
|
||||
attack_count = active_duration / cycle_time
|
||||
|
||||
# Shiva 공격: BaseDamage × 0.8
|
||||
total_damage = base_damage * 0.8 * attack_count
|
||||
summon_dps = total_damage / active_duration if active_duration > 0 else 0
|
||||
|
||||
summon_results[skill_id] = {
|
||||
'name': summon_info['name'],
|
||||
'summon': summon_info['summon'],
|
||||
'active_duration': active_duration,
|
||||
'cycle_time': round(cycle_time, 2),
|
||||
'attack_count': round(attack_count, 2),
|
||||
'dps': round(summon_dps, 2),
|
||||
'notes': '단일 몽타주 반복'
|
||||
}
|
||||
|
||||
return summon_results
|
||||
|
||||
|
||||
def save_dps_results_json(all_results: Dict, output_dir: Path) -> None:
|
||||
"""DPS 계산 결과를 JSON으로 저장 (Claude 분석용)"""
|
||||
|
||||
# 정렬된 데이터 준비
|
||||
scenario1_sorted = sorted(
|
||||
[(sid, all_results[sid]['scenario1']) for sid in STALKERS if sid in all_results],
|
||||
key=lambda x: x[1]['dps'],
|
||||
reverse=True
|
||||
)
|
||||
|
||||
scenario2_sorted = sorted(
|
||||
[(sid, all_results[sid]['scenario2']) for sid in STALKERS if sid in all_results],
|
||||
key=lambda x: x[1]['dps'],
|
||||
reverse=True
|
||||
)
|
||||
|
||||
scenario3_sorted = sorted(
|
||||
[(sid, all_results[sid]['scenario3']) for sid in STALKERS if sid in all_results],
|
||||
key=lambda x: x[1]['dps'],
|
||||
reverse=True
|
||||
)
|
||||
|
||||
# 정렬된 순위 정보 추가
|
||||
for rank, (stalker_id, _) in enumerate(scenario1_sorted, 1):
|
||||
all_results[stalker_id]['scenario1']['rank'] = rank
|
||||
|
||||
for rank, (stalker_id, _) in enumerate(scenario2_sorted, 1):
|
||||
all_results[stalker_id]['scenario2']['rank'] = rank
|
||||
|
||||
for rank, (stalker_id, _) in enumerate(scenario3_sorted, 1):
|
||||
all_results[stalker_id]['scenario3']['rank'] = rank
|
||||
|
||||
# JSON 저장
|
||||
output_file = output_dir / "dps_raw_results.json"
|
||||
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(all_results, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"Generated: {output_file}")
|
||||
|
||||
|
||||
def main():
|
||||
"""메인 실행 함수"""
|
||||
print("=" * 80)
|
||||
print("DPS 시나리오 계산 v2")
|
||||
print("=" * 80)
|
||||
|
||||
# 1. 출력 디렉토리 가져오기 (가장 최근 v2 폴더)
|
||||
output_dir = get_output_dir(create_new=False)
|
||||
print(f"\nOutput directory: {output_dir}")
|
||||
|
||||
# 2. validated_data.json 로드
|
||||
print("\nLoading validated_data.json...")
|
||||
validated_data = load_validated_data(output_dir)
|
||||
print(f"Loaded data for {len(validated_data)} stalkers")
|
||||
|
||||
# 3. 각 스토커별 DPS 계산
|
||||
print("\nCalculating DPS scenarios...")
|
||||
all_results = {}
|
||||
|
||||
for stalker_id in STALKERS:
|
||||
print(f"\n Processing: {STALKER_INFO[stalker_id]['name']} ({stalker_id})...")
|
||||
|
||||
stalker_data = validated_data.get(stalker_id, {})
|
||||
if not stalker_data:
|
||||
print(f" WARNING: No data for {stalker_id}, skipping")
|
||||
continue
|
||||
|
||||
stats = stalker_data['stats']['stats']
|
||||
|
||||
# BaseDamage 계산
|
||||
base_damage = calculate_base_damage(stalker_id, stats)
|
||||
print(f" BaseDamage: {round(base_damage, 2)}")
|
||||
|
||||
# 시나리오 1: 평타 DPS
|
||||
scenario1 = calculate_basic_attack_dps(stalker_id, stalker_data, base_damage)
|
||||
print(f" Basic Attack DPS: {scenario1['dps']}")
|
||||
|
||||
# 시나리오 2: 스킬 로테이션 DPS (30초)
|
||||
scenario2 = calculate_skill_rotation_dps(stalker_id, stalker_data, base_damage, 30.0)
|
||||
print(f" Rotation DPS: {scenario2['dps']}")
|
||||
|
||||
# 시나리오 3: 버스트 DPS (10초)
|
||||
scenario3 = calculate_burst_dps(stalker_id, stalker_data, base_damage, 10.0)
|
||||
print(f" Burst DPS: {scenario3['dps']}")
|
||||
|
||||
all_results[stalker_id] = {
|
||||
'scenario1': scenario1,
|
||||
'scenario2': scenario2,
|
||||
'scenario3': scenario3
|
||||
}
|
||||
|
||||
# 소환체 분석 (Rene만)
|
||||
if stalker_id == 'rene':
|
||||
summon_analysis = calculate_summon_independent_dps(stalker_data, base_damage)
|
||||
all_results[stalker_id]['summon_analysis'] = summon_analysis
|
||||
print(f" Summon analysis complete: {len(summon_analysis)} skills")
|
||||
|
||||
# 4. DoT 분석 (전역)
|
||||
print("\n Calculating DoT DPS...")
|
||||
dot_analysis = calculate_dot_dps_by_hp([100, 500, 1000])
|
||||
all_results['dot_analysis'] = dot_analysis
|
||||
print(f" DoT analysis complete: {len(dot_analysis)} skills")
|
||||
|
||||
# 5. JSON 저장 (Claude 분석용)
|
||||
print("\nSaving DPS results to JSON...")
|
||||
save_dps_results_json(all_results, output_dir)
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("DPS calculation complete!")
|
||||
print("=" * 80)
|
||||
print("\nNext step: Run Claude analysis to generate 02_DPS_시나리오_비교분석_v2.md")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user