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

178 lines
7.5 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;
using System.Collections.Generic;
using UnityEngine;
namespace TKS.OutGame
{
/// <summary>순찰 수확 1회(미리보기 · 실제 지급 공용).</summary>
public class PatrolYield
{
public float hours;
public int gold;
public int exp;
public string gearGrade;
public int gear;
public int chests;
public float gearF;
public float chestF;
public float mult = 1f;
public List<string> gearIds = new List<string>();
public List<string> expTo = new List<string>();
public List<string> chestIds = new List<string>();
}
/// <summary>
/// 내정 순찰 규칙(기획서 §7 · S10 · PD #1093 C) — 마지막 클리어 챕터(chapterMax)의 patrol.csv 행 × 누적 시간(오프라인 포함 · 상한 patrol_cap_hours + 농업 patrol_cap_min) ·
/// 골드(× 순찰 수확·순찰 골드 개발) · 병사 경험(출전 편성 전원 · × 순찰 수확·경험 개발) · 승급 장비(patrol_gear_per_hour × 장비 확률 개발 · 등급 = gear_grade + 둔전제) ·
/// 청동 상자(chest_bronze_per_hour) — 장비·상자 소수점은 다음 수확으로 이월(결정적) · 광고 2배(patrol_ad_daily/일 · 스텁) · 즉시 수확(보옥 · patrol_instant_hours 분 · 타이머 그대로).
/// </summary>
public static class PatrolRules
{
public static float CapHours(OutGameData db, SaveData d)
{
return db.Economy.PatrolCapHours + DevRules.Mod(db, d, DevRules.PatrolCap) / 60f;
}
public static DateTime StartAt(SaveData d, DateTime nowUtc)
{
var t = SaveStore.ParseIso(d.patrol.startAt, nowUtc);
return t > nowUtc ? nowUtc : t;
}
/// <summary>지금까지 쌓인 시간(상한에서 멈춤).</summary>
public static float Hours(OutGameData db, SaveData d, DateTime nowUtc)
{
float h = (float)(nowUtc - StartAt(d, nowUtc)).TotalHours;
return Mathf.Clamp(h, 0f, CapHours(db, d));
}
public static PatrolTable.Row Row(OutGameData db, SaveData d)
{
return db.Patrol.For(Mathf.Max(1, d.chapterMax));
}
/// <summary>순찰 장비 등급 = 표 gear_grade + 둔전제 Lv(최고 등급에서 멈춤).</summary>
public static string GearGrade(OutGameData db, SaveData d)
{
var row = Row(db, d);
if (row == null) return "";
int order = db.Gear.OrderOf(row.gearGrade) + Mathf.RoundToInt(DevRules.Mod(db, d, DevRules.PatrolGearGrade));
string best = row.gearGrade;
int bestOrder = 0;
foreach (var it in db.Gear.items)
if (it.order <= order && it.order > bestOrder)
{
bestOrder = it.order;
best = it.grade;
}
return best;
}
public static float GoldPerHour(OutGameData db, SaveData d)
{
var row = Row(db, d);
return row == null ? 0f : row.goldPerHour * (1f + (DevRules.Mod(db, d, DevRules.PatrolYield) + DevRules.Mod(db, d, DevRules.PatrolGold)) / 100f);
}
public static float ExpPerHour(OutGameData db, SaveData d)
{
var row = Row(db, d);
return row == null ? 0f : row.expPerHour * (1f + (DevRules.Mod(db, d, DevRules.PatrolYield) + DevRules.Mod(db, d, DevRules.ExpGain)) / 100f);
}
public static float GearPerHour(OutGameData db, SaveData d)
{
return db.Economy.PatrolGearPerHour * DevRules.Mult(db, d, DevRules.PatrolGearChance) * DevRules.Mult(db, d, DevRules.PatrolYield);
}
public static float ChestPerHour(OutGameData db, SaveData d)
{
var row = Row(db, d);
return row == null ? 0f : row.chestPerHour * DevRules.Mult(db, d, DevRules.PatrolYield);
}
/// <summary>hours 시간분 수확량(mult = 광고 2배 등 · 이월 소수점 포함).</summary>
public static PatrolYield Preview(OutGameData db, SaveData d, float hours, float mult)
{
var y = new PatrolYield { hours = hours, mult = mult, gearGrade = GearGrade(db, d) };
y.gold = Mathf.RoundToInt(GoldPerHour(db, d) * hours * mult);
y.exp = Mathf.RoundToInt(ExpPerHour(db, d) * hours * mult);
y.gearF = GearPerHour(db, d) * hours * mult + d.patrol.carryGear;
y.chestF = ChestPerHour(db, d) * hours * mult + d.patrol.carryChest;
y.gear = Mathf.FloorToInt(y.gearF + 1e-4f);
y.chests = Mathf.FloorToInt(y.chestF + 1e-4f);
return y;
}
static void Pay(OutGameData db, SaveData d, PatrolYield y, IList<string> expTo, System.Random rng)
{
d.gold += y.gold;
if (expTo != null)
foreach (var id in expTo)
{
var s = d.Level(id);
if (s == null || y.expTo.Contains(id)) continue;
HeroRules.GrantExp(s, y.exp);
y.expTo.Add(id);
}
var pool = new List<string>();
foreach (var it in db.Gear.items)
if (it.grade == y.gearGrade) pool.Add(it.id);
for (int i = 0; i < y.gear && pool.Count > 0; i++)
{
var g = pool[rng.Next(pool.Count)];
d.inv.AddGear(g, 1);
y.gearIds.Add(g);
}
for (int i = 0; i < y.chests; i++)
{
var got = TreasureRules.AddChest(db, d, db.Economy.PatrolChest, rng);
if (got != null) y.chestIds.Add(got);
}
d.patrol.carryGear = Mathf.Max(0f, y.gearF - y.gear);
d.patrol.carryChest = Mathf.Max(0f, y.chestF - y.chests);
}
/// <summary>수확(쌓인 시간 전부 · 타이머 리셋 · 순찰 카운터 +1). ad = 광고 2배(하루 patrol_ad_daily 회 · 스텁).</summary>
public static PatrolYield Harvest(OutGameData db, SaveData d, DateTime nowUtc, IList<string> expTo, bool ad, System.Random rng, out string why)
{
why = null;
float h = Hours(db, d, nowUtc);
var e = db.Economy;
EconomyRules.RollDaily(d, nowUtc);
if (ad && d.adsUsed.patrol >= e.PatrolAdDaily)
{
why = "오늘 광고 2배 " + e.PatrolAdDaily + "회를 모두 썼습니다";
return null;
}
if (h <= 0f)
{
why = "쌓인 수확 없음";
return null;
}
var y = Preview(db, d, h, ad ? e.PatrolAdMult : 1f);
Pay(db, d, y, expTo, rng);
if (ad) d.adsUsed.patrol++;
d.patrol.startAt = SaveStore.Iso(nowUtc);
TreasureRules.AddCount(d, BattleEvents.Patrols, 1);
return y;
}
/// <summary>즉시 수확 = 보옥 patrol_instant_gem → patrol_instant_hours 시간분(쌓인 타이머는 그대로).</summary>
public static PatrolYield Instant(OutGameData db, SaveData d, IList<string> expTo, System.Random rng, out string why)
{
why = null;
var e = db.Economy;
if (d.gem < e.PatrolInstantGem)
{
why = "보옥 부족 " + d.gem + " / " + e.PatrolInstantGem;
return null;
}
d.gem -= e.PatrolInstantGem;
var y = Preview(db, d, e.PatrolInstantHours, 1f);
Pay(db, d, y, expTo, rng);
return y;
}
}
}