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

178 lines
7.5 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}
}