ThreeKingdomsSlingshot/Assets/Scripts/Runtime/Skills/SkillCaptureDriver.cs

188 lines
7.5 KiB
C#
Raw Permalink Normal View History

스킬 시스템(PD #1093 세션 B) — skills.csv effect_key 51종 처리기 · 액티브(예약/즉시)·패시브 · HUD 스킬 버튼 · 적 무장 패시브 · 스킬 레벨업·스킬서 - 데이터: SkillInfoDb(= 스킬 DB) 에 effect_key·p1~p3(pMask)·lv_scale·needs_impl 추가 · 임포터 검증(trigger · 키 51종 · p 칸 · skill_levels 연속 · lv_max) · skill_levels = levels(비용 표) - 처리기 Runtime/Skills: SkillKeys(51종 · 레벨 계수 = 값×계수 / 배율은 1 넘는 몫 / 횟수 반올림 / 켜기·치환 고정 · 팀 키) · SkillDef·SkillCatalog · SkillSystem(유닛별 쿨·예약 · 즉시형 시한 효과 이번 아군 턴/이번 적 턴 · 행동 훅 · 호위 분담 · HUD 한 줄) · SkillMods(전투 코드 수치 질의) · SkillRules(상한 = 등급 + ★3 액티브 +1 · ★5 패시브 ×1.5 = evolve.csv · 레벨업 · 스킬서 판매) - 전투 연결: ActionResolver(밀기·튕김·추가 이동 n·추가 이동 공격·충돌 추가 피해·밀며 전진 ×·삼첨도·관통 피해/넉백/반격 없음/빗나가도 복귀/한 번 더 관통·사거리·사선 통과·크리·광역 반경/비율/아군 0·백기습영 이동 후 사격·군령/대장군 공격 횟수) · Combat(상성 하한·공 %·주는/받는 피해·근접 패널티 해제·반격 배율/받는 반격·견제 배율) · BattleManager(편성·적 무장 부착 · 견제 반경/횟수/무제한/회복 · 교전 반응 정지 · 교전 범위 · 즉시형 실행) · EnemyAI·RangeIndicator(슬링·사거리 스킬 반영) - UI: HUD 스킬 버튼(이름·Lv·쿨·사용 가능/불가·패시브) · 탭 = 예약/취소/즉시 · 조준 중 2손가락 탭 · 발동 = 이름 글자 + 플래시 · 스킬 한 줄 · 예약 배지 · 변진 반경 원 · 영웅 상세 스킬 레벨업 팝업 · 거래 상점 스킬서 칸(임시) - 표: economy.csv start_skill_books 10 · skill_book_shop_daily 3 · skill_book_price 300(임시 · PD 확인) - 테스트 73/73(기존 56 + SkillTests 17 · 51종 전부) · console error 0 · 캡처 = DataImporter.CaptureSkills Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 14:31:27 +00:00
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
namespace TKS.OutGame
{
/// <summary>
/// 스킬 화면 검증 캡처(에디터 DataImporter.CaptureSkills 가 SessionState 로 출력 폴더를 넘기면 생성 · 세이브 = 메모리 전용 신규 유저 · PD #1093 B).
/// ① 스킬 레벨업(영웅 상세 → 팝업 · 액티브 Lv+1 뒤) ② 스킬 버튼(전투 · 관우 선택) ③ 액티브 발동(장비 장판 일갈 · 이름 글자 + 플래시 + 밀기)
/// ④ 패시브 표시(조운 관통 → 상산의 창 HUD 한 줄). 캡처를 위해 적 1기씩을 아군 앞에 옮겨 둔다(그 밖은 실제 처리).
/// </summary>
public class SkillCaptureDriver : MonoBehaviour
{
public const string DirKey = "TKS_SKILL_CAPTURE_DIR";
public string OutputDir;
public int Width = 720;
public int Height = 1280;
readonly List<string> log = new List<string>();
int shot = 1;
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void AutoSpawn()
{
string dir = UnityEditor.SessionState.GetString(DirKey, "");
if (string.IsNullOrEmpty(dir)) return;
UnityEditor.SessionState.EraseString(DirKey);
var d = new GameObject("SkillCaptureDriver").AddComponent<SkillCaptureDriver>();
d.OutputDir = dir;
}
#endif
IEnumerator Start()
{
Directory.CreateDirectory(OutputDir);
yield return null;
yield return null;
var root = OutGameRoot.Instance;
var cam = Camera.main;
if (root == null || cam == null)
{
Log("FAIL: OutGameRoot 또는 카메라 없음");
Finish(1);
yield break;
}
var s = root.Session;
Log("memoryOnly=" + SaveStore.MemoryOnly + " newUser=" + s.IsNewUser + " books=" + s.Save.inv.books.skill + " gold=" + s.Save.gold);
// ① 스킬 레벨업: 첫 시작 영웅(★3 = 액티브 상한 +1) 액티브 Lv1 → 2
var id = s.Db.Economy.StartHeroes[0];
var h = s.Def(id);
var hs = s.Save.Hero(id);
root.ShowHero(id);
yield return Wait();
Log("skill cap active=" + SkillRules.Cap(s.Db, h, hs, 0) + " passive=" + SkillRules.Cap(s.Db, h, hs, 1) + " star=" + hs.star + " grade=" + hs.grade);
Log("levelup active → " + (SkillRules.LevelUp(s.Db, s.Save, h, hs, 0) ?? "ok") + " lv=" + hs.skillLv[0] + " books=" + s.Save.inv.books.skill + " gold=" + s.Save.gold);
s.Commit();
root.HeroDetail.Refresh();
root.HeroDetail.OnSkillUp();
yield return Wait();
yield return Shot(cam, "skill_levelup_" + id);
root.CloseModal();
// 전투 1-1
root.ShowLobby();
root.OpenWorldMap();
yield return Wait();
var wm = root.Map;
wm.OpenPopup(0);
yield return Wait();
wm.ConfirmPopup();
var bm = root.Battle;
float t0 = Time.realtimeSinceStartup;
while (bm.Phase != BattlePhase.PlayerTurn && Time.realtimeSinceStartup - t0 < 10f) yield return null;
var byHero = new Dictionary<string, Unit>();
foreach (var u in bm.Units)
{
if (u == null || u.Faction != Faction.Player) continue;
var us = bm.Skills.Of(u);
Log(" " + u.DisplayName + " " + u.Type + " active=" + (us != null && us.Active != null ? us.Active.Id + " Lv" + us.ActiveLv : "-") + " passive=" + (us != null && us.Passive != null ? us.Passive.Id + " ×" + us.PassiveMult : "-") + " hp" + u.MaxHp + " @" + u.Position);
if (us != null) byHero[us.HeroId] = u;
}
// ② 스킬 버튼: 관우 선택(다음 행동형 · 사용 가능)
Unit gy;
if (byHero.TryGetValue("guanyu", out gy))
{
bm.Sling.Select(gy);
yield return Wait();
Log("button: " + bm.Hud.SkillButtonText);
yield return Shot(cam, "skill_button_guanyu");
}
// ③ 액티브 발동: 장비 장판 일갈(즉시 · 반경 안 적 밀기 + 이번 적 턴 교전 반응 없음)
Unit zf;
if (byHero.TryGetValue("zhangfei", out zf))
{
var foe = Nearest(bm, zf);
if (foe != null) foe.Position = zf.Position + new Vector2(0f, 2f);
Vector2 before = foe != null ? foe.Position : Vector2.zero;
bm.Sling.Select(zf);
yield return null;
Log("press: " + bm.Hud.PressSkill());
yield return new WaitForSecondsRealtime(0.18f);
yield return Shot(cam, "active_zhangfei");
t0 = Time.realtimeSinceStartup;
while (bm.Phase != BattlePhase.PlayerTurn && Time.realtimeSinceStartup - t0 < 10f) yield return null;
Log("pushed " + (foe != null ? foe.DisplayName + " " + before + "→" + foe.Position : "-") + " readyTurn=" + bm.Skills.Of(zf).ReadyTurn + " acted=" + zf.ActedThisTurn);
}
// ④ 패시브 표시: 조운 관통(상산의 창 = 넉백 ×) → HUD 한 줄
Unit zy;
if (byHero.TryGetValue("zhaoyun", out zy))
{
var foe = Nearest(bm, zy);
if (foe != null)
{
foe.Position = zy.Position + new Vector2(0f, 3.5f);
bm.RequestPlayerAction(zy, foe.Position);
t0 = Time.realtimeSinceStartup;
while (bm.Phase != BattlePhase.PlayerTurn && Time.realtimeSinceStartup - t0 < 10f) yield return null;
}
bm.Sling.Select(zy);
yield return null;
Log("log: " + string.Join(" / ", bm.Skills.RecentLog(30f, 8).ToArray()));
Log("msg: " + bm.LastMessage);
yield return Shot(cam, "passive_zhaoyun");
}
Finish(0);
}
static Unit Nearest(BattleManager bm, Unit from)
{
Unit best = null;
float bd = float.MaxValue;
foreach (var e in bm.AliveUnits(Faction.Enemy))
{
float d = Vector2.Distance(e.Position, from.Position);
if (d < bd)
{
bd = d;
best = e;
}
}
return best;
}
static IEnumerator Wait()
{
yield return null;
yield return new WaitForSecondsRealtime(0.3f);
}
IEnumerator Shot(Camera cam, string name)
{
string file = Path.Combine(OutputDir, shot.ToString("00") + "_" + name + ".png");
shot++;
yield return CaptureUtil.Capture(cam, Width, Height, file);
Log("shot " + file);
}
void Log(string s)
{
log.Add(s);
Debug.Log("[SkillCapture] " + s);
try
{
File.WriteAllLines(Path.Combine(OutputDir, "capture_log.txt"), log.ToArray());
}
catch
{
}
}
void Finish(int code)
{
Log("finish code=" + code);
#if UNITY_EDITOR
UnityEditor.EditorApplication.Exit(code);
#else
Application.Quit(code);
#endif
}
}
}