dynamicmusicplayer.c 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
  1. //#define DMP_DEBUG_PRINT
  2. //#define DMP_DEBUG_SETTINGS
  3. class DynamicMusicLocationTypes
  4. {
  5. const int NONE = -1;
  6. const int CONTAMINATED_ZONE = 0;
  7. const int UNDERGROUND = 1;
  8. }
  9. class DynamicMusicLocationShape
  10. {
  11. const int BOX = 0;
  12. const int POLYGON = 1;
  13. }
  14. class DynamicMusicLocationDynamicData
  15. {
  16. int m_Type = DynamicMusicLocationTypes.NONE;
  17. //! rectangle coords (2d only)
  18. vector m_Min = vector.Zero;
  19. vector m_Max = vector.Zero;
  20. static array<vector> GetRectangularCoordsFromSize(vector origin, float size)
  21. {
  22. vector min = Vector(origin[0] - size, origin[1], origin[2] - size);
  23. vector max = Vector(origin[0] + size, origin[1], origin[2] + size);
  24. return {min, max};
  25. }
  26. }
  27. class DynamicMusicPlayerSettings
  28. {
  29. float m_MinWaitTimeSeconds = 3.0;
  30. float m_MaxWaitTimeSeconds = 5.0;
  31. float m_PreviousTrackFadeoutSeconds = 30;
  32. }
  33. class DynamicMusicTrackData
  34. {
  35. bool m_HasPriority = false;
  36. int m_TimeOfDay = -1;
  37. int m_LocationType = DynamicMusicLocationTypes.NONE;
  38. int m_Shape = DynamicMusicLocationShape.BOX;
  39. string m_SoundSet;
  40. EDynamicMusicPlayerCategory m_Category;
  41. ref array<ref array<vector>> locationBoundaries = new array<ref array<vector>>();
  42. ref array<vector> vertices = new array<vector>();
  43. void InsertLocation(vector min, vector max)
  44. {
  45. locationBoundaries.Insert({min, max});
  46. }
  47. }
  48. class DynamicMusicPlayerTrackHistoryLookupType
  49. {
  50. const int ANY = 0;
  51. const int BUFFER = 1;
  52. }
  53. /*
  54. * \brief Structure for setting of Category playback
  55. * @param m_Forced Force play of category (DEPRECATED)
  56. * @param m_FadeOut If true, previously playing track will be faded out and stopped. Otherwise stopped only.
  57. * @param m_Category Selected category that is going to be played
  58. */
  59. class DynamicMusicPlayerCategoryPlaybackData
  60. {
  61. bool m_Forced = false;
  62. bool m_FadeOut = false;
  63. EDynamicMusicPlayerCategory m_Category = EDynamicMusicPlayerCategory.NONE;
  64. }
  65. class DynamicMusicPlayer
  66. {
  67. #ifdef DMP_DEBUG_SETTINGS
  68. protected const float TICK_TIME_OF_DATE_UPDATE_SECONDS = 10.0;
  69. protected const float TICK_LOCATION_CACHE_UPDATE_SECONDS = 10.0;
  70. protected const float TICK_LOCATION_UPDATE_SECONDS = 5.0;
  71. protected const float TICK_PRIORITY_LOCATION_UPDATE_SECONDS = 2.0;
  72. #else
  73. protected const float TICK_TIME_OF_DATE_UPDATE_SECONDS = 300.0;
  74. protected const float TICK_LOCATION_CACHE_UPDATE_SECONDS = 120.0;
  75. protected const float TICK_LOCATION_UPDATE_SECONDS = 120.0;
  76. protected const float TICK_PRIORITY_LOCATION_UPDATE_SECONDS = 30.0;
  77. #endif
  78. protected const float TICK_FADEOUT_PROCESSOR_SECONDS = 0.2;
  79. protected const int TRACKS_BUFFER_HISTORY_SIZE = 2;
  80. protected const float LOCATION_DISTANCE_MAX = 500;
  81. protected float m_TickTimeOfDateElapsed;
  82. protected float m_TickLocationCacheUpdateElapsed;
  83. protected float m_TickLocationUpdateElapsed;
  84. protected float m_TickPriorityLocationUpdateElapsed;
  85. protected float m_TickFadeOutProcessingElapsed;
  86. protected int m_ActualTimeOfDay
  87. protected EDynamicMusicPlayerCategory m_CategorySelected;
  88. protected DynamicMusicTrackData m_CurrentTrack;
  89. protected ref DynamicMusicPlayerRegistry m_DynamicMusicPlayerRegistry;
  90. protected ref map<int, ref DynamicMusicLocationDynamicData> m_LocationsDynamic //! map of dynamically registered locations during runtime
  91. private ref array<ref DynamicMusicTrackData> m_TracksLocationStaticCached; //! static + filtered by the distance between player and center of zone
  92. private ref array<ref DynamicMusicTrackData> m_TracksLocationStaticPrioritizedCached; //! static prio + filtered by the distance between player and center of zone
  93. protected ref array<ref DynamicMusicTrackData> m_TracksLocationMatchedPlayerInside;
  94. protected AbstractWave m_SoundPlaying;
  95. private bool m_WaitingForPlayback;
  96. private int m_RequestedPlaybackMode //! gets the playback mode as set in sounds menu; 0 - all; 1 - menu only
  97. private ref map<EDynamicMusicPlayerCategory, ref SimpleCircularBuffer<int>> m_LastPlayedTrackBufferPerCategory;
  98. private vector m_PlayerPosition;
  99. private float m_FadeoutTimeElapsed;
  100. private float m_FadeoutTimeRequested;
  101. private bool m_FadeoutInProgress;
  102. private bool m_Created;
  103. void DynamicMusicPlayer(DynamicMusicPlayerRegistry configuration)
  104. {
  105. m_DynamicMusicPlayerRegistry = configuration;
  106. m_RequestedPlaybackMode = g_Game.GetProfileOptionInt(EDayZProfilesOptions.AMBIENT_MUSIC_MODE);
  107. m_ActualTimeOfDay = DynamicMusicPlayerTimeOfDay.ANY;
  108. m_CategorySelected = EDynamicMusicPlayerCategory.NONE;
  109. m_LastPlayedTrackBufferPerCategory = new map<EDynamicMusicPlayerCategory, ref SimpleCircularBuffer<int>>;
  110. m_LastPlayedTrackBufferPerCategory[EDynamicMusicPlayerCategory.MENU] = new SimpleCircularBuffer<int>(TRACKS_BUFFER_HISTORY_SIZE, -1);
  111. m_LastPlayedTrackBufferPerCategory[EDynamicMusicPlayerCategory.CREDITS] = new SimpleCircularBuffer<int>(TRACKS_BUFFER_HISTORY_SIZE, -1);
  112. m_LastPlayedTrackBufferPerCategory[EDynamicMusicPlayerCategory.TIME] = new SimpleCircularBuffer<int>(TRACKS_BUFFER_HISTORY_SIZE, -1);
  113. m_LastPlayedTrackBufferPerCategory[EDynamicMusicPlayerCategory.LOCATION_STATIC] = new SimpleCircularBuffer<int>(TRACKS_BUFFER_HISTORY_SIZE, -1);
  114. m_LastPlayedTrackBufferPerCategory[EDynamicMusicPlayerCategory.LOCATION_STATIC_PRIORITY] = new SimpleCircularBuffer<int>(TRACKS_BUFFER_HISTORY_SIZE, -1);
  115. m_LastPlayedTrackBufferPerCategory[EDynamicMusicPlayerCategory.LOCATION_DYNAMIC] = new SimpleCircularBuffer<int>(TRACKS_BUFFER_HISTORY_SIZE, -1);
  116. m_LocationsDynamic = new map<int, ref DynamicMusicLocationDynamicData>();
  117. m_TracksLocationStaticCached = new array<ref DynamicMusicTrackData>();
  118. m_TracksLocationStaticPrioritizedCached = new array<ref DynamicMusicTrackData>();
  119. m_TracksLocationMatchedPlayerInside = new array<ref DynamicMusicTrackData>();
  120. SetTimeOfDate();
  121. //! fadeout settings
  122. m_FadeoutTimeElapsed = 0.0;
  123. m_FadeoutTimeRequested = 0.0;
  124. g_Game.GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(RefreshTracksCache, 5000);
  125. DayZProfilesOptions.m_OnIntOptionChanged.Insert(OnProfileOptionChanged);
  126. m_Created = true;
  127. }
  128. void OnUpdate(float timeslice)
  129. {
  130. if (m_DynamicMusicPlayerRegistry == null)
  131. return;
  132. m_TickTimeOfDateElapsed = Math.Clamp(m_TickTimeOfDateElapsed + timeslice, 0.0, TICK_TIME_OF_DATE_UPDATE_SECONDS);
  133. m_TickLocationCacheUpdateElapsed = Math.Clamp(m_TickLocationCacheUpdateElapsed + timeslice, 0.0, TICK_LOCATION_CACHE_UPDATE_SECONDS);
  134. m_TickLocationUpdateElapsed = Math.Clamp(m_TickLocationUpdateElapsed + timeslice, 0.0, TICK_LOCATION_UPDATE_SECONDS);
  135. m_TickPriorityLocationUpdateElapsed = Math.Clamp(m_TickPriorityLocationUpdateElapsed + timeslice, 0.0, TICK_PRIORITY_LOCATION_UPDATE_SECONDS);
  136. m_TickFadeOutProcessingElapsed = Math.Clamp(m_TickFadeOutProcessingElapsed + timeslice, 0.0, TICK_FADEOUT_PROCESSOR_SECONDS);
  137. //! handle fadeouts
  138. if (m_FadeoutInProgress && m_TickFadeOutProcessingElapsed >= TICK_FADEOUT_PROCESSOR_SECONDS)
  139. {
  140. m_FadeoutTimeElapsed += TICK_FADEOUT_PROCESSOR_SECONDS;
  141. m_TickFadeOutProcessingElapsed = 0.0;
  142. if (m_FadeoutTimeElapsed >= m_FadeoutTimeRequested)
  143. {
  144. m_FadeoutTimeElapsed = 0.0;
  145. m_FadeoutTimeRequested = 0.0;
  146. m_FadeoutInProgress = false;
  147. OnFadeoutFinished(m_CategorySelected);
  148. }
  149. else
  150. ProcessFadeOut();
  151. }
  152. else
  153. {
  154. if (m_CategorySelected != EDynamicMusicPlayerCategory.MENU && m_CategorySelected != EDynamicMusicPlayerCategory.CREDITS)
  155. {
  156. //! caching of locations based on distance from player (<= LOCATION_DISTANCE_MAX)
  157. if (m_TickLocationCacheUpdateElapsed >= TICK_LOCATION_CACHE_UPDATE_SECONDS)
  158. {
  159. m_TickLocationCacheUpdateElapsed = 0.0;
  160. RefreshTracksCache();
  161. }
  162. if (m_TickPriorityLocationUpdateElapsed >= TICK_PRIORITY_LOCATION_UPDATE_SECONDS)
  163. {
  164. if (g_Game.GetPlayer())
  165. {
  166. m_PlayerPosition = g_Game.GetPlayer().GetPosition();
  167. m_PlayerPosition[1] = 0.0;
  168. }
  169. m_TickPriorityLocationUpdateElapsed = 0.0;
  170. //! no playback at all OR playback of non-prioritized category
  171. if ((IsPlaybackActive() && !IsPriotitizedCategorySelected()) || !IsPlaybackActive())
  172. {
  173. if (PlayerInsideOfLocationFilter(m_LocationsDynamic))
  174. OnLocationMatched(EDynamicMusicPlayerCategory.LOCATION_DYNAMIC, true);
  175. else if (PlayerInsideOfLocationFilter(m_TracksLocationStaticPrioritizedCached))
  176. OnLocationMatched(EDynamicMusicPlayerCategory.LOCATION_STATIC_PRIORITY, true);
  177. }
  178. }
  179. if (m_TickLocationUpdateElapsed >= TICK_LOCATION_UPDATE_SECONDS)
  180. {
  181. m_TickLocationUpdateElapsed = 0.0;
  182. if (!IsPlaybackActive())
  183. {
  184. if (PlayerInsideOfLocationFilter(m_TracksLocationStaticCached))
  185. OnLocationMatched(EDynamicMusicPlayerCategory.LOCATION_STATIC, false);
  186. }
  187. }
  188. if (m_TickTimeOfDateElapsed >= TICK_TIME_OF_DATE_UPDATE_SECONDS)
  189. {
  190. m_TickTimeOfDateElapsed = 0.0;
  191. SetTimeOfDate();
  192. //! works as default category selector
  193. if (!IsPlaybackActive() || !IsPriotitizedCategorySelected())
  194. {
  195. DynamicMusicPlayerCategoryPlaybackData playbackData = new DynamicMusicPlayerCategoryPlaybackData();
  196. playbackData.m_Category = EDynamicMusicPlayerCategory.TIME;
  197. SetCategory(playbackData);
  198. }
  199. }
  200. }
  201. else
  202. {
  203. if (!IsPlaybackActive())
  204. DetermineTrackByCategory(m_CategorySelected);
  205. }
  206. }
  207. #ifdef DIAG_DEVELOPER
  208. if (DiagMenu.GetBool(DiagMenuIDs.SOUNDS_DYNAMIC_MUSIC_PLAYER_STATS))
  209. {
  210. DisplayDebugStats(true);
  211. DisplayStaticLocations(true);
  212. }
  213. #endif
  214. }
  215. /*
  216. * \brief Set playback category
  217. * @param playbackData Structure holding information about category playback settings
  218. */
  219. void SetCategory(DynamicMusicPlayerCategoryPlaybackData playbackData)
  220. {
  221. if (m_DynamicMusicPlayerRegistry == null)
  222. return;
  223. m_CategorySelected = playbackData.m_Category;
  224. if (!playbackData.m_FadeOut)
  225. {
  226. OnCategorySet(playbackData.m_Category, playbackData.m_Forced);
  227. return;
  228. }
  229. //! pass to fadeout handler
  230. FadeoutTrack(GetPreviousTrackFadeoutSeconds(playbackData.m_Category));
  231. }
  232. void RegisterDynamicLocation(notnull Entity caller, int locationType, float locationSize)
  233. {
  234. int id = caller.GetID();
  235. if (!m_LocationsDynamic.Contains(id))
  236. {
  237. array<vector> minMax = DynamicMusicLocationDynamicData.GetRectangularCoordsFromSize(caller.GetPosition(), locationSize);
  238. DynamicMusicLocationDynamicData location = new DynamicMusicLocationDynamicData();
  239. location.m_Type = locationType;
  240. location.m_Min = minMax[0];
  241. location.m_Max = minMax[1];
  242. m_LocationsDynamic.Insert(id, location);
  243. }
  244. }
  245. void UnregisterDynamicLocation(notnull Entity caller)
  246. {
  247. m_LocationsDynamic.Remove(caller.GetID());
  248. }
  249. void OnGameEvent(EventType eventTypeId, Param params)
  250. {
  251. if (eventTypeId == MPSessionPlayerReadyEventTypeID)
  252. {
  253. SetTimeOfDate();
  254. DynamicMusicPlayerCategoryPlaybackData playbackData = new DynamicMusicPlayerCategoryPlaybackData();
  255. playbackData.m_Category = EDynamicMusicPlayerCategory.TIME;
  256. SetCategory(playbackData);
  257. }
  258. }
  259. protected bool IsPriotitizedCategorySelected()
  260. {
  261. return m_CategorySelected == EDynamicMusicPlayerCategory.LOCATION_STATIC_PRIORITY || m_CategorySelected == EDynamicMusicPlayerCategory.LOCATION_DYNAMIC;
  262. }
  263. protected void DetermineTrackByCategory(EDynamicMusicPlayerCategory category)
  264. {
  265. if (m_CategorySelected != EDynamicMusicPlayerCategory.MENU && m_RequestedPlaybackMode == 1)
  266. return;
  267. if (IsPlaybackActive())
  268. return;
  269. switch (category)
  270. {
  271. case EDynamicMusicPlayerCategory.MENU:
  272. if (SetSelectedTrackFromCategory(category, m_DynamicMusicPlayerRegistry.m_TracksMenu, DynamicMusicPlayerTrackHistoryLookupType.BUFFER))
  273. break;
  274. g_Game.GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(DetermineTrackByCategory, 5000, false, category);
  275. break;
  276. case EDynamicMusicPlayerCategory.CREDITS:
  277. if (SetSelectedTrackFromCategory(category, m_DynamicMusicPlayerRegistry.m_TracksCredits, DynamicMusicPlayerTrackHistoryLookupType.BUFFER))
  278. break;
  279. g_Game.GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(DetermineTrackByCategory, 5000, false, category);
  280. break;
  281. case EDynamicMusicPlayerCategory.TIME:
  282. if (SetSelectedTrackFromCategory(category, m_DynamicMusicPlayerRegistry.m_TracksTime, DynamicMusicPlayerTrackHistoryLookupType.BUFFER))
  283. break;
  284. g_Game.GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(DetermineTrackByCategory, 5000, false, category);
  285. break;
  286. case EDynamicMusicPlayerCategory.LOCATION_STATIC:
  287. if (SetSelectedTrackFromCategory(category, m_TracksLocationMatchedPlayerInside))
  288. break;
  289. g_Game.GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(DetermineTrackByCategory, 5000, false, category);
  290. break;
  291. case EDynamicMusicPlayerCategory.LOCATION_STATIC_PRIORITY:
  292. if (SetSelectedTrackFromCategory(category, m_TracksLocationMatchedPlayerInside))
  293. break;
  294. g_Game.GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(DetermineTrackByCategory, 5000, false, category);
  295. break;
  296. case EDynamicMusicPlayerCategory.LOCATION_DYNAMIC:
  297. if (SetSelectedTrackFromCategory(category, m_DynamicMusicPlayerRegistry.m_TracksLocationDynamic))
  298. break;
  299. g_Game.GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(DetermineTrackByCategory, 5000, false, category);
  300. break;
  301. }
  302. }
  303. protected bool IsPlaybackActive()
  304. {
  305. return m_SoundPlaying || m_WaitingForPlayback;
  306. }
  307. //! Events
  308. //! --------------------------------------------------------------------------------
  309. protected void OnProfileOptionChanged(EDayZProfilesOptions option, int value)
  310. {
  311. if (option == EDayZProfilesOptions.AMBIENT_MUSIC_MODE)
  312. {
  313. m_RequestedPlaybackMode = value;
  314. if (value == 1 && m_CategorySelected != EDynamicMusicPlayerCategory.MENU)
  315. {
  316. StopTrack();
  317. ResetWaitingQueue();
  318. }
  319. }
  320. }
  321. protected void OnCategorySet(EDynamicMusicPlayerCategory category, bool forced)
  322. {
  323. #ifdef ENABLE_LOGGING
  324. DMPDebugPrint(string.Format(
  325. "OnCategorySet() - category: %1, forced: %2",
  326. EnumTools.EnumToString(EDynamicMusicPlayerCategory, category),
  327. forced),
  328. );
  329. #endif
  330. DetermineTrackByCategory(category);
  331. }
  332. protected void OnTrackEnded()
  333. {
  334. #ifdef ENABLE_LOGGING
  335. if (m_CurrentTrack)
  336. DMPDebugPrint(string.Format("Track END - %1", m_CurrentTrack.m_SoundSet));
  337. #endif
  338. m_SoundPlaying = null;
  339. m_CurrentTrack = null;
  340. m_WaitingForPlayback = false;
  341. }
  342. protected void OnTrackStopped()
  343. {
  344. //! stopped only by fadeouts
  345. #ifdef ENABLE_LOGGING
  346. if (m_CurrentTrack)
  347. DMPDebugPrint(string.Format("Track STOP - %1", m_CurrentTrack.m_SoundSet));
  348. #endif
  349. m_SoundPlaying = null;
  350. m_CurrentTrack = null;
  351. m_WaitingForPlayback = false;
  352. }
  353. protected void OnNextTrackSelected(DynamicMusicTrackData track, float waitTime)
  354. {
  355. m_WaitingForPlayback = true;
  356. m_CurrentTrack = track;
  357. if (m_Created)
  358. m_Created = false;
  359. #ifdef ENABLE_LOGGING
  360. DMPDebugPrint(string.Format(
  361. "WaitTime set to %1s, deferring playback of \"%2\"",
  362. (int)waitTime,
  363. track.m_SoundSet),
  364. );
  365. m_DebugWaitTime = waitTime;
  366. #endif
  367. g_Game.GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(PlayTrack, (int)waitTime * 1000, false, track);
  368. }
  369. protected void OnLocationMatched(EDynamicMusicPlayerCategory category, bool isPriorityLocation)
  370. {
  371. #ifdef ENABLE_LOGGING
  372. string messagePriority;
  373. if (isPriorityLocation)
  374. messagePriority = "(with priority)";
  375. DMPDebugPrint(string.Format("Location matched %1", messagePriority));
  376. #endif
  377. DynamicMusicPlayerCategoryPlaybackData playbackData = new DynamicMusicPlayerCategoryPlaybackData();
  378. playbackData.m_Category = category;
  379. if (isPriorityLocation)
  380. {
  381. playbackData.m_Forced = isPriorityLocation;
  382. if (!IsPriotitizedCategorySelected())
  383. {
  384. m_CategorySelected = category;
  385. if (m_WaitingForPlayback)
  386. ResetWaitingQueue();
  387. if (m_SoundPlaying)
  388. {
  389. playbackData.m_FadeOut = true;
  390. SetCategory(playbackData);
  391. return;
  392. }
  393. SetCategory(playbackData);
  394. }
  395. else
  396. SetCategory(playbackData); //! play prio location track (no fadeout)
  397. }
  398. else
  399. SetCategory(playbackData); //! play location track (no fadeout)
  400. }
  401. protected void OnFadeoutFinished(EDynamicMusicPlayerCategory category)
  402. {
  403. if (m_SoundPlaying)
  404. m_SoundPlaying.GetEvents().Event_OnSoundWaveEnded.Remove(OnTrackEnded);
  405. StopTrack();
  406. DynamicMusicPlayerCategoryPlaybackData playbackData = new DynamicMusicPlayerCategoryPlaybackData();
  407. playbackData.m_Category = category;
  408. playbackData.m_Forced = IsPriotitizedCategorySelected();
  409. SetCategory(playbackData);
  410. }
  411. //! --------------------------------------------------------------------------------
  412. private void PlayTrack(DynamicMusicTrackData track)
  413. {
  414. SoundParams soundParams = new SoundParams(track.m_SoundSet);
  415. if (soundParams.IsValid())
  416. {
  417. SoundObjectBuilder soundBuilder = new SoundObjectBuilder(soundParams);
  418. SoundObject soundObject = soundBuilder.BuildSoundObject();
  419. soundObject.SetKind(WaveKind.WAVEMUSIC);
  420. m_SoundPlaying = GetGame().GetSoundScene().Play2D(soundObject, soundBuilder);
  421. if (m_SoundPlaying)
  422. {
  423. m_SoundPlaying.Loop(false);
  424. m_SoundPlaying.Play();
  425. //! register callbacks
  426. m_SoundPlaying.GetEvents().Event_OnSoundWaveEnded.Insert(OnTrackEnded);
  427. m_SoundPlaying.GetEvents().Event_OnSoundWaveStopped.Insert(OnTrackStopped);
  428. m_WaitingForPlayback = false;
  429. }
  430. }
  431. else //! invalid sound set is used
  432. {
  433. m_WaitingForPlayback = false;
  434. m_CurrentTrack = null;
  435. }
  436. }
  437. private void StopTrack()
  438. {
  439. if (m_SoundPlaying)
  440. m_SoundPlaying.Stop();
  441. }
  442. private void ResetWaitingQueue()
  443. {
  444. if (m_WaitingForPlayback)
  445. {
  446. g_Game.GetCallQueue(CALL_CATEGORY_SYSTEM).Remove(PlayTrack);
  447. m_WaitingForPlayback = false;
  448. m_CurrentTrack = null;
  449. }
  450. }
  451. private void FadeoutTrack(float fadeoutSeconds)
  452. {
  453. if (m_FadeoutInProgress)
  454. return;
  455. ResetWaitingQueue();
  456. if (m_CurrentTrack && m_SoundPlaying)
  457. {
  458. #ifdef ENABLE_LOGGING
  459. DMPDebugPrint(string.Format("Stopping currently played track %1", m_CurrentTrack.m_SoundSet));
  460. DMPDebugPrint(string.Format("-- Setting fadeout to %1", fadeoutSeconds));
  461. #endif
  462. m_FadeoutInProgress = true;
  463. m_FadeoutTimeRequested = fadeoutSeconds;
  464. }
  465. }
  466. private void ProcessFadeOut()
  467. {
  468. if (m_SoundPlaying)
  469. {
  470. float volume = 1 - (m_FadeoutTimeElapsed / m_FadeoutTimeRequested);
  471. m_SoundPlaying.SetFadeOutFactor(volume);
  472. }
  473. }
  474. private bool PlayerInsideOfLocationFilter(array<ref DynamicMusicTrackData> locations)
  475. {
  476. m_TracksLocationMatchedPlayerInside.Clear();
  477. if (locations.Count() > 0)
  478. {
  479. foreach (DynamicMusicTrackData track : locations)
  480. {
  481. switch (track.m_Shape)
  482. {
  483. case DynamicMusicLocationShape.BOX:
  484. foreach (int locationId, array<vector> bounds : track.locationBoundaries)
  485. {
  486. if (Math.IsPointInRectangle(bounds[0], bounds[1], m_PlayerPosition))
  487. {
  488. if (m_TracksLocationMatchedPlayerInside.Find(track) == INDEX_NOT_FOUND)
  489. m_TracksLocationMatchedPlayerInside.Insert(track);
  490. #ifdef ENABLE_LOGGING
  491. DMPDebugPrint(string.Format("Player inside location <%1, %2>", bounds[0], bounds[1]));
  492. #endif
  493. }
  494. }
  495. break;
  496. case DynamicMusicLocationShape.POLYGON:
  497. if (Math2D.IsPointInPolygonXZ(track.vertices, m_PlayerPosition))
  498. {
  499. if (m_TracksLocationMatchedPlayerInside.Find(track) == INDEX_NOT_FOUND)
  500. m_TracksLocationMatchedPlayerInside.Insert(track);
  501. #ifdef ENABLE_LOGGING
  502. DMPDebugPrint(string.Format("Player inside polygon location at <%1>", m_PlayerPosition));
  503. #endif
  504. }
  505. break;
  506. }
  507. }
  508. }
  509. return m_TracksLocationMatchedPlayerInside.Count() > 0;
  510. }
  511. private bool PlayerInsideOfLocationFilter(map<int, ref DynamicMusicLocationDynamicData> locations)
  512. {
  513. if (locations.Count() > 0)
  514. {
  515. foreach (int locationId, DynamicMusicLocationDynamicData location : locations)
  516. {
  517. if (Math.IsPointInRectangle(location.m_Min, location.m_Max, m_PlayerPosition))
  518. {
  519. #ifdef ENABLE_LOGGING
  520. DMPDebugPrint(string.Format("Player inside location <%1, %2>", location.m_Min, location.m_Max));
  521. #endif
  522. return true;
  523. }
  524. }
  525. }
  526. return false;
  527. }
  528. private bool SetSelectedTrackFromCategory(EDynamicMusicPlayerCategory category, notnull array<ref DynamicMusicTrackData> tracklist, int historyLookupType = DynamicMusicPlayerTrackHistoryLookupType.ANY)
  529. {
  530. if (tracklist.Count() == 0)
  531. return true;
  532. array<ref DynamicMusicTrackData> filteredTracks = new array<ref DynamicMusicTrackData>();
  533. foreach (DynamicMusicTrackData filteredTrack : tracklist)
  534. {
  535. if (filteredTrack.m_TimeOfDay == m_ActualTimeOfDay || filteredTrack.m_TimeOfDay == DynamicMusicPlayerTimeOfDay.ANY)
  536. filteredTracks.Insert(filteredTrack);
  537. }
  538. float trackIndex;
  539. //! currently prioritize main menu track on DMP start
  540. if (m_Created && category == EDynamicMusicPlayerCategory.MENU)
  541. trackIndex = SelectRandomTrackIndexFromCategoryPriorityFlagFirst(category, filteredTracks);
  542. else
  543. trackIndex = SelectRandomTrackIndexFromCategory(category, historyLookupType, filteredTracks);
  544. if (trackIndex > INDEX_NOT_FOUND)
  545. {
  546. m_LastPlayedTrackBufferPerCategory[category].Add(trackIndex);
  547. OnNextTrackSelected(filteredTracks[trackIndex], GetWaitTimeForCategory(category));
  548. return true;
  549. }
  550. return false;
  551. }
  552. private int SelectRandomTrackIndexFromCategoryPriorityFlagFirst(EDynamicMusicPlayerCategory category, notnull array<ref DynamicMusicTrackData> tracks)
  553. {
  554. //! main menu priority tracks first
  555. if (category == EDynamicMusicPlayerCategory.MENU)
  556. {
  557. array<int> priorityFlagIndices = new array<int>();
  558. foreach (int i, DynamicMusicTrackData track : tracks)
  559. {
  560. if (!track.m_HasPriority)
  561. continue;
  562. priorityFlagIndices.Insert(i);
  563. }
  564. if (priorityFlagIndices.Count() > 0)
  565. return priorityFlagIndices[priorityFlagIndices.GetRandomIndex()];
  566. //! fallback in case there is no priority track
  567. return tracks.GetRandomIndex();
  568. }
  569. return INDEX_NOT_FOUND;
  570. }
  571. private int SelectRandomTrackIndexFromCategory(EDynamicMusicPlayerCategory category, int lookupType, notnull array<ref DynamicMusicTrackData> tracks)
  572. {
  573. int count = tracks.Count();
  574. if (count > 0)
  575. {
  576. int index = Math.RandomInt(0, count);
  577. switch (lookupType)
  578. {
  579. case DynamicMusicPlayerTrackHistoryLookupType.ANY:
  580. return index;
  581. case DynamicMusicPlayerTrackHistoryLookupType.BUFFER:
  582. // fallback - num of track is smaller than actual history size;
  583. if (count <= TRACKS_BUFFER_HISTORY_SIZE)
  584. return index;
  585. if (m_LastPlayedTrackBufferPerCategory[category].GetValues().Find(index) == INDEX_NOT_FOUND)
  586. return index;
  587. return INDEX_NOT_FOUND;
  588. }
  589. }
  590. return INDEX_NOT_FOUND;
  591. }
  592. private void SetTimeOfDate()
  593. {
  594. if (g_Game.GetMission())
  595. {
  596. m_ActualTimeOfDay = g_Game.GetMission().GetWorldData().GetDaytime();
  597. return;
  598. }
  599. m_ActualTimeOfDay = DynamicMusicPlayerTimeOfDay.DAY;
  600. }
  601. protected float GetWaitTimeForCategory(EDynamicMusicPlayerCategory category)
  602. {
  603. return Math.RandomFloatInclusive(GetMinWaitTimePerCategory(category), GetMaxWaitTimePerCategory(category));
  604. }
  605. private float GetMinWaitTimePerCategory(EDynamicMusicPlayerCategory category)
  606. {
  607. float waitTime = m_DynamicMusicPlayerRegistry.m_SettingsByCategory[category].m_MinWaitTimeSeconds;
  608. #ifdef DIAG_DEVELOPER
  609. if (FeatureTimeAccel.GetFeatureTimeAccelEnabled(ETimeAccelCategories.DYNAMIC_MUSIC_PLAYER))
  610. {
  611. float timeAccel = FeatureTimeAccel.GetFeatureTimeAccelValue();
  612. if (timeAccel > 0)
  613. return waitTime / FeatureTimeAccel.GetFeatureTimeAccelValue();
  614. }
  615. #endif
  616. return waitTime;
  617. }
  618. private float GetMaxWaitTimePerCategory(EDynamicMusicPlayerCategory category)
  619. {
  620. float waitTime = m_DynamicMusicPlayerRegistry.m_SettingsByCategory[category].m_MaxWaitTimeSeconds;
  621. #ifdef DIAG_DEVELOPER
  622. if (FeatureTimeAccel.GetFeatureTimeAccelEnabled(ETimeAccelCategories.DYNAMIC_MUSIC_PLAYER))
  623. {
  624. float timeAccel = FeatureTimeAccel.GetFeatureTimeAccelValue();
  625. if (timeAccel > 0)
  626. return waitTime / FeatureTimeAccel.GetFeatureTimeAccelValue();
  627. }
  628. #endif
  629. return waitTime;
  630. }
  631. private float GetPreviousTrackFadeoutSeconds(EDynamicMusicPlayerCategory category)
  632. {
  633. return m_DynamicMusicPlayerRegistry.m_SettingsByCategory[category].m_PreviousTrackFadeoutSeconds;
  634. }
  635. private void RefreshTracksCache()
  636. {
  637. if (m_DynamicMusicPlayerRegistry)
  638. {
  639. m_TracksLocationStaticCached.Clear();
  640. foreach (DynamicMusicTrackData track : m_DynamicMusicPlayerRegistry.m_TracksLocationStatic)
  641. {
  642. if (track.m_Shape == DynamicMusicLocationShape.BOX)
  643. {
  644. foreach (array<vector> bounds : track.locationBoundaries)
  645. {
  646. if (vector.Distance(m_PlayerPosition, Math.CenterOfRectangle(bounds[0], bounds[1])) > LOCATION_DISTANCE_MAX)
  647. continue;
  648. }
  649. }
  650. m_TracksLocationStaticCached.Insert(track);
  651. }
  652. m_TracksLocationStaticPrioritizedCached.Clear();
  653. foreach (DynamicMusicTrackData trackPrio : m_DynamicMusicPlayerRegistry.m_TracksLocationStaticPrioritized)
  654. {
  655. if (trackPrio.m_Shape == DynamicMusicLocationShape.BOX)
  656. {
  657. foreach (array<vector> boundsPrio : trackPrio.locationBoundaries)
  658. {
  659. if (vector.Distance(m_PlayerPosition, Math.CenterOfRectangle(boundsPrio[0], boundsPrio[1])) > LOCATION_DISTANCE_MAX)
  660. continue;
  661. }
  662. }
  663. m_TracksLocationStaticPrioritizedCached.Insert(trackPrio);
  664. }
  665. }
  666. }
  667. #ifdef DIAG_DEVELOPER
  668. private ref array<Shape> m_DebugShapesLocations = new array<Shape>();
  669. private ref array<Shape> m_DebugShapesLocationsVertices = new array<Shape>();
  670. private float m_DebugWaitTime = 0;
  671. private void DisplayDebugStats(bool enabled)
  672. {
  673. int windowPosX = 10;
  674. int windowPosY = 200;
  675. DbgUI.Begin("DMP - Overall stats", windowPosX, windowPosY);
  676. if (enabled)
  677. {
  678. bool isPlaybackActive = m_SoundPlaying != null;
  679. DbgUI.Text(string.Format("Day/Night: %1", DynamicMusicPlayerTimeOfDay.ToString(m_ActualTimeOfDay)));
  680. DbgUI.Text("Playback:");
  681. DbgUI.Text(string.Format(" active: %1", isPlaybackActive.ToString()));
  682. DbgUI.Text(string.Format(" waiting: %1", m_WaitingForPlayback.ToString()));
  683. DbgUI.Text(string.Format("Selected Category: %1", EnumTools.EnumToString(EDynamicMusicPlayerCategory, m_CategorySelected)));
  684. if (m_CategorySelected != EDynamicMusicPlayerCategory.MENU && m_CategorySelected != EDynamicMusicPlayerCategory.CREDITS)
  685. {
  686. DbgUI.Text("Update timers:");
  687. DbgUI.Text(string.Format(" TimeOfDay: %1(%2)", TICK_TIME_OF_DATE_UPDATE_SECONDS, TICK_TIME_OF_DATE_UPDATE_SECONDS - (int)m_TickTimeOfDateElapsed));
  688. DbgUI.Text(string.Format(" Location: %1(%2)", TICK_LOCATION_UPDATE_SECONDS, TICK_LOCATION_UPDATE_SECONDS - (int)m_TickLocationUpdateElapsed));
  689. DbgUI.Text(string.Format(" PriorityLocation: %1(%2)", TICK_PRIORITY_LOCATION_UPDATE_SECONDS, TICK_PRIORITY_LOCATION_UPDATE_SECONDS - (int)m_TickPriorityLocationUpdateElapsed));
  690. DbgUI.Text(string.Format(" Location Cache: %1(%2)", TICK_LOCATION_CACHE_UPDATE_SECONDS, TICK_LOCATION_CACHE_UPDATE_SECONDS - (int)m_TickLocationCacheUpdateElapsed));
  691. }
  692. if (m_CategorySelected != EDynamicMusicPlayerCategory.MENU && m_CategorySelected != EDynamicMusicPlayerCategory.CREDITS)
  693. {
  694. DbgUI.Text("Player:");
  695. DbgUI.Text(string.Format(" position: %1", m_PlayerPosition.ToString()));
  696. DbgUI.Text(string.Format(" matched num tracks(location): %1", m_TracksLocationMatchedPlayerInside.Count()));
  697. }
  698. DbgUI.Text("Tracks counts:");
  699. if (m_CategorySelected == EDynamicMusicPlayerCategory.MENU || m_CategorySelected == EDynamicMusicPlayerCategory.CREDITS)
  700. {
  701. DbgUI.Text(string.Format(" Menu: %1", m_DynamicMusicPlayerRegistry.m_TracksMenu.Count()));
  702. DbgUI.Text(string.Format(" Credits: %1", m_DynamicMusicPlayerRegistry.m_TracksCredits.Count()));
  703. }
  704. else
  705. {
  706. DbgUI.Text(string.Format(" Time: %1", m_DynamicMusicPlayerRegistry.m_TracksTime.Count()));
  707. DbgUI.Text(string.Format(" Static[cache]: %1", m_TracksLocationStaticCached.Count()));
  708. DbgUI.Text(string.Format(" Static(prio)[cache]: %1", m_TracksLocationStaticPrioritizedCached.Count()));
  709. DbgUI.Text(string.Format(" Dynamic(prio): %1", m_DynamicMusicPlayerRegistry.m_TracksLocationDynamic.Count()));
  710. }
  711. }
  712. DbgUI.End();
  713. DbgUI.Begin("DMP - Current track", windowPosX, windowPosY+380);
  714. if (enabled && m_CurrentTrack)
  715. {
  716. string isPlaying = "waiting";
  717. if (m_SoundPlaying != null)
  718. isPlaying = "playing";
  719. DbgUI.Text(string.Format("State: %1", isPlaying));
  720. if (m_WaitingForPlayback)
  721. {
  722. DbgUI.Text(string.Format("Wait time: %1s (%2s)", (int)m_DebugWaitTime, (int)(g_Game.GetCallQueue(CALL_CATEGORY_SYSTEM).GetRemainingTime(PlayTrack) * 0.001)));
  723. }
  724. DbgUI.Text(string.Format("Sound set: %1", m_CurrentTrack.m_SoundSet));
  725. DbgUI.Text(string.Format("Category: %1", EnumTools.EnumToString(EDynamicMusicPlayerCategory, m_CurrentTrack.m_Category)));
  726. DbgUI.Text(string.Format("Time of day: %1", DynamicMusicPlayerTimeOfDay.ToString(m_CurrentTrack.m_TimeOfDay)));
  727. }
  728. DbgUI.End();
  729. DbgUI.Begin("DMP - Controls", windowPosX + 500, windowPosY);
  730. if (enabled)
  731. {
  732. if (DbgUI.Button("Stop"))
  733. StopTrack();
  734. if (DbgUI.Button("Reset Waiting"))
  735. ResetWaitingQueue();
  736. DynamicMusicPlayerCategoryPlaybackData playbackData = new DynamicMusicPlayerCategoryPlaybackData();
  737. playbackData.m_Category = EDynamicMusicPlayerCategory.TIME;
  738. DbgUI.Text("Set Category:\n");
  739. if (DbgUI.Button("Time"))
  740. SetCategory(playbackData);
  741. if (DbgUI.Button("Location"))
  742. {
  743. playbackData.m_Category = EDynamicMusicPlayerCategory.LOCATION_STATIC;
  744. SetCategory(playbackData);
  745. }
  746. if (DbgUI.Button("Menu"))
  747. {
  748. playbackData.m_Category = EDynamicMusicPlayerCategory.MENU;
  749. SetCategory(playbackData);
  750. }
  751. if (DbgUI.Button("Credits"))
  752. {
  753. playbackData.m_Category = EDynamicMusicPlayerCategory.CREDITS;
  754. SetCategory(playbackData);
  755. }
  756. DbgUI.Text("Reset Timers\n");
  757. if (DbgUI.Button("Timer ALL"))
  758. {
  759. m_TickTimeOfDateElapsed = TICK_TIME_OF_DATE_UPDATE_SECONDS - 1.0;
  760. m_TickLocationUpdateElapsed = TICK_LOCATION_UPDATE_SECONDS - 1.0;
  761. m_TickPriorityLocationUpdateElapsed = TICK_PRIORITY_LOCATION_UPDATE_SECONDS - 1.0;
  762. }
  763. if (DbgUI.Button("Timer Daytime"))
  764. m_TickTimeOfDateElapsed = TICK_TIME_OF_DATE_UPDATE_SECONDS - 1.0;
  765. if (DbgUI.Button("Timer Location"))
  766. m_TickLocationUpdateElapsed = TICK_LOCATION_UPDATE_SECONDS - 1.0;
  767. if (DbgUI.Button("Timer Location(prio)"))
  768. m_TickPriorityLocationUpdateElapsed = TICK_PRIORITY_LOCATION_UPDATE_SECONDS - 1.0;
  769. }
  770. DbgUI.End();
  771. }
  772. private void DisplayStaticLocations(bool enabled)
  773. {
  774. if (enabled)
  775. {
  776. vector locationMin;
  777. vector locationMax;
  778. vector position = g_Game.GetCurrentCameraPosition();
  779. foreach (DynamicMusicTrackData track : m_TracksLocationStaticCached)
  780. {
  781. foreach (array<vector> bounds : track.locationBoundaries)
  782. {
  783. locationMin = bounds[0];
  784. locationMax = bounds[1];
  785. if (vector.Distance(position, Math.CenterOfRectangle(locationMin, locationMax)) > 2000)
  786. continue;
  787. Debug.CleanupDrawShapes(m_DebugShapesLocations);
  788. locationMax[1] = locationMin[1] + 200.0; //! force the height of box for debug
  789. locationMin[1] = locationMin[1] - 50.0;
  790. m_DebugShapesLocations.Insert(Debug.DrawBoxEx(locationMin, locationMax, Colors.PURPLE, ShapeFlags.NOZWRITE|ShapeFlags.ONCE));
  791. }
  792. Debug.CleanupDrawShapes(m_DebugShapesLocationsVertices);
  793. DrawPolygonLocation(track);
  794. }
  795. foreach (DynamicMusicTrackData trackPrio : m_TracksLocationStaticPrioritizedCached)
  796. {
  797. foreach (array<vector> boundsPrio : trackPrio.locationBoundaries)
  798. {
  799. locationMin = boundsPrio[0];
  800. locationMax = boundsPrio[1];
  801. if (vector.Distance(position, Math.CenterOfRectangle(locationMin, locationMax)) > 2000)
  802. continue;
  803. Debug.CleanupDrawShapes(m_DebugShapesLocations);
  804. locationMax[1] = locationMin[1] + 200.0; //! force the height of box for debug
  805. locationMin[1] = locationMin[1] - 50.0;
  806. m_DebugShapesLocations.Insert(Debug.DrawBoxEx(locationMin, locationMax, Colors.RED, ShapeFlags.NOZWRITE|ShapeFlags.ONCE));
  807. }
  808. Debug.CleanupDrawShapes(m_DebugShapesLocationsVertices);
  809. DrawPolygonLocation(trackPrio);
  810. }
  811. foreach (DynamicMusicLocationDynamicData locationDynamic : m_LocationsDynamic)
  812. {
  813. locationMin = locationDynamic.m_Min;
  814. locationMax = locationDynamic.m_Max;
  815. if (vector.Distance(position, Math.CenterOfRectangle(locationMin, locationMax)) > 2000)
  816. continue;
  817. Debug.CleanupDrawShapes(m_DebugShapesLocations);
  818. locationMax[1] = locationMin[1] + 200.0; //! force the height of box for debug
  819. locationMin[1] = locationMin[1] - 50.0;
  820. m_DebugShapesLocations.Insert(Debug.DrawBoxEx(locationMin, locationMax, Colors.YELLOW, ShapeFlags.NOZWRITE|ShapeFlags.ONCE));
  821. }
  822. }
  823. else
  824. {
  825. Debug.CleanupDrawShapes(m_DebugShapesLocations);
  826. Debug.CleanupDrawShapes(m_DebugShapesLocationsVertices);
  827. }
  828. }
  829. private void DrawPolygonLocation(notnull DynamicMusicTrackData track)
  830. {
  831. vector first, current, last;
  832. int count = track.vertices.Count();
  833. foreach (int i, vector vertexPos : track.vertices)
  834. {
  835. vertexPos[1] = vertexPos[1] + 0.5;
  836. current = vertexPos;
  837. if (i == 0)
  838. first = vertexPos;
  839. else
  840. m_DebugShapesLocationsVertices.Insert(Debug.DrawLine(last, current, COLOR_WHITE, ShapeFlags.TRANSP|ShapeFlags.NOZWRITE|ShapeFlags.ONCE));
  841. last = current;
  842. }
  843. m_DebugShapesLocationsVertices.Insert(Debug.DrawLine(current, first, COLOR_WHITE, ShapeFlags.TRANSP|ShapeFlags.NOZWRITE|ShapeFlags.ONCE));
  844. }
  845. #ifdef ENABLE_LOGGING
  846. private void DMPDebugPrint(string message)
  847. {
  848. #ifdef DMP_DEBUG_PRINT
  849. Debug.Log(message);
  850. #endif
  851. }
  852. #endif
  853. #endif
  854. //!DEPRECATED
  855. private void CleanupDebugShapes(array<Shape> shapesArr)
  856. {
  857. Debug.CleanupDrawShapes(shapesArr);
  858. }
  859. void SetCategory(EDynamicMusicPlayerCategory category, bool forced)
  860. {
  861. DynamicMusicPlayerCategoryPlaybackData playbackData = new DynamicMusicPlayerCategoryPlaybackData();
  862. playbackData.m_Category = category;
  863. playbackData.m_Forced = forced;
  864. SetCategory(playbackData);
  865. }
  866. }
  867. //! for backward compatibility
  868. class DynamicMusicPlayerTimeOfDay : WorldDataDaytime {}