using System.Collections.Generic; using UnityEngine; using VRShopping.Items; using VRShopping.Player; using VRShopping.UI; namespace VRShopping.Shopping { // 씬 로드 시 현재 씬의 상품 재고를 스캔해서 랜덤 쇼핑 미션과 예산을 생성한다. // - 미션 항목 수: [_minMissionCount, _maxMissionCount] 범위에서 랜덤 (씬 보유 그룹 수로 자동 클램프) // - 항목당 수량: [1, _maxQuantityPerEntry] 범위에서 랜덤 (그룹별 재고 수로 자동 클램프) // - 예산: 그룹별 최저가 기준 최적 비용 × (1 + Random[_budgetMarginMin, _budgetMarginMax]) public class MissionGenerator : MonoBehaviour, ISceneInitializable { [Header("Mission Size")] [SerializeField, Min(1)] private int _minMissionCount = 10; [SerializeField, Min(1)] private int _maxMissionCount = 15; [Header("Quantity Per Entry")] [SerializeField, Min(1)] private int _maxQuantityPerEntry = 5; [Header("Budget Margin (over optimal cost)")] [SerializeField, Range(0f, 1f)] private float _budgetMarginMin = 0.20f; [SerializeField, Range(0f, 1f)] private float _budgetMarginMax = 0.30f; // 테스트용 — 여기에 추가된 ProductGroup은 미션 후보에서 자동 제외 [Header("Excluded Groups (Test)")] [SerializeField] private List _excludedGroups = new List(); [Header("References")] [SerializeField] private ShoppingOrderView _orderView; [SerializeField] private PlayerWallet _wallet; public void OnSceneLoaded() { Generate(); } public void Generate() { if (_orderView == null || _wallet == null) { Debug.LogWarning("[MissionGenerator] 참조 누락 (orderView/wallet)"); return; } // 1. 씬의 모든 상품을 ProductGroup별로 집계 (None, 제외 그룹, ItemData 누락은 제외) var stockByGroup = new Dictionary>(); var allItems = Object.FindObjectsByType(FindObjectsSortMode.None); foreach (var inst in allItems) { var data = inst.ItemDataInfo; if (data == null) continue; if (data.ProductGroup == ProductGroup.None) continue; if (_excludedGroups.Contains(data.ProductGroup)) continue; if (!stockByGroup.TryGetValue(data.ProductGroup, out var list)) { list = new List(); stockByGroup[data.ProductGroup] = list; } list.Add(data); } if (stockByGroup.Count == 0) { Debug.LogWarning("[MissionGenerator] 씬에 유효한 상품이 없습니다"); return; } // 2. 사용 가능한 그룹 풀에서 미션 개수만큼 무작위 선택 var groupPool = new List(stockByGroup.Keys); Shuffle(groupPool); int desiredCount = Random.Range(_minMissionCount, _maxMissionCount + 1); int missionCount = Mathf.Min(desiredCount, groupPool.Count); // 3. 각 그룹별로 (재고로 클램프된) 수량 부여 + 최저가 기준 최적 비용 누적 var entries = new List(missionCount); int optimalCost = 0; for (int i = 0; i < missionCount; i++) { var group = groupPool[i]; var stock = stockByGroup[group]; int maxQty = Mathf.Min(_maxQuantityPerEntry, stock.Count); int qty = Random.Range(1, maxQty + 1); int cheapestPrice = int.MaxValue; foreach (var data in stock) { if (data.FinalPrice < cheapestPrice) cheapestPrice = data.FinalPrice; } entries.Add(new ShoppingOrderEntry(group, qty)); optimalCost += cheapestPrice * qty; } // 4. 런타임 ShoppingOrderList 생성 후 뷰에 주입 var runtimeList = ScriptableObject.CreateInstance(); runtimeList.name = "ShoppingOrderList (Runtime)"; runtimeList.SetEntries(entries); _orderView.SetOrderList(runtimeList); // 5. 예산 책정 (최적 비용 + 20~30% 마진) float margin = Random.Range(_budgetMarginMin, _budgetMarginMax); int budget = Mathf.CeilToInt(optimalCost * (1f + margin)); _wallet.SetBudget(budget); Debug.Log($"[MissionGenerator] 미션 {missionCount}개 / 최적가 {optimalCost:N0} / 예산 {budget:N0} (+{margin * 100f:F0}%)"); } private static void Shuffle(IList list) { for (int i = list.Count - 1; i > 0; i--) { int j = Random.Range(0, i + 1); (list[i], list[j]) = (list[j], list[i]); } } } }