225 lines
14 KiB
C#
225 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Numerics;
|
|
using UnityEngine;
|
|
|
|
// 원작 표(JSON) 구조 그대로 — 값은 Resources/Data 의 SOT 에서만 읽는다 (C45).
|
|
namespace VS.Sim
|
|
{
|
|
[Serializable] public class LevelRow { public int Level; public string Price; public string Follower; public int TapRate; public int PassRate; public float UpgradeTime; public int Condition; }
|
|
[Serializable] public class ItemRow { public int Index; public string Name; public int IndexDependency; public List<LevelRow> Levels; }
|
|
[Serializable] public class ItemTable { public List<ItemRow> Items; }
|
|
[Serializable] public class ClothesRow { public int Level; public string Price; public int StatRate; public int RankDependency; public float UpgradeTime; }
|
|
[Serializable] public class ClothesTable { public List<ClothesRow> Levels; }
|
|
[Serializable] public class RankRow { public int Index; public string Rank; public string Follower; public int RankBoostRate; public string Reward; }
|
|
[Serializable] public class RankTable { public List<RankRow> Items; }
|
|
[Serializable] public class QuestRow { public int id; public int preconditionId; public int conditionType; public string param1; public string param2; public string rewardNum; }
|
|
[Serializable] public class QuestTable { public List<QuestRow> items; }
|
|
[Serializable] public class ChatNameTable { public List<string> Names; }
|
|
[Serializable] public class ChatCommentRow { public int Index; public string CommentText; public List<int> EmojiIndexes; }
|
|
[Serializable] public class ChatCommentTable { public List<ChatCommentRow> Items; }
|
|
|
|
// ---- 원작 직렬화 기본값 (config.json · level0 RemoteConfig 등)
|
|
[Serializable]
|
|
public class RemoteCfg
|
|
{
|
|
public float AFKRate; public int AFKTimeMax, AFKTimeMin, AOACapTime, SessionShowAOA, BannerOffFirstSessionTime; public bool BannerOffFirstSessionValue;
|
|
public float BaseIncome; public int BigMoneyGiftCooldown; public float BigMoneyMultiplierX, BigMoneyMultiplierY;
|
|
public int GiftAppearTime, GiftCooldownNormal, GiftDisappearTime; public float GrowthIncome, HypeMulti;
|
|
public int InterAdCapTime, InterAdFirstShowTime, InterAdShowTime, SessionShowInter, RemoveAdsPopupCooldown, GiftUnlockTime, BannerUnlockTime;
|
|
public float KeyLevelMulti; public int NoADsGiftTime;
|
|
public bool OnOffAOA, OnOffGift, OnOffPK, OnOffSplashAds, OnOffSwitchAppAds, OnOffTut, OnOffRate, OnOffStory, OnOffQuest;
|
|
public int SaleOffGiftTime; public float SaleOffGiftValue; public int SmallMoneyGiftCooldown; public float SmallMoneyMultiplierX, SmallMoneyMultiplierY;
|
|
public int X10IncomeTime, X4PassiveIncomeGiftTime, X4TapIncomeGiftTime; public float X2CoinRewardTime, AutoTapRewardTime;
|
|
public int AutoClickIntroPopupCount, X2CoinIntroPopupCount, X4TapIntroPopupCount, X4PassiveIntroPopupCount, X10IncomeIntroPopupCount, ReducePriceIntroPopupCount;
|
|
public int MaxTapCoinCount, MaxTapHypeCount; public float HypePercentage, TapMultiplier, X10IncomeGiftCooldown; public int FreeGiftTicketAmount;
|
|
public bool OnOffActiveHold; public float HoldActivateSeconds; public int HoldTapsPerSecond;
|
|
public string AbTestGroup, NativeAOATier, ShowNativeAfterInters; public int TimeDelayInterAfterReward; public int[] TimeDailyTicket;
|
|
}
|
|
[Serializable]
|
|
public class InputCfg
|
|
{
|
|
public float _tapTimeWindow; public int _autoTapVisualPerSecond; public float _tapVisualWorldZ, _autoTapVisualXMin, _autoTapVisualXMax, _autoTapVisualYMin, _autoTapVisualYMax;
|
|
public float _punchScaleIntensity, _punchTapForce, _punchSpringStiffness, _punchSpringDamping, _returnIdleDelay; public int _hypeTapPerPercentage; public float _timeStartResetHypeState;
|
|
}
|
|
[Serializable] public class IdolCfg { public float _timeBetweenIdleBlink, _idleBlinkDuration, _hypeAnimDuration, _timeBetweenHypeFrame; }
|
|
[Serializable] public class GameCfg { public bool IsNewPkMode; public float ConvertRate; public bool IsPkEnabled; }
|
|
[Serializable] public class GiftDefRow { public int Type, Category, UnlockRankIndex; public float EffectDurationSeconds, CooldownSeconds; public int RequiredRewardedVideos; public bool RequireAdToClaim; public string Title, PopupDescription; }
|
|
[Serializable]
|
|
public class GiftCfg
|
|
{
|
|
public List<GiftDefRow> giftDefinitions; public int giftTimeSource, maxVisibleSlots;
|
|
public float idleTimeoutSeconds, refillDelaySeconds, unlockDelayAfterTutorial2Seconds, introStepSeconds, day2IdleTimeoutSeconds, day2RefillDelaySeconds;
|
|
public float dismissHoldDay1And2Seconds, dismissHoldDay3PlusSeconds, blinkRemainingRatio, reducePriceFactor;
|
|
public int smallMoneyPackPassiveMultiplier, smallMoneyPackTapMultiplier, bigMoneyPackPassiveMultiplier, bigMoneyPackTapMultiplier;
|
|
}
|
|
[Serializable] public class OutfitRow { public int Type, UnlockType, VisibilityRank, UnlockLevel, TicketPrice; public string ProductId; }
|
|
[Serializable] public class ClosetCfg { public List<OutfitRow> outfits; }
|
|
[Serializable] public class EnemyCfg { public List<string> EnemyName; }
|
|
[Serializable] public class ConfigFile { public RemoteCfg remote; public InputCfg input; public IdolCfg idol; public GameCfg game; public GiftCfg gift; public ClosetCfg closet; public EnemyCfg enemy; }
|
|
[Serializable] public class ChatRule { public int state; public float CommentsPerSecond, IntervalReducePercentPerRank, DonateReplaceChance; public int MaxConsecutiveDonate; }
|
|
[Serializable] public class DailyReward { public int kind; public int amount; }
|
|
[Serializable]
|
|
public class ExtraCfg
|
|
{
|
|
public int[] giftDay1Types, giftDay2Types, giftSeedOrder; public int giftSmallPackAdMultiplier, giftNoAdsVideos;
|
|
public List<ChatRule> chatRules; public int chatDonateMin, chatDonateMax; public float chatShowInterval, chatShowIntervalHype; public int chatQueueCap; public float chatMaxQueueAge; public int chatVisibleLines;
|
|
public int[] afkMultipliers; public float afkArrowMoveDuration;
|
|
public int questSlots, closetFirstPurchasePercent; public float x2BoostMultiplier; public int[] questTemplateIndex;
|
|
public List<DailyReward> dailyRewards; public int onlineTicketPerStep; public int[] ticketPacks;
|
|
public int viewRandomMin, viewRandomMax, viewSmallFollower, viewSmallMin; public float viewRerollMin, viewRerollMax;
|
|
}
|
|
|
|
public static class Big
|
|
{
|
|
public static BigInteger P(string s) => string.IsNullOrEmpty(s) ? BigInteger.Zero : BigInteger.Parse(s, CultureInfo.InvariantCulture);
|
|
public static decimal D(string s) => string.IsNullOrEmpty(s) ? 0m : decimal.Parse(s, NumberStyles.Float, CultureInfo.InvariantCulture);
|
|
public static string S(decimal d) => d.ToString(CultureInfo.InvariantCulture);
|
|
public static decimal ToDec(BigInteger b) => b > (BigInteger)decimal.MaxValue ? decimal.MaxValue : (decimal)b;
|
|
public static BigInteger FloorBig(decimal d) => new BigInteger(decimal.Floor(d));
|
|
}
|
|
|
|
/// <summary>로컬라이즈 CSV (Key,English,Russian,Vietnamese,French,German,Japanese,Chinese,Korean)</summary>
|
|
public class Loc
|
|
{
|
|
readonly Dictionary<string, string[]> rows = new Dictionary<string, string[]>();
|
|
public int Column = 8; // Korean
|
|
|
|
public void AddCsv(string text)
|
|
{
|
|
if (string.IsNullOrEmpty(text)) return;
|
|
foreach (var f in ParseCsv(text))
|
|
if (f.Count > 1 && f[0] != "Key") rows[f[0]] = f.ToArray();
|
|
}
|
|
|
|
public string Get(string key, string fallback = null)
|
|
{
|
|
if (key != null && rows.TryGetValue(key, out var r))
|
|
{
|
|
if (Column < r.Length && !string.IsNullOrEmpty(r[Column])) return r[Column];
|
|
if (r.Length > 1 && !string.IsNullOrEmpty(r[1])) return r[1];
|
|
}
|
|
return fallback ?? key;
|
|
}
|
|
|
|
public bool Has(string key) => key != null && rows.ContainsKey(key);
|
|
|
|
static List<List<string>> ParseCsv(string t)
|
|
{
|
|
var all = new List<List<string>>();
|
|
var row = new List<string>();
|
|
var sb = new System.Text.StringBuilder();
|
|
bool q = false;
|
|
for (int i = 0; i < t.Length; i++)
|
|
{
|
|
char c = t[i];
|
|
if (q)
|
|
{
|
|
if (c == '"') { if (i + 1 < t.Length && t[i + 1] == '"') { sb.Append('"'); i++; } else q = false; }
|
|
else sb.Append(c);
|
|
}
|
|
else if (c == '"') q = true;
|
|
else if (c == ',') { row.Add(sb.ToString()); sb.Clear(); }
|
|
else if (c == '\n' || c == '\r')
|
|
{
|
|
if (c == '\r' && i + 1 < t.Length && t[i + 1] == '\n') i++;
|
|
row.Add(sb.ToString()); sb.Clear();
|
|
if (row.Count > 1 || row[0].Length > 0) all.Add(row);
|
|
row = new List<string>();
|
|
}
|
|
else sb.Append(c);
|
|
}
|
|
if (sb.Length > 0 || row.Count > 0) { row.Add(sb.ToString()); all.Add(row); }
|
|
return all;
|
|
}
|
|
}
|
|
|
|
public class Tables
|
|
{
|
|
public const int HouseCount = 6;
|
|
public ItemTable[] Rooms = new ItemTable[HouseCount];
|
|
public ItemTable Skills;
|
|
public ClothesTable Clothes;
|
|
public RankTable Ranks;
|
|
public QuestTable Quests;
|
|
public ChatNameTable ChatNames;
|
|
public ChatCommentTable Comments;
|
|
public ConfigFile Cfg;
|
|
public ExtraCfg X;
|
|
public BattleCfg Battle;
|
|
public Loc Loc = new Loc();
|
|
public string StoryLogicJson, StoryLocaleJson, SubStoryJson, SubStoryLocaleJson;
|
|
|
|
// 파싱 캐시
|
|
public BigInteger[][][] RoomPrice, RoomFollower;
|
|
public BigInteger[][] SkillPrice, SkillFollower;
|
|
public BigInteger[] ClothesPrice, RankFollower, RankReward;
|
|
|
|
public static readonly LevelRow EmptyLevel = new LevelRow { Level = -1, Price = "0", Follower = "0" };
|
|
|
|
/// <param name="read">파일 이름(확장자 제외) → 텍스트. 없으면 null.</param>
|
|
public static Tables Load(Func<string, string> read)
|
|
{
|
|
var t = new Tables();
|
|
for (int h = 0; h < HouseCount; h++) t.Rooms[h] = JsonUtility.FromJson<ItemTable>(read("Room" + (h + 1)));
|
|
t.Skills = JsonUtility.FromJson<ItemTable>(read("Skills"));
|
|
t.Clothes = JsonUtility.FromJson<ClothesTable>(read("Clothes"));
|
|
t.Ranks = JsonUtility.FromJson<RankTable>(read("RankFollower"));
|
|
t.Quests = JsonUtility.FromJson<QuestTable>(read("QuestNormal"));
|
|
t.ChatNames = JsonUtility.FromJson<ChatNameTable>(read("ChatNames"));
|
|
t.Comments = JsonUtility.FromJson<ChatCommentTable>(read("ChatComments_ko") ?? read("ChatComments"));
|
|
t.Cfg = JsonUtility.FromJson<ConfigFile>(read("config"));
|
|
t.X = JsonUtility.FromJson<ExtraCfg>(read("config_extra"));
|
|
t.Battle = JsonUtility.FromJson<BattleCfg>(read("battle"));
|
|
foreach (var n in new[] { "Common", "Gameplay", "Content", "Task", "UI" }) t.Loc.AddCsv(read("loc_" + n));
|
|
t.StoryLogicJson = read("StoryLogic");
|
|
t.StoryLocaleJson = read("StoryLocale_ko");
|
|
t.SubStoryJson = read("SubStory");
|
|
t.SubStoryLocaleJson = read("SubStoryLocale_ko");
|
|
t.Build();
|
|
return t;
|
|
}
|
|
|
|
void Build()
|
|
{
|
|
RoomPrice = new BigInteger[HouseCount][][];
|
|
RoomFollower = new BigInteger[HouseCount][][];
|
|
for (int h = 0; h < HouseCount; h++)
|
|
{
|
|
var items = Rooms[h].Items;
|
|
RoomPrice[h] = new BigInteger[items.Count][];
|
|
RoomFollower[h] = new BigInteger[items.Count][];
|
|
for (int i = 0; i < items.Count; i++)
|
|
{
|
|
var lv = items[i].Levels;
|
|
RoomPrice[h][i] = new BigInteger[lv.Count];
|
|
RoomFollower[h][i] = new BigInteger[lv.Count];
|
|
for (int k = 0; k < lv.Count; k++) { RoomPrice[h][i][k] = Big.P(lv[k].Price); RoomFollower[h][i][k] = Big.P(lv[k].Follower); }
|
|
}
|
|
}
|
|
SkillPrice = new BigInteger[Skills.Items.Count][];
|
|
SkillFollower = new BigInteger[Skills.Items.Count][];
|
|
for (int i = 0; i < Skills.Items.Count; i++)
|
|
{
|
|
var lv = Skills.Items[i].Levels;
|
|
SkillPrice[i] = new BigInteger[lv.Count];
|
|
SkillFollower[i] = new BigInteger[lv.Count];
|
|
for (int k = 0; k < lv.Count; k++) { SkillPrice[i][k] = Big.P(lv[k].Price); SkillFollower[i][k] = Big.P(lv[k].Follower); }
|
|
}
|
|
ClothesPrice = new BigInteger[Clothes.Levels.Count];
|
|
for (int k = 0; k < Clothes.Levels.Count; k++) ClothesPrice[k] = Big.P(Clothes.Levels[k].Price);
|
|
RankFollower = new BigInteger[Ranks.Items.Count];
|
|
RankReward = new BigInteger[Ranks.Items.Count];
|
|
for (int k = 0; k < Ranks.Items.Count; k++) { RankFollower[k] = Big.P(Ranks.Items[k].Follower); RankReward[k] = Big.P(Ranks.Items[k].Reward); }
|
|
}
|
|
|
|
public int RoomItemCount(int house) => Rooms[house].Items.Count;
|
|
public int ItemRowIndex(int house, int type) => Rooms[house].Items.FindIndex(x => x.Index == type);
|
|
public int SkillRowIndex(int type) => Skills.Items.FindIndex(x => x.Index == type);
|
|
public int RankRowIndex(int index) => Ranks.Items.FindIndex(x => x.Index == index);
|
|
public int ClothesRowIndex(int level) => Clothes.Levels.FindIndex(x => x.Level == level);
|
|
public GiftDefRow Gift(int type) => Cfg.gift.giftDefinitions.Find(g => g.Type == type);
|
|
}
|
|
}
|