ThreeKingdomsSlingshot/Assets/Scripts/OutGame/Domestic/DevRules.cs

174 lines
7.7 KiB
C#
Raw Permalink Normal View History

아웃게임 2차(PD #1093 세션 C) — 내정(훈련·순찰·개발) · 보물(수집품·세트·상자) · 연무장(탑·일일 던전·투기장·적장 토벌) · 거래 상자 탭 + 영웅 56명 확장(PD #1096 · 신규 effect_key 4) - 데이터: C 표 10장(training · training_ranks · patrol · dev_tree · treasures · treasure_sets · chests(+iron/buy 열) · tower · dungeons(+reward_amount) · arena(+상점 키)) + economy.csv C 키 24 → 임포트 18 자산(OutGameTables.C.cs 검증: 행·연속 Lv/랭크/층·참조·확률 합 1·선행 노드·효과 키 처리기·카운터 키 · 오류 시 자산 불변) - 영웅 56명: heroes 71 · skills 112 · hero_gear 1344 · gacha_pool 71 재복사 · 신규 키 4(team_dmg_pct_turn · cooldown_add · charm · heal_self_pct) + 기존 키 조건 열(받는 피해 p2 HP 임계/보스/근접 · p3 출전 인원 아군 전원 · 최대 HP·반격 아군 전원 · 피해 % 발동 턴) · 대장군 키는 표 미사용(처리기 유지) - 내정: 훈련(병종 5 · 3항목 Lv · cost_mult · 랭크 승급 · train_bonus → §3-2 항목별) · 순찰(챕터 행 × 시간 · 오프라인 · 상한 12h + 농업 · 소수점 이월 · 광고 2배 스텁 · 즉시 수확 보옥) · 개발(갈래 3 · 선행 · 골드·도면 비용 · 효과 키 → 군량·보상·상점·순찰·상자 적용 지점 · 초기화 보옥 + 도면 환급) - 보물: 보유 중(4열 · 등급 색 · 상세·강화) · 세트(단계 효과) · 상자(포인트 → 명장 · 확률 · 중복 조각) · 효과 hp/atk/def → HeroRules · crit/range/splash → BattleManager.SideEffectsProvider → SkillMods · 조건 카운터 8종 = Runtime/BattleEvents(전투 코드 Raise 1줄씩 · 아군만) · 스테이지 청동 상자 10 % - 연무장: BattleManager.StartMode(BattleMode = 적 편성·제한 턴·onExit) · 탑 100층(입장권·층 배율·보상·10층 상자) · 던전 3(요일·2/일·보상) · 투기장(샘플 10 · EnemyAI 방어 · 메달·주간 순위·조각 상점) · 적장 토벌(주간 순환·hp ×20·5턴·누적 피해·명장 상자·천기) · 결과 패널 → 탭 = 연무장 - 거래: 상자 탭(구매 = 골드/보옥/포인트 · 거래 할인) · 패스·월정액·챕터 팩·첫 결제 팩 = 준비 중 스텁 · 스킬서 임시 판매 제거(사당 던전) - 테스트 87/87(기존 73 유지 · 갱신 3 = 로비 탭 · 표 키 목록 · 상점 스킬서 칸 / 신규 14 = OutGameCTests 12 + 확장 키 2) · console error 0 · 캡처 = DataImporter.CaptureC Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 15:22:54 +00:00
using System.Collections.Generic;
using UnityEngine;
namespace TKS.OutGame
{
/// <summary>
/// 내정 개발 트리 규칙(기획서 §7 · S11 · PD #1093 C) — 노드 레벨 · 선행 조건 · 비용(골드 = gold_base × gold_growth^lv · 도면 = blueprint_from_lv 이상 Lv 마다 blueprint_per_lv) ·
/// 초기화(보옥 dev_reset_gem · 쓴 도면 환급 · 골드 환급 없음) · 효과 = Mod(키) = Σ pct_per_lv × Lv 를 각 계산 지점이 읽는다(아래 Keys = 키 → 적용 지점).
/// </summary>
public static class DevRules
{
public const string GoldGain = "gold_gain_pct";
public const string GemBonus = "gem_bonus_pct";
public const string TradeDiscount = "trade_discount_pct";
public const string ChestPoint = "chest_point_pct";
public const string TradeFreeRefresh = "trade_free_refresh";
public const string PatrolGold = "patrol_gold_pct";
public const string SellPrice = "sell_price_pct";
public const string FirstClearGem = "first_clear_gem_pct";
public const string StarReward = "star_reward_pct";
public const string FoodCap = "food_cap_add";
public const string FoodRegen = "food_regen_cut_sec";
public const string PatrolYield = "patrol_yield_pct";
public const string PatrolCap = "patrol_cap_min";
public const string PatrolGearGrade = "patrol_gear_grade_up";
public const string ExpGain = "exp_gain_pct";
public const string FoodCost = "food_cost_cut";
public const string AdFood = "ad_food_add";
public const string PatrolGearChance = "patrol_gear_chance_pct";
public const string GearDrop = "gear_drop_pct";
public const string ChestHigh = "chest_high_grade_pct";
public const string IronGain = "iron_gain_pct";
public const string CraftCost = "craft_cost_cut_pct";
public const string CraftBulk = "craft_bulk";
public const string CraftBlueprint = "craft_blueprint_cut";
public const string GearSell = "gear_sell_pct";
public const string BlueprintGain = "blueprint_gain_pct";
public const string DungeonGear = "dungeon_gear_pct";
public const string ChestTier = "chest_tier_up_pct";
/// <summary>효과 키 → 적용 지점(화면 설명 · 「미구현」 = 판매·합성·별 보상이 아직 없어 적용 지점 없음 · PD 확인 대상).</summary>
public static readonly Dictionary<string, string> Keys = new Dictionary<string, string>
{
{ GoldGain, "스테이지·군량고 골드 +%" },
{ GemBonus, "스테이지·탑 보옥 +%" },
{ TradeDiscount, "상점 장비·상자 가격 −%" },
{ ChestPoint, "상자 포인트 +%" },
{ TradeFreeRefresh, "상점 무료 새로고침 +회/일" },
{ PatrolGold, "순찰 골드 +%" },
{ SellPrice, "판매가 +% (판매 미구현)" },
{ FirstClearGem, "첫 클리어 보옥 +%" },
{ StarReward, "별 보상 +% (별 보상 미구현)" },
{ FoodCap, "군량 상한 +" },
{ FoodRegen, "군량 회복 −초" },
{ PatrolYield, "순찰 수확 +%" },
{ PatrolCap, "순찰 상한 +분" },
{ PatrolGearGrade, "순찰 장비 등급 +" },
{ ExpGain, "스테이지·순찰 경험 +%" },
{ FoodCost, "스테이지 군량 −(1 단위 내림)" },
{ AdFood, "광고 군량 +" },
{ PatrolGearChance, "순찰 장비 +%" },
{ GearDrop, "스테이지 장비 드랍 +%p" },
{ ChestHigh, "상자 상위 등급 확률 +%" },
{ IronGain, "운철 +%" },
{ CraftCost, "합성 비용 −% (합성 미구현)" },
{ CraftBulk, "합성 일괄 (합성 미구현)" },
{ CraftBlueprint, "합성 도면 − (합성 미구현)" },
{ GearSell, "장비 판매 +% (판매 미구현)" },
{ BlueprintGain, "도면 획득 +%" },
{ DungeonGear, "대장간 장비 +%" },
{ ChestTier, "상자 한 단계 위 확률 +%" },
};
public static bool Known(string key)
{
return key != null && Keys.ContainsKey(key);
}
public static int Level(SaveData d, string node)
{
return Counts.Get(d.dev.nodes, node);
}
/// <summary>효과 합 = Σ(키를 가진 노드) pct_per_lv × Lv(단위는 키마다 · Keys).</summary>
public static float Mod(OutGameData db, SaveData d, string key)
{
if (db == null || db.Dev == null || d == null) return 0f;
float s = 0f;
foreach (var n in db.Dev.nodes)
{
int lv = Level(d, n.id);
if (lv <= 0) continue;
foreach (var k in n.EffectKeys)
if (k == key) s += n.pctPerLv * lv;
}
return s;
}
/// <summary>% 효과의 배율 = 1 + Mod/100.</summary>
public static float Mult(OutGameData db, SaveData d, string key)
{
return 1f + Mod(db, d, key) / 100f;
}
/// <summary>Lv → Lv+1 비용(골드 · 도면).</summary>
public static void NextCost(DevTree.Node n, int lv, out int gold, out int blueprint)
{
gold = Mathf.RoundToInt(n.goldBase * Mathf.Pow(n.goldGrowth, lv));
blueprint = lv + 1 >= n.blueprintFromLv ? n.blueprintPerLv : 0;
}
public static bool Unlocked(SaveData d, DevTree.Node n)
{
foreach (var r in n.requires)
if (Level(d, r.node) < r.lv) return false;
return true;
}
public static string CanUpgrade(OutGameData db, SaveData d, string id)
{
var n = db.Dev.Get(id);
if (n == null) return "없는 노드";
int lv = Level(d, id);
if (lv >= n.lvMax) return "최고 레벨";
if (!Unlocked(d, n))
{
var sb = new List<string>();
foreach (var r in n.requires)
if (Level(d, r.node) < r.lv) sb.Add(db.Dev.Get(r.node).nameKo + " Lv " + r.lv);
return "선행: " + string.Join(" · ", sb.ToArray());
}
int gold, bp;
NextCost(n, lv, out gold, out bp);
if (d.gold < gold) return "골드 부족 " + d.gold.ToString("N0") + " / " + gold.ToString("N0");
if (d.inv.blueprint < bp) return "도면 부족 " + d.inv.blueprint + " / " + bp;
return null;
}
public static string Upgrade(OutGameData db, SaveData d, string id)
{
var why = CanUpgrade(db, d, id);
if (why != null) return why;
var n = db.Dev.Get(id);
int gold, bp;
NextCost(n, Level(d, id), out gold, out bp);
d.gold -= gold;
d.inv.blueprint -= bp;
d.dev.blueprintSpent += bp;
Counts.Add(d.dev.nodes, id, 1);
return null;
}
public static int TotalLevels(SaveData d)
{
int n = 0;
foreach (var c in d.dev.nodes) n += c.n;
return n;
}
/// <summary>트리 초기화 = 보옥 dev_reset_gem · 쓴 도면 환급 · 골드 환급 없음.</summary>
public static string Reset(OutGameData db, SaveData d)
{
if (TotalLevels(d) == 0) return "초기화할 노드 없음";
int cost = db.Economy.DevResetGem;
if (d.gem < cost) return "보옥 부족 " + d.gem + " / " + cost;
d.gem -= cost;
d.inv.blueprint += d.dev.blueprintSpent;
d.dev.blueprintSpent = 0;
d.dev.nodes.Clear();
return null;
}
}
}