1
0

GameMapManager.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. using System.Diagnostics;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Linq;
  4. using Content.Server.GameTicking;
  5. using Content.Shared.CCVar;
  6. using Robust.Server.Player;
  7. using Robust.Shared.Configuration;
  8. using Robust.Shared.ContentPack;
  9. using Robust.Shared.Prototypes;
  10. using Robust.Shared.Random;
  11. using Robust.Shared.Utility;
  12. namespace Content.Server.Maps;
  13. public sealed class GameMapManager : IGameMapManager
  14. {
  15. [Dependency] private readonly IEntityManager _entityManager = default!;
  16. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  17. [Dependency] private readonly IConfigurationManager _configurationManager = default!;
  18. [Dependency] private readonly IPlayerManager _playerManager = default!;
  19. [Dependency] private readonly IResourceManager _resMan = default!;
  20. [Dependency] private readonly IRobustRandom _random = default!;
  21. [ViewVariables(VVAccess.ReadOnly)]
  22. private readonly Queue<string> _previousMaps = new();
  23. [ViewVariables(VVAccess.ReadOnly)]
  24. private GameMapPrototype? _configSelectedMap;
  25. [ViewVariables(VVAccess.ReadOnly)]
  26. private GameMapPrototype? _selectedMap; // Don't change this value during a round!
  27. [ViewVariables(VVAccess.ReadOnly)]
  28. private bool _mapRotationEnabled;
  29. [ViewVariables(VVAccess.ReadOnly)]
  30. private int _mapQueueDepth = 1;
  31. private ISawmill _log = default!;
  32. public void Initialize()
  33. {
  34. _log = Logger.GetSawmill("mapsel");
  35. _configurationManager.OnValueChanged(CCVars.GameMap, value =>
  36. {
  37. if (TryLookupMap(value, out GameMapPrototype? map))
  38. {
  39. _configSelectedMap = map;
  40. return;
  41. }
  42. if (string.IsNullOrEmpty(value))
  43. {
  44. _configSelectedMap = default!;
  45. return;
  46. }
  47. if (_configurationManager.GetCVar<bool>(CCVars.UsePersistence))
  48. {
  49. var startMap = _configurationManager.GetCVar<string>(CCVars.PersistenceMap);
  50. _configSelectedMap = _prototypeManager.Index<GameMapPrototype>(startMap);
  51. var mapPath = new ResPath(value);
  52. if (_resMan.UserData.Exists(mapPath))
  53. {
  54. _configSelectedMap = _configSelectedMap.Persistence(mapPath);
  55. _log.Info($"Using persistence map from {value}");
  56. return;
  57. }
  58. // persistence save path doesn't exist so we just use the start map
  59. _log.Warning($"Using persistence start map {startMap} as {value} doesn't exist");
  60. return;
  61. }
  62. _log.Error($"Unknown map prototype {value} was selected!");
  63. }, true);
  64. _configurationManager.OnValueChanged(CCVars.GameMapRotation, value => _mapRotationEnabled = value, true);
  65. _configurationManager.OnValueChanged(CCVars.GameMapMemoryDepth, value =>
  66. {
  67. _mapQueueDepth = value;
  68. // Drain excess.
  69. while (_previousMaps.Count > _mapQueueDepth)
  70. {
  71. _previousMaps.Dequeue();
  72. }
  73. }, true);
  74. var maps = AllVotableMaps().ToArray();
  75. _random.Shuffle(maps);
  76. foreach (var map in maps)
  77. {
  78. if (_previousMaps.Count >= _mapQueueDepth)
  79. break;
  80. _previousMaps.Enqueue(map.ID);
  81. }
  82. }
  83. public IEnumerable<GameMapPrototype> CurrentlyEligibleMaps()
  84. {
  85. var maps = AllVotableMaps().Where(IsMapEligible).ToArray();
  86. return maps.Length == 0 ? AllMaps().Where(x => x.Fallback) : maps;
  87. }
  88. public IEnumerable<GameMapPrototype> AllVotableMaps()
  89. {
  90. var poolPrototype = _entityManager.System<GameTicker>().Preset?.MapPool ??
  91. _configurationManager.GetCVar(CCVars.GameMapPool);
  92. if (_prototypeManager.TryIndex<GameMapPoolPrototype>(poolPrototype, out var pool))
  93. {
  94. foreach (var map in pool.Maps)
  95. {
  96. if (!_prototypeManager.TryIndex<GameMapPrototype>(map, out var mapProto))
  97. {
  98. _log.Error($"Couldn't index map {map} in pool {poolPrototype}");
  99. continue;
  100. }
  101. yield return mapProto;
  102. }
  103. }
  104. else
  105. {
  106. throw new Exception($"Could not index map pool prototype {poolPrototype}!");
  107. }
  108. }
  109. public IEnumerable<GameMapPrototype> AllMaps()
  110. {
  111. return _prototypeManager.EnumeratePrototypes<GameMapPrototype>();
  112. }
  113. public GameMapPrototype? GetSelectedMap()
  114. {
  115. return _configSelectedMap ?? _selectedMap;
  116. }
  117. public void ClearSelectedMap()
  118. {
  119. _selectedMap = default!;
  120. }
  121. public bool TrySelectMapIfEligible(string gameMap)
  122. {
  123. if (!TryLookupMap(gameMap, out var map) || !IsMapEligible(map))
  124. return false;
  125. _selectedMap = map;
  126. return true;
  127. }
  128. public void SelectMap(string gameMap)
  129. {
  130. if (!TryLookupMap(gameMap, out var map))
  131. throw new ArgumentException($"The map \"{gameMap}\" is invalid!");
  132. _selectedMap = map;
  133. }
  134. public void SelectMapRandom()
  135. {
  136. var maps = CurrentlyEligibleMaps().ToList();
  137. _selectedMap = _random.Pick(maps);
  138. }
  139. public void SelectMapFromRotationQueue(bool markAsPlayed = false)
  140. {
  141. var map = GetFirstInRotationQueue();
  142. _selectedMap = map;
  143. if (markAsPlayed)
  144. EnqueueMap(map.ID);
  145. }
  146. public void SelectMapByConfigRules()
  147. {
  148. if (_mapRotationEnabled)
  149. {
  150. _log.Info("selecting the next map from the rotation queue");
  151. SelectMapFromRotationQueue(true);
  152. }
  153. else
  154. {
  155. _log.Info("selecting a random map");
  156. SelectMapRandom();
  157. }
  158. }
  159. public bool CheckMapExists(string gameMap)
  160. {
  161. return TryLookupMap(gameMap, out _);
  162. }
  163. private bool IsMapEligible(GameMapPrototype map)
  164. {
  165. return map.MaxPlayers >= _playerManager.PlayerCount &&
  166. map.MinPlayers <= _playerManager.PlayerCount &&
  167. map.Conditions.All(x => x.Check(map)) &&
  168. _entityManager.System<GameTicker>().IsMapEligible(map);
  169. }
  170. private bool TryLookupMap(string gameMap, [NotNullWhen(true)] out GameMapPrototype? map)
  171. {
  172. return _prototypeManager.TryIndex(gameMap, out map);
  173. }
  174. private int GetMapRotationQueuePriority(string gameMapProtoName)
  175. {
  176. var i = 0;
  177. foreach (var map in _previousMaps.Reverse())
  178. {
  179. if (map == gameMapProtoName)
  180. return i;
  181. i++;
  182. }
  183. return _mapQueueDepth;
  184. }
  185. private GameMapPrototype GetFirstInRotationQueue()
  186. {
  187. _log.Info($"map queue: {string.Join(", ", _previousMaps)}");
  188. var eligible = CurrentlyEligibleMaps()
  189. .Select(x => (proto: x, weight: GetMapRotationQueuePriority(x.ID)))
  190. .OrderByDescending(x => x.weight)
  191. .ToArray();
  192. _log.Info($"eligible queue: {string.Join(", ", eligible.Select(x => (x.proto.ID, x.weight)))}");
  193. // YML "should" be configured with at least one fallback map
  194. Debug.Assert(eligible.Length != 0, $"couldn't select a map with {nameof(GetFirstInRotationQueue)}()! No eligible maps and no fallback maps!");
  195. var weight = eligible[0].weight;
  196. return eligible.Where(x => x.Item2 == weight)
  197. .MinBy(x => x.proto.ID)
  198. .proto;
  199. }
  200. private void EnqueueMap(string mapProtoName)
  201. {
  202. _previousMaps.Enqueue(mapProtoName);
  203. while (_previousMaps.Count > _mapQueueDepth)
  204. {
  205. _previousMaps.Dequeue();
  206. }
  207. }
  208. }