using System; using System.Collections.Generic; using System.Globalization; using Logic.Multilingual; using UnityEngine; public enum QuestionnaireQuestionType { Open = 0, SingleChoice = 1, MultipleChoice = 2 } public enum QuestionnaireStatus { Featured = 0, LongTerm = 1, Expired = 2, Hidden = 3 } [Serializable] [CreateAssetMenu(fileName = "QuestionnaireDataAssets", menuName = "TH1 Game Data/Questionnaire Data Asset")] public class QuestionnaireDataAssets : ScriptableObject { public string DefaultQuestionnaireId = "player-feedback-2026-06"; [MultilingualField] public string MenuButtonText; public List QuestionnaireList = new List(); [NonSerialized] private Dictionary _questionnaireDict; public QuestionnaireInfo GetDefaultQuestionnaire() { if (GetQuestionnaireInfo(DefaultQuestionnaireId, out var info)) return info; return QuestionnaireList != null && QuestionnaireList.Count > 0 ? QuestionnaireList[0] : null; } public List GetVisibleQuestionnaires(DateTime? nowUtc = null) { var result = new List(); if (QuestionnaireList == null) return result; var now = nowUtc ?? DateTime.UtcNow; foreach (var info in QuestionnaireList) { if (info == null || !info.IsVisibleAt(now)) continue; result.Add(info); } result.Sort(CompareQuestionnaire); return result; } public bool GetQuestionnaireInfo(string questionnaireId, out QuestionnaireInfo info) { EnsureDict(); if (!string.IsNullOrEmpty(questionnaireId) && _questionnaireDict.TryGetValue(questionnaireId, out info)) { return true; } info = null; return false; } private void EnsureDict() { if (_questionnaireDict != null && QuestionnaireList != null && _questionnaireDict.Count == QuestionnaireList.Count) { return; } _questionnaireDict = new Dictionary(); if (QuestionnaireList == null) return; foreach (var info in QuestionnaireList) { if (info == null || string.IsNullOrEmpty(info.QuestionnaireId)) continue; _questionnaireDict[info.QuestionnaireId] = info; } } private static int CompareQuestionnaire(QuestionnaireInfo left, QuestionnaireInfo right) { var statusCompare = GetStatusRank(left?.Status ?? QuestionnaireStatus.Hidden) .CompareTo(GetStatusRank(right?.Status ?? QuestionnaireStatus.Hidden)); if (statusCompare != 0) return statusCompare; var priorityCompare = (right?.Priority ?? 0).CompareTo(left?.Priority ?? 0); if (priorityCompare != 0) return priorityCompare; return string.Compare(left?.QuestionnaireId, right?.QuestionnaireId, StringComparison.Ordinal); } private static int GetStatusRank(QuestionnaireStatus status) { return status switch { QuestionnaireStatus.Featured => 0, QuestionnaireStatus.LongTerm => 1, QuestionnaireStatus.Expired => 2, _ => 3 }; } } [Serializable] public class QuestionnaireInfo { public string QuestionnaireId; public int Revision = 1; public QuestionnaireStatus Status = QuestionnaireStatus.LongTerm; public int Priority; public string StartAtUtc; public string EndAtUtc; public bool AllowMultipleSubmissions = true; public int MaxSubmissionCount; public int MinSubmitIntervalHours; public bool CanViewHistory = true; public bool CanSubmitAfterExpired; public QuestionnaireTargetRules TargetRules = new QuestionnaireTargetRules(); [MultilingualField] public string Title; [MultilingualField] public string Description; [MultilingualField] public string StartButtonText; [MultilingualField] public string SubmitButtonText; [MultilingualField] public string ResubmitButtonText; [MultilingualField] public string CloseButtonText; [MultilingualField] public string SubmittedMessage; [MultilingualField] public string UploadingMessage; [MultilingualField] public string UploadSuccessMessage; [MultilingualField] public string UploadFailedMessage; [MultilingualField] public string UploadAuthFailedMessage; [MultilingualField] public string RequiredMessage; [MultilingualField] public string RefillHintText; [MultilingualField] public string SaveFailedMessage; public List Questions = new List(); public QuestionnaireStatus GetEffectiveStatus(DateTime utcNow) { if (Status == QuestionnaireStatus.Hidden) return QuestionnaireStatus.Hidden; if (Status == QuestionnaireStatus.Expired) return QuestionnaireStatus.Expired; if (HasEnded(utcNow) && !CanSubmitAfterExpired) return QuestionnaireStatus.Expired; return Status; } public bool IsVisibleAt(DateTime utcNow) { if (Status == QuestionnaireStatus.Hidden) return false; if (!HasStarted(utcNow)) return false; return true; } public bool CanSubmitAt(DateTime utcNow) { if (Status == QuestionnaireStatus.Hidden) return false; if (Status == QuestionnaireStatus.Expired && !CanSubmitAfterExpired) return false; if (!HasStarted(utcNow)) return false; if (HasEnded(utcNow) && !CanSubmitAfterExpired) return false; return true; } private bool HasStarted(DateTime utcNow) { return !TryParseUtc(StartAtUtc, out var startAt) || utcNow >= startAt; } private bool HasEnded(DateTime utcNow) { return TryParseUtc(EndAtUtc, out var endAt) && utcNow > endAt; } private static bool TryParseUtc(string raw, out DateTime value) { value = default; if (string.IsNullOrWhiteSpace(raw)) return false; return DateTime.TryParse(raw, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out value); } } [Serializable] public class QuestionnaireTargetRules { public List VersionWhitelist = new List(); public List PlatformWhitelist = new List(); public List LanguageWhitelist = new List(); public bool SteamOnly; public bool BetaOnly; public int MinFinishedMatchCount; } [Serializable] public class QuestionnaireQuestionInfo { public string QuestionId; public QuestionnaireQuestionType QuestionType; [MultilingualField] public string Title; [MultilingualField] public string Hint; public bool Required; public int MaxSelectCount; public List Options = new List(); } [Serializable] public class QuestionnaireOptionInfo { public string OptionId; [MultilingualField] public string Text; }