· 3 years ago · Sep 08, 2022, 10:10 PM
1using System.Collections;
2using System.Collections.Generic;
3using UnityEngine;
4using System;
5
6using MoreMountains.Tools;
7using MoreMountains.TopDownEngine;
8
9
10namespace KazePashaMSK
11{
12 /// <summary>
13 /// This class is responsible for the operation of the dialog system and should be attached to the gameobject in scene
14 /// </summary>
15 public class DialogueManager : MMSingleton<DialogueManager>
16 {
17 [Tooltip("link to component MstItems in folder ExcellImporter")]
18 [SerializeField] MstItems MstItems;
19
20 [Tooltip("dialog swithing time")]
21 [SerializeField] public float TransitionTime = 0.2f;
22
23 // public link to player's gameobject, using in dialogue commands
24 [HideInInspector] public GameObject Player;
25
26 // public link to player's animator gameobject, using in dialogue commands
27 [HideInInspector] public Animator PlayerAnimator;
28
29 // public link to dialogue zones on scene
30 [SerializeField] private DialogueZoneChild dialogueZoneChild;
31
32 protected enum DialogueStates { Inactive, Playing, Transitions }
33
34 // current dialogue state
35 protected DialogueStates CurrentDialogueState = DialogueStates.Inactive;
36
37 // this value is cached during the start of the dialog so that the update method does not refer to this field in each frame
38 protected float _currentDialogueDuration;
39
40 // current dialogue playing timer
41 protected float _playingTimer = 0;
42
43 // current transition timer
44 protected float _transitionsTimer = 0;
45
46 // in this dictionary stores all key-value pairs "id dialogue" string and instane of "Dialogue" class
47 protected Dictionary<string, Dialogue> _allDialoguesDictionary = new();
48
49 // this is the current queue of dialogs that will be played in the future. The concept of a FIFO queue is taken, it's postulated that a dialog with index 0 is always played,
50 // it's also postulated that the dialogs are arranged in the order of non-exagerration of the "dialog weight" field, and the last element in this list has the lowest priority.
51 // When a new items is added to this list , all items in the queue that have a weight lower than the one being added will be deleted. The list data structure is used because
52 // it's necessary to remove elements from it.
53 protected List<Dialogue> _queueDialogue = new();
54
55 // current dialogue is based on the structure principle FIFO
56 protected Dialogue CurrentDialogue { get { return _queueDialogue[0]; } }
57
58
59 protected virtual void Start()
60 {
61 //Initialization(); //release
62 StartCoroutine(OnInitialization()); // script level manager have priority of execution below the current class, and initialization of player's list in it occurs later
63 // than accessing it, in the assembly, the order of execution should be changed
64 }
65
66
67 private IEnumerator OnInitialization()
68 {
69 yield return new WaitForSeconds(1);
70 Initialization();
71 //yield return new WaitForSeconds(6);
72 TestSystem();
73 }
74
75 /// <summary>
76 /// Initialization of the necessary components, parsing of row data from excel
77 /// </summary>
78 private void Initialization()
79 {
80 Player = LevelManager.Instance.Players[0].gameObject;
81 PlayerAnimator = Player.GetComponent<Animator>();
82
83 ParseExcelData();
84 }
85
86 /// <summary>
87 /// Public API to add a new dialogue with validate for uniquess "id" key
88 /// </summary>
89 /// <param name="dialogue"> </param>
90 public void AddNewDialogue(Dialogue dialogue)
91 {
92 if (_allDialoguesDictionary.ContainsKey(dialogue.Id))
93 {
94 throw new Exception($"the dialog's id {dialogue.Id} is not unique", new ArgumentException());
95 }
96 else
97 {
98 InternalAddNewDialogue(dialogue);
99 }
100 }
101
102 /// <summary>
103 /// Add new dialogue to dialogue's dictionary
104 /// </summary>
105 /// <param name="dialogue"></param>
106 private void InternalAddNewDialogue(Dialogue dialogue)
107 {
108 _allDialoguesDictionary.Add(dialogue.Id, dialogue);
109 }
110
111 /// <summary>
112 /// Public API to add multiple dialogs to the queue at once
113 /// </summary>
114 /// <param name="dialogues"></param>
115 public virtual void AddDialoguesInQueue(List<string> dialogues)
116 {
117 foreach (string dialogueId in dialogues)
118 {
119 PlayDialogue(dialogueId);
120 }
121 }
122
123 protected virtual void Update()
124 {
125 switch (CurrentDialogueState)
126 {
127 case DialogueStates.Inactive:
128 break;
129 case DialogueStates.Playing:
130 _playingTimer += Time.deltaTime;
131 if (_playingTimer >= _currentDialogueDuration)
132 {
133 StopDialogue();
134 }
135 break;
136 case DialogueStates.Transitions:
137 _transitionsTimer += Time.deltaTime;
138 if (_transitionsTimer >= TransitionTime)
139 {
140 StartDialogue();
141 _transitionsTimer = 0;
142 }
143 break;
144 }
145 }
146
147 /// <summary>
148 /// Parsing row data from excel to dialog instaces and adding them to the dictionary
149 /// </summary>
150 protected virtual void ParseExcelData()
151 {
152 if (MstItems == null)
153 {
154 throw new Exception("MstItems is null", new NullReferenceException());
155 }
156
157 foreach (MstItemEntity mstItem in MstItems.Entities)
158 {
159 // if field "id" is not unique
160 if (_allDialoguesDictionary.ContainsKey(mstItem.Id))
161 {
162 throw new Exception($"the dialog's id {mstItem.Id} is not unique", new ArgumentException());
163 }
164 List<ModelJson> startJson = ParseStringToListJsons(mstItem.StartCommands);
165 List<ModelJson> stopJson = ParseStringToListJsons(mstItem.StopCommands);
166 SequenceDialogueCommand startCommand = CreateSequence(startJson);
167 SequenceDialogueCommand stopCommand = CreateSequence(stopJson);
168 Dialogue dialogue = new Dialogue(mstItem.Id, mstItem.Message, mstItem.Duration, mstItem.Weight, startCommand, stopCommand);
169 InternalAddNewDialogue(dialogue);
170 }
171 }
172
173 /// <summary>
174 /// Parsing a row containing multiple jsons separarted via ';' and recording values from them to list
175 /// </summary>
176 /// <param name="stringToParse"> a row containing multiple jsons separarted via ';' </param>
177 /// <returns> data for futher processing in the desired format </returns>
178 protected List<ModelJson> ParseStringToListJsons(string stringToParse)
179 {
180 List<ModelJson> jsons = new();
181 string[] strArray = stringToParse.Split(';');
182 for (int i = 0; i < strArray.Length; i++)
183 {
184 if (strArray[i].Length > 10) // the lenght of the string containing json is actually more than 10, this is a condition rather for gaps that may remain in the excel
185 {
186 ModelJson json = JsonUtility.FromJson<ModelJson>(strArray[i]);
187 jsons.Add(json);
188 }
189 }
190
191 return jsons;
192 }
193
194 /// <summary>
195 /// Recognition of the type of command and it's actual creation and adding to SequenceDialogueCommand
196 /// </summary>
197 /// <param name="jsonsList"></param>
198 /// <returns></returns>
199 protected virtual SequenceDialogueCommand CreateSequence(List<ModelJson> jsonsList)
200 {
201 SequenceDialogueCommand sequence = new();
202 foreach (ModelJson json in jsonsList)
203 {
204 string typeCommand = json.type;
205 string parametr = json.parameters;
206 switch (typeCommand)
207 {
208 case "NoAction":
209 sequence.AddDialogueCommandToSequence(new NoActionCommand());
210 break;
211 case "DisablePlayerControl":
212 sequence.AddDialogueCommandToSequence(new DisablePlayerControlCommand());
213 break;
214 case "EnablePlayerControl":
215 sequence.AddDialogueCommandToSequence(new ReturnPlayerControlCommand());
216 break;
217 case "DisableAIControl":
218 sequence.AddDialogueCommandToSequence(new DisableAllAiControlCommand());
219 break;
220 case "EnableAIControl":
221 sequence.AddDialogueCommandToSequence(new ReturnAllAiControlCommand());
222 break;
223 case "SwitchCamera":
224 sequence.AddDialogueCommandToSequence(new SwitchCameraCommand(parametr));
225 break;
226 case "LoadScene":
227 sequence.AddDialogueCommandToSequence(new LoadAnotherSceneCommand(parametr));
228 break;
229 case "PlayDialogue":
230 sequence.AddDialogueCommandToSequence(new PlayDialogueCommand(parametr));
231 break;
232 case "SetAnimatorTrigger":
233 sequence.AddDialogueCommandToSequence(new SetAnimatorTriggerCommand(parametr));
234 break;
235 default:
236 throw new Exception($"command type {typeCommand} is not valid", new ArgumentException());
237 }
238 }
239
240 return sequence;
241 }
242
243 /// <summary>
244 /// General public API to add dialogue to queue
245 /// </summary>
246 /// <param name="id"> dialogue id </param>
247 public void PlayDialogue(string id)
248 {
249 if (_allDialoguesDictionary.TryGetValue(id, out Dialogue dialogue))
250 {
251 if (_queueDialogue.Count == 0) // if there are no dialogues in the queue, just add and start
252 {
253 AddDialogueToQueue(dialogue);
254 StartDialogue();
255 }
256 else
257 {
258 // if the weight of the added dialogue is strictly greater than the weight of the last dialogue in queue, start the interruption
259 if (dialogue.DialogueWeight > _queueDialogue[^1].DialogueWeight)
260 {
261 InterruptCurrentDialogueWith(dialogue);
262 }
263 // just add to the end of queue
264 else
265 {
266 AddDialogueToQueue(dialogue);
267 }
268 }
269 }
270 else
271 {
272 throw new Exception($"dialogues dictionary contains no dialogue with id {id}", new ArgumentException());
273 }
274 }
275
276 /// <summary>
277 /// Only add dialogue to queue, all checks must be fulfilled
278 /// </summary>
279 /// <param name="dialogue"></param>
280 private void AddDialogueToQueue(Dialogue dialogue)
281 {
282 _queueDialogue.Add(dialogue);
283 }
284
285 /// <summary>
286 /// Start the dialogue
287 /// </summary>
288 protected virtual void StartDialogue()
289 {
290 Dialogue dialogue = _queueDialogue[0];
291 _currentDialogueDuration = dialogue.PlayDuration; //cached for update metod
292 CurrentDialogueState = DialogueStates.Playing;
293 dialogue.StartDialogue();
294 }
295
296 /// <summary>
297 /// Stop the dialogue
298 /// </summary>
299 protected virtual void StopDialogue()
300 {
301 CurrentDialogue.StopDialogue();
302 _queueDialogue.RemoveAt(0);
303 _playingTimer = 0;
304 if (_queueDialogue.Count == 0)
305 {
306 CurrentDialogueState = DialogueStates.Inactive;
307 }
308 else
309 {
310 CurrentDialogueState = DialogueStates.Transitions;
311 }
312 }
313
314 /// <summary>
315 /// Interrupting the current queue and deleting a dialog with priority, lower that dialogue that interrupts queue
316 /// </summary>
317 /// <param name="newDialogue"> dialogue that interrupts queue </param>
318 protected virtual void InterruptCurrentDialogueWith(Dialogue newDialogue)
319 {
320 int i = 0;
321 // If current state is playing, we pass the list with index 1, because index 0 is now playing and will be remove from the queue when calling the method StopDialogue().
322 // If not, the current state - transition, and no dialogue is playing now, and we have to check all the dialogues in the queue.
323 if (CurrentDialogueState == DialogueStates.Playing)
324 {
325 i = 1;
326 }
327 for (; i < _queueDialogue.Count; i++)
328 {
329 if (_queueDialogue[i].DialogueWeight < newDialogue.DialogueWeight)
330 {
331 _queueDialogue.RemoveAt(i);
332 i--;
333 }
334 }
335
336 AddDialogueToQueue(newDialogue);
337 }
338
339
340 protected virtual void TestSystem()
341 {
342 PlayDialogue("4");
343 PlayDialogue("3");
344 PlayDialogue("i2");
345 PlayDialogue("1");
346 PlayDialogue("6");
347 //uploading dialogues to that will be played in dialogue zone
348 for (int i=0; i<_queueDialogue.Count; i++)
349 {
350 dialogueZoneChild.AddDialogueLine(_queueDialogue[i].Message, _queueDialogue[i].PlayDuration);
351 }
352 }
353 }
354}
355