using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.SceneManagement; public sealed class DailyTaskService : MonoBehaviour { public static DailyTaskService Instance { get; private set; } public event Action OnTasksChanged; private readonly Queue pendingEvents = new Queue(); private readonly Dictionary definitionById = new Dictionary(StringComparer.Ordinal); private DailyTaskSaveData saveData; private userTasksPool configuredTaskPool; private int configuredTaskMaxSize = 5; private int configuredRefreshMaxTimes = 3; private bool initialized; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void Bootstrap() { EnsureInstance(); } public static DailyTaskService EnsureInstance() { if (Instance != null) { return Instance; } var host = new GameObject("__daily_task_service"); host.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector; DontDestroyOnLoad(host); Instance = host.AddComponent(); return Instance; } private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.sceneLoaded += OnSceneLoaded; InitializeStorageIfNeeded(); } private void OnDestroy() { if (Instance == this) { SceneManager.sceneLoaded -= OnSceneLoaded; } } public void Configure(userTasksPool taskPool, int taskMaxSize, int refreshMaxTimes) { configuredTaskPool = taskPool; configuredTaskMaxSize = Mathf.Max(1, taskMaxSize); configuredRefreshMaxTimes = Mathf.Max(0, refreshMaxTimes); RebuildDefinitionIndex(); EnsureTodayTasks(); FlushPendingEvents(); NotifyTasksChanged(); } public IReadOnlyList GetActiveTaskViews() { EnsureTodayTasks(); var result = new List(); if (saveData == null || saveData.activeTasks == null) { return result; } for (int i = 0; i < saveData.activeTasks.Count; i++) { var runtimeEntry = saveData.activeTasks[i]; if (runtimeEntry == null || string.IsNullOrEmpty(runtimeEntry.taskID)) { continue; } userTasksPool.TaskDefinition definition; if (!definitionById.TryGetValue(runtimeEntry.taskID, out definition)) { continue; } result.Add(new DailyTaskViewData { definition = definition, runtimeEntry = runtimeEntry }); } return result; } public void ReportEvent(DailyTaskEventData eventData) { InitializeStorageIfNeeded(); if (!IsConfigured()) { pendingEvents.Enqueue(eventData); return; } EnsureTodayTasks(); float accumulatedIncrement = GetAccumulatedIncrement(eventData); if (accumulatedIncrement <= 0f) { return; } float accumulatedValue = AddAccumulatedProgress(eventData.taskType, accumulatedIncrement); bool changed = false; for (int i = 0; i < saveData.activeTasks.Count; i++) { var runtimeEntry = saveData.activeTasks[i]; if (runtimeEntry == null || runtimeEntry.isClaimed) { continue; } userTasksPool.TaskDefinition definition; if (!definitionById.TryGetValue(runtimeEntry.taskID, out definition)) { continue; } if (!IsEventMatch(definition, eventData)) { continue; } float previousProgress = runtimeEntry.progress; bool previousCompleted = runtimeEntry.isCompleted; runtimeEntry.progress = GetInitialProgress(definition, accumulatedValue); runtimeEntry.isCompleted = runtimeEntry.progress >= Mathf.Max(0.0001f, definition.targetValue); if (!Mathf.Approximately(previousProgress, runtimeEntry.progress) || previousCompleted != runtimeEntry.isCompleted) { changed = true; } } if (changed) { Save(); NotifyTasksChanged(); } } public bool TryRefreshTasks() { if (!IsConfigured()) { return false; } EnsureTodayTasks(); if (saveData.refreshUsedCount >= configuredRefreshMaxTimes) { return false; } saveData.refreshUsedCount += 1; GenerateDailyTasks(); Save(); NotifyTasksChanged(); return true; } public int GetRemainingRefreshCount() { EnsureTodayTasks(); return Mathf.Max(0, configuredRefreshMaxTimes - (saveData != null ? saveData.refreshUsedCount : 0)); } public bool TryClaimReward(string taskID, Player_SO playerData, out string failureMessage) { failureMessage = string.Empty; if (string.IsNullOrWhiteSpace(taskID)) { failureMessage = "任务参数无效"; return false; } EnsureTodayTasks(); DailyTaskRuntimeEntry runtimeEntry; userTasksPool.TaskDefinition definition; if (!TryGetTaskPair(taskID, out runtimeEntry, out definition)) { failureMessage = "未找到任务"; return false; } if (!runtimeEntry.isCompleted) { failureMessage = "任务尚未完成"; return false; } if (runtimeEntry.isClaimed) { failureMessage = "奖励已领取"; return false; } if (!TryGrantReward(definition, playerData, out failureMessage)) { return false; } runtimeEntry.isClaimed = true; Save(); NotifyTasksChanged(); return true; } public int ClaimAllAvailableRewards(Player_SO playerData, out string failureMessage) { failureMessage = string.Empty; EnsureTodayTasks(); int claimedCount = 0; var taskIds = new List(); var views = GetActiveTaskViews(); for (int i = 0; i < views.Count; i++) { var runtimeEntry = views[i].runtimeEntry; if (runtimeEntry == null || !runtimeEntry.isCompleted || runtimeEntry.isClaimed) { continue; } taskIds.Add(runtimeEntry.taskID); } for (int i = 0; i < taskIds.Count; i++) { string claimError; if (!TryClaimReward(taskIds[i], playerData, out claimError)) { if (claimedCount <= 0) { failureMessage = claimError; } continue; } claimedCount += 1; } if (claimedCount <= 0 && string.IsNullOrEmpty(failureMessage)) { failureMessage = "暂无可领取奖励"; } return claimedCount; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (string.Equals(scene.name, "UI_UI", StringComparison.OrdinalIgnoreCase)) { ReportEvent(new DailyTaskEventData { taskType = userTasksPool.TaskType.Login, amount = 1f }); } } private void InitializeStorageIfNeeded() { if (initialized) { return; } saveData = DailyTaskSaveService.Load() ?? new DailyTaskSaveData(); initialized = true; } private void EnsureTodayTasks() { InitializeStorageIfNeeded(); if (!IsConfigured()) { return; } string todayKey = GetTodayKey(); if (saveData == null) { saveData = new DailyTaskSaveData(); } bool needRegenerate = !string.Equals(saveData.dateKey, todayKey, StringComparison.Ordinal) || saveData.activeTasks == null || saveData.activeTasks.Count == 0; if (!needRegenerate) { EnsureAccumulatedProgressCompatibility(); return; } saveData.dateKey = todayKey; saveData.refreshUsedCount = 0; saveData.accumulatedProgress = new List(); GenerateDailyTasks(); Save(); } private void GenerateDailyTasks() { if (saveData == null) { saveData = new DailyTaskSaveData(); } saveData.activeTasks = new List(); var candidates = configuredTaskPool.tasks .Where(t => t != null && t.addToTaskPool && !string.IsNullOrWhiteSpace(t.taskID) && t.selectionWeight > 0) .ToList(); if (candidates.Count == 0) { return; } int pickCount = Mathf.Min(configuredTaskMaxSize, candidates.Count); var random = new System.Random(unchecked(GetTodayKey().GetHashCode() + saveData.refreshUsedCount * 397)); var selectedDefinitions = new List(); while (selectedDefinitions.Count < pickCount && candidates.Count > 0) { int totalWeight = candidates.Sum(t => Mathf.Max(1, t.selectionWeight)); int roll = random.Next(0, totalWeight); int cumulative = 0; int selectedIndex = 0; for (int i = 0; i < candidates.Count; i++) { cumulative += Mathf.Max(1, candidates[i].selectionWeight); if (roll < cumulative) { selectedIndex = i; break; } } selectedDefinitions.Add(candidates[selectedIndex]); candidates.RemoveAt(selectedIndex); } for (int i = 0; i < selectedDefinitions.Count; i++) { var definition = selectedDefinitions[i]; float currentProgress = GetInitialProgress(definition, GetAccumulatedProgress(definition.taskType)); saveData.activeTasks.Add(new DailyTaskRuntimeEntry { taskID = definition.taskID, progress = currentProgress, isCompleted = currentProgress >= Mathf.Max(0.0001f, definition.targetValue), isClaimed = false }); } } private bool IsConfigured() { return configuredTaskPool != null && definitionById.Count > 0; } private void RebuildDefinitionIndex() { definitionById.Clear(); if (configuredTaskPool == null || configuredTaskPool.tasks == null) { return; } for (int i = 0; i < configuredTaskPool.tasks.Count; i++) { var definition = configuredTaskPool.tasks[i]; if (definition == null || string.IsNullOrWhiteSpace(definition.taskID)) { continue; } if (!definitionById.ContainsKey(definition.taskID)) { definitionById.Add(definition.taskID, definition); } } } private void EnsureAccumulatedProgressCompatibility() { if (saveData == null) { return; } if (saveData.accumulatedProgress == null) { saveData.accumulatedProgress = new List(); } if (saveData.accumulatedProgress.Count > 0 || saveData.activeTasks == null || saveData.activeTasks.Count == 0) { return; } bool changed = false; for (int i = 0; i < saveData.activeTasks.Count; i++) { var runtimeEntry = saveData.activeTasks[i]; if (runtimeEntry == null || string.IsNullOrWhiteSpace(runtimeEntry.taskID)) { continue; } userTasksPool.TaskDefinition definition; if (!definitionById.TryGetValue(runtimeEntry.taskID, out definition)) { continue; } AddAccumulatedProgress(definition.taskType, Mathf.Max(0f, runtimeEntry.progress)); changed = true; } if (changed) { Save(); } } private void FlushPendingEvents() { if (!IsConfigured()) { return; } while (pendingEvents.Count > 0) { ReportEvent(pendingEvents.Dequeue()); } } private static bool IsEventMatch(userTasksPool.TaskDefinition definition, DailyTaskEventData eventData) { return definition != null && definition.taskType == eventData.taskType; } private float AddAccumulatedProgress(userTasksPool.TaskType taskType, float amount) { if (saveData == null) { saveData = new DailyTaskSaveData(); } if (saveData.accumulatedProgress == null) { saveData.accumulatedProgress = new List(); } int taskTypeValue = (int)taskType; for (int i = 0; i < saveData.accumulatedProgress.Count; i++) { var entry = saveData.accumulatedProgress[i]; if (entry == null || entry.taskType != taskTypeValue) { continue; } entry.progress = Mathf.Max(0f, entry.progress + amount); return entry.progress; } var newEntry = new DailyTaskAccumulatedProgress { taskType = taskTypeValue, progress = Mathf.Max(0f, amount) }; saveData.accumulatedProgress.Add(newEntry); return newEntry.progress; } private float GetAccumulatedProgress(userTasksPool.TaskType taskType) { if (saveData == null || saveData.accumulatedProgress == null) { return 0f; } int taskTypeValue = (int)taskType; for (int i = 0; i < saveData.accumulatedProgress.Count; i++) { var entry = saveData.accumulatedProgress[i]; if (entry == null || entry.taskType != taskTypeValue) { continue; } return Mathf.Max(0f, entry.progress); } return 0f; } private static float GetInitialProgress(userTasksPool.TaskDefinition definition, float accumulatedValue) { if (definition == null) { return 0f; } return Mathf.Min(Mathf.Max(0f, accumulatedValue), Mathf.Max(0.0001f, definition.targetValue)); } private bool TryGetTaskPair(string taskID, out DailyTaskRuntimeEntry runtimeEntry, out userTasksPool.TaskDefinition definition) { runtimeEntry = null; definition = null; if (saveData == null || saveData.activeTasks == null) { return false; } for (int i = 0; i < saveData.activeTasks.Count; i++) { var candidate = saveData.activeTasks[i]; if (candidate == null || !string.Equals(candidate.taskID, taskID, StringComparison.Ordinal)) { continue; } userTasksPool.TaskDefinition matchedDefinition; if (!definitionById.TryGetValue(candidate.taskID, out matchedDefinition)) { return false; } runtimeEntry = candidate; definition = matchedDefinition; return true; } return false; } private static bool TryGrantReward(userTasksPool.TaskDefinition definition, Player_SO playerData, out string failureMessage) { failureMessage = string.Empty; if (definition == null) { failureMessage = "任务定义无效"; return false; } if (definition.rewardAmount <= 0) { failureMessage = "奖励数量无效"; return false; } switch (definition.rewardType) { case userTasksPool.RewardType.coins: { var wallet = PlayerEconomyLedger.EnsureInstance(); if (playerData != null) { wallet.AttachPlayerData(playerData); } wallet.AddCoins(definition.rewardAmount); return true; } case userTasksPool.RewardType.expBottle: { var bottleLedger = ExpBottleLedger.EnsureInstance(); if (playerData != null) { bottleLedger.AttachPlayerData(playerData); } bottleLedger.Add(definition.rewardExpBottleKind, definition.rewardAmount); return true; } case userTasksPool.RewardType.dushMaterial: { var materialLedger = DushMaterialLedger.EnsureInstance(); if (playerData != null) { materialLedger.AttachPlayerData(playerData); } materialLedger.Add(definition.rewardDushMaterialKind, definition.rewardAmount); return true; } default: failureMessage = "暂不支持该奖励类型"; return false; } } private static float GetAccumulatedIncrement(DailyTaskEventData eventData) { switch (eventData.taskType) { case userTasksPool.TaskType.TotalScore: case userTasksPool.TaskType.SpendCoins: case userTasksPool.TaskType.GameDuration: return Mathf.Max(0f, eventData.amount); case userTasksPool.TaskType.Login: case userTasksPool.TaskType.PlaySong: case userTasksPool.TaskType.FullCombo: case userTasksPool.TaskType.UseItem: return Mathf.Max(1f, eventData.amount); default: return Mathf.Max(0f, eventData.amount); } } private static string GetTodayKey() { return DateTime.Now.ToString("yyyy-MM-dd"); } private void Save() { DailyTaskSaveService.Save(saveData); } private void NotifyTasksChanged() { if (OnTasksChanged != null) { OnTasksChanged(); } } }