using System; using System.Collections.Generic; using Unity.Multiplayer.Center.Questionnaire; using UnityEngine; namespace Unity.Multiplayer.Center.Recommendations { /// /// Contains all the architectural options that we offer to users, with a score specific to their answers. /// This is the data that we show in the UI. /// [Serializable] internal class RecommendationViewData { /// /// NGO or N4E or Other /// public RecommendedSolutionViewData[] NetcodeOptions; /// /// Client hosted / dedicated server. /// It comes with UGS services that we might or might not recommend /// public RecommendedSolutionViewData[] ServerArchitectureOptions; } /// /// For each selection (netcode, hosting) model, there is a specific set of recommended packages /// This class stores this information and provides access to it. /// [Serializable] internal class SolutionsToRecommendedPackageViewData { [SerializeField] RecommendedPackageViewDataArray[] m_Packages; public SolutionSelection[] Selections; // Because we cannot serialize two-dimensional arrays [Serializable] internal struct RecommendedPackageViewDataArray { public RecommendedPackageViewData[] Packages; } public SolutionsToRecommendedPackageViewData(SolutionSelection[] selections, RecommendedPackageViewData[][] packages) { Debug.Assert(selections.Length == packages.Length, "Selections and packages must have the same length"); Selections = selections; m_Packages = new RecommendedPackageViewDataArray[packages.Length]; for (var i = 0; i < packages.Length; i++) { m_Packages[i] = new RecommendedPackageViewDataArray {Packages = packages[i]}; } } public RecommendedPackageViewData[] GetPackagesForSelection(PossibleSolution netcode, PossibleSolution hosting) { return GetPackagesForSelection(new SolutionSelection(netcode, hosting)); } public RecommendedPackageViewData[] GetPackagesForSelection(SolutionSelection selection) { var index = Array.IndexOf(Selections, selection); return index < 0 ? Array.Empty() : m_Packages[index].Packages; } } /// /// Base fields for things that we can recommend (typically a package or a solution). /// [Serializable] internal class RecommendedItemViewData { /// /// How much we want to recommend this item. With the score, this is what is modified by the recommender system. /// The architecture option with the best score will be marked as MainArchitectureChoice, the other as SecondArchitectureChoice, /// which enables us to highlight the recommended option. /// public RecommendationType RecommendationType; /// /// Item is part of the selection, because it was preselected or user selected it. /// public bool Selected; /// /// Optional: reason why this recommendation was made /// public string Reason; /// /// Url to feature documentation /// public string DocsUrl; /// /// Recommendation is installed and direct dependency /// public bool IsInstalledAsProjectDependency; /// /// Installed version number of a package /// public string InstalledVersion; } /// /// Architectural solution (netcode or server architecture) that we offer. It comes with an optional main package and /// associated recommended packages. /// [Serializable] internal class RecommendedSolutionViewData : RecommendedItemViewData { public string Title; public PossibleSolution Solution; /// /// How much of a match is this item. Computed by recommender system based on answers. /// public float Score; /// /// The main package to install for this solution (note that this might be null, e.g. for client hosted game) /// public RecommendedPackageViewData MainPackage; public string WarningString; public RecommendedSolutionViewData(RecommenderSystemData data, RecommendedSolution solution, RecommendationType type, Scoring scoring, Dictionary installedPackageDictionary) { if (!string.IsNullOrEmpty(solution.MainPackageId)) { var mainPackageDetails = data.PackageDetailsById[solution.MainPackageId]; DocsUrl = string.IsNullOrEmpty(solution.DocUrl) ? mainPackageDetails.DocsUrl : solution.DocUrl; if (installedPackageDictionary.ContainsKey(solution.MainPackageId)) { InstalledVersion = installedPackageDictionary[solution.MainPackageId]; IsInstalledAsProjectDependency = PackageManagement.IsDirectDependency(solution.MainPackageId); } MainPackage = new RecommendedPackageViewData(mainPackageDetails, type, InstalledVersion); } else { DocsUrl = solution.DocUrl; } RecommendationType = type; Title = solution.Title; Reason = scoring?.GetReasonString(); Score = scoring?.TotalScore ?? 0f; Selected = RecommendationType.IsRecommendedSolution(); Solution = solution.Type; } } /// /// Single package that is part of a recommendation. /// [Serializable] internal class RecommendedPackageViewData : RecommendedItemViewData { public string PackageId; public string Name; public string PreReleaseVersion; /// /// A short description of the feature. /// public string ShortDescription = "Short description not added yet"; public RecommendedPackageViewData(PackageDetails details, RecommendationType type, string installedVersion=null, string reason = null) { RecommendationType = type; PackageId = details.Id; Name = details.Name; PreReleaseVersion = details.PreReleaseVersion; Selected = type.IsRecommendedPackage(); ShortDescription = details.ShortDescription; Reason = reason; DocsUrl = details.DocsUrl; InstalledVersion = installedVersion; IsInstalledAsProjectDependency = installedVersion != null && PackageManagement.IsDirectDependency(PackageId); } public RecommendedPackageViewData(PackageDetails details, RecommendedPackage recommendation, string installedVersion=null) : this(details, recommendation.Type, installedVersion, recommendation.Reason) { } } }