using System; using System.Numerics; // 의상: 공통 옷장 레벨(Clothes 72단계 · StatRate = 전역 배율 · 장착 의상과 무관) + 의상 32종 해금·장착 — systems.md §5 namespace VS.Sim { public static class ClosetUnlock { public const int Default = 0, Free = 1, Ticket = 2, InEvent = 4; } public partial class Game { public ClothesRow ClosetLevelRow(int level) { int i = T.ClothesRowIndex(level); return i >= 0 ? T.Clothes.Levels[i] : null; } public bool ClosetMaxed => S.closetLevel >= ClosetMaxLevel; public ClothesRow ClosetNext => ClosetMaxed ? null : ClosetLevelRow(S.closetLevel + 1); /// GetUpgradeQuote RVA 0x23EAE14 — 할인가 · 첫 구매는 10% (FirstPurchasePricePercent) public BigInteger ClosetNextPrice() { var n = ClosetNext; if (n == null) return BigInteger.Zero; var p = DiscountedPrice(T.ClothesPrice[T.ClothesRowIndex(n.Level)]); if (!S.isBuyCloset && S.closetLevel < 1) p = p * T.X.closetFirstPurchasePercent / 100; return p; } /// IsRankLocked RVA 0x23EAD74 public bool ClosetRankOk() { var n = ClosetNext; return n != null && n.RankDependency <= S.rankIndex; } /// TryUpgrade RVA 0x23EB404 (1단계) public BuyResult UpgradeCloset() { var n = ClosetNext; if (n == null) return BuyResult.Max; if (ClosetUpgrading) return BuyResult.Busy; if (!ClosetRankOk()) return BuyResult.Locked; var price = ClosetNextPrice(); if (price > Coin) return BuyResult.NoMoney; SpendCoin(price); int before = S.closetLevel; S.closetLevel = n.Level; if (n.UpgradeTime > 0f) S.closetUpgradeEnd = Now().AddSeconds(n.UpgradeTime).Ticks; S.isBuyCloset = true; MarkNewFreeOutfits(before, S.closetLevel); OnClosetUpgraded(); return BuyResult.Ok; } public void SkipClosetUpgrade() { S.closetUpgradeEnd = 0; } // ---- 의상(outfit) — IsUnlocked RVA 0x23ECD0C public OutfitRow Outfit(int type) => T.Cfg.closet.outfits.Find(o => o.Type == type); public bool OutfitUnlocked(int type) { if (type == 1000) return true; var o = Outfit(type); if (o == null) return false; switch (o.UnlockType) { case ClosetUnlock.Default: return true; case ClosetUnlock.Free: return S.closetLevel >= o.UnlockLevel; default: return S.closetUnlocked.Contains(type); } } /// GetOutfitTicketPrice RVA 0x23ED174 (광고 차감 = 클론에 없음) public int OutfitTicketPrice(OutfitRow o) => Math.Max(o.TicketPrice, 1); /// IsTicketOutfitRankLocked RVA 0x23ED230 public bool OutfitRankOk(OutfitRow o) => o.VisibilityRank <= 0 || S.rankIndex >= o.VisibilityRank; void MarkNewFreeOutfits(int before, int after) { foreach (var o in T.Cfg.closet.outfits) if (o.UnlockType == ClosetUnlock.Free && o.UnlockLevel > before && o.UnlockLevel <= after && !S.closetNew.Contains(o.Type)) S.closetNew.Add(o.Type); } /// TryStageTicketPurchase RVA 0x23ED45C public BuyResult UnlockTicketOutfit(int type) { var o = Outfit(type); if (o == null || o.UnlockType != ClosetUnlock.Ticket) return BuyResult.Locked; if (OutfitUnlocked(type)) return BuyResult.Max; if (!OutfitRankOk(o)) return BuyResult.Locked; int price = OutfitTicketPrice(o); if (S.ticket < price) return BuyResult.NoMoney; S.ticket -= price; S.closetUnlocked.Add(type); if (!S.closetNew.Contains(type)) S.closetNew.Add(type); return BuyResult.Ok; } /// 일일 보상 의상 (TryGrantEventOutfit) public void GrantEventOutfit(int type) { if (!S.closetUnlocked.Contains(type)) { S.closetUnlocked.Add(type); S.closetNew.Add(type); } } public bool EquipOutfit(int type) { if (!OutfitUnlocked(type)) return false; S.closetCurrent = type; S.closetNew.Remove(type); return true; } } }