A game about forced loneliness, made by TACStudios
1using System;
2using Unity.Multiplayer.Center.Analytics;
3using Unity.Multiplayer.Center.Common;
4using Unity.Multiplayer.Center.Questionnaire;
5using Unity.Multiplayer.Center.Recommendations;
6using Unity.Multiplayer.Center.Window.UI;
7using Unity.Multiplayer.Center.Window.UI.RecommendationView;
8using UnityEditor;
9using UnityEngine;
10using UnityEngine.UIElements;
11
12namespace Unity.Multiplayer.Center.Window
13{
14 internal class RecommendationTabView : ITabView
15 {
16 QuestionnaireView m_QuestionnaireView;
17 RecommendationView m_RecommendationView;
18
19 RecommendationViewBottomBar m_BottomBarView;
20
21 bool m_IsVisible;
22 bool m_ShouldRefresh = true;
23
24 [SerializeField]
25 PreReleaseHandling m_PreReleaseHandling = new();
26
27 [field: SerializeField] // Marked as redundant by Rider, but it is not.
28 public string Name { get; private set; }
29
30 public VisualElement RootVisualElement { get; set; }
31 public IMultiplayerCenterAnalytics MultiplayerCenterAnalytics { get; set; }
32
33 // Access to QuestionnaireView for testing purposes
34 internal QuestionnaireView QuestionnaireView => m_QuestionnaireView;
35
36 public RecommendationTabView(string name = "Recommendation")
37 {
38 Name = name;
39 m_PreReleaseHandling.OnAllChecksFinished += PatchData;
40 m_PreReleaseHandling.CheckForUpdates();
41 Undo.undoRedoPerformed += OnUndoRedoPerformed;
42 }
43
44 void OnUndoRedoPerformed()
45 {
46 UserChoicesObject.instance.Save();
47 m_QuestionnaireView?.Refresh();
48 UpdateRecommendation(keepSelection:true);
49 }
50
51 public void Clear()
52 {
53 Undo.undoRedoPerformed -= OnUndoRedoPerformed;
54 m_RecommendationView?.Clear();
55 m_QuestionnaireView?.Clear();
56 RootVisualElement?.Clear();
57 m_QuestionnaireView = null;
58 m_RecommendationView = null;
59 }
60
61 public void SetVisible(bool visible)
62 {
63 RootVisualElement.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None;
64 m_IsVisible = visible;
65 }
66
67 public void Refresh()
68 {
69 Debug.Assert(MultiplayerCenterAnalytics != null, "MultiplayerCenterAnalytics != null");
70 RefreshPreReleaseHandling();
71 if (!m_ShouldRefresh && RootVisualElement.childCount > 0) return;
72 CreateStandardView();
73 m_ShouldRefresh = false;
74 }
75
76 void RefreshPreReleaseHandling()
77 {
78 if (!m_PreReleaseHandling.IsReady)
79 {
80 m_PreReleaseHandling.OnAllChecksFinished += PatchData;
81 m_PreReleaseHandling.CheckForUpdates();
82 }
83 else
84 {
85 m_PreReleaseHandling.PatchRecommenderSystemData();
86 }
87 }
88
89 void CreateStandardView()
90 {
91 Clear();
92 Undo.undoRedoPerformed -= OnUndoRedoPerformed;
93 Undo.undoRedoPerformed += OnUndoRedoPerformed;
94 MigrateUserChoices();
95
96 // We need this because Bottom bar is a part of the Recommendations Tab and it should always stay
97 // at the bottom of the view. So we need to make sure that the root tab element is always 100% height.
98 RootVisualElement.style.height = Length.Percent(100);
99
100 var horizontalContainer = new TwoPaneSplitView(0, 250, TwoPaneSplitViewOrientation.Horizontal);
101 horizontalContainer.AddToClassList(StyleClasses.MainSplitView);
102 horizontalContainer.name = "recommendation-tab-split-view";
103
104 // This is used to make sure the left side does not grow to 100% as this is what would happen by default.
105 // It feels not 100% correct. But it seems to be the only way to match the height of the 2 sides with how
106 // our views are build currently.
107 horizontalContainer.contentContainer.style.position = Position.Relative;
108
109 m_QuestionnaireView = new QuestionnaireView(QuestionnaireObject.instance.Questionnaire);
110 m_QuestionnaireView.OnQuestionnaireDataChanged += HandleQuestionnaireDataChanged;
111 m_QuestionnaireView.OnPresetSelected += OnPresetSelected;
112 m_QuestionnaireView.Root.AddToClassList(StyleClasses.MainSplitViewLeft);
113 horizontalContainer.Add(m_QuestionnaireView.Root);
114
115 m_RecommendationView = new RecommendationView();
116 m_RecommendationView.Root.AddToClassList(StyleClasses.MainSplitViewRight);
117 horizontalContainer.Add(m_RecommendationView.Root);
118 RootVisualElement.Add(horizontalContainer);
119
120 m_BottomBarView = new RecommendationViewBottomBar(MultiplayerCenterAnalytics);
121 m_RecommendationView.OnPackageSelectionChanged +=
122 () => m_BottomBarView.UpdatePackagesToInstall(m_RecommendationView.CurrentRecommendation, m_RecommendationView.AllPackages);
123 RootVisualElement.Add(m_BottomBarView);
124 UpdateRecommendation(keepSelection: true);
125
126 m_BottomBarView.SetInfoTextForCheckingPackages(!m_PreReleaseHandling.IsReady);
127 }
128
129 void HandleQuestionnaireDataChanged()
130 {
131 UpdateRecommendation(keepSelection: false);
132 }
133
134 static void MigrateUserChoices()
135 {
136 var questionnaire = QuestionnaireObject.instance.Questionnaire;
137 var userChoices = UserChoicesObject.instance;
138
139 // make sure the version of the questionnaire is the same as the one in the user choices.
140 if (questionnaire.Version != userChoices.QuestionnaireVersion && userChoices.UserAnswers.Answers.Count > 0)
141 {
142 Logic.MigrateUserChoices(questionnaire, userChoices);
143 }
144 }
145
146 void UpdateRecommendation(bool keepSelection)
147 {
148 var questionnaire = QuestionnaireObject.instance.Questionnaire;
149 var userChoices = UserChoicesObject.instance;
150
151 var errors = Logic.ValidateAnswers(questionnaire, userChoices.UserAnswers);
152 foreach (var error in errors)
153 {
154 Debug.LogError(error);
155 }
156
157 var recommendation = userChoices.Preset == Preset.None? null
158 : RecommenderSystem.GetRecommendation(questionnaire, userChoices.UserAnswers);
159 m_PreReleaseHandling.PatchPackages(recommendation);
160 if (keepSelection)
161 {
162 RecommendationUtils.ApplyPreviousSelection(recommendation, userChoices.SelectedSolutions);
163 }
164 else if (recommendation != null) // we only send the event if there is a recommendation and it is a new one
165 {
166 MultiplayerCenterAnalytics.SendRecommendationEvent(userChoices.UserAnswers, userChoices.Preset);
167 }
168
169 m_RecommendationView.UpdateRecommendation(recommendation, m_PreReleaseHandling);
170 m_BottomBarView.UpdatePackagesToInstall(recommendation, m_RecommendationView.AllPackages);
171 }
172
173 void PatchData()
174 {
175 m_PreReleaseHandling.PatchRecommenderSystemData();
176 m_PreReleaseHandling.OnAllChecksFinished -= PatchData;
177 m_ShouldRefresh = true;
178 if(m_IsVisible)
179 Refresh();
180 }
181
182 void OnPresetSelected(Preset preset)
183 {
184 var (resultAnswerData, recommendation) = Logic.ApplyPresetToAnswerData(
185 UserChoicesObject.instance.UserAnswers, preset, QuestionnaireObject.instance.Questionnaire);
186
187 UserChoicesObject.instance.UserAnswers = resultAnswerData;
188 UserChoicesObject.instance.Save();
189
190 if (recommendation != null)
191 MultiplayerCenterAnalytics.SendRecommendationEvent(resultAnswerData, preset);
192
193 m_QuestionnaireView.Refresh();
194 m_RecommendationView.UpdateRecommendation(recommendation, m_PreReleaseHandling);
195 }
196 }
197}