Adds support for a map rotation system. (#7162)

* Adds support for a map rotation system.
It is now the default way of selecting a map, map votes have been disabled.

* whoops

* Randomize the map it starts off with, too.
(it'd pick bagel every time otherwise)

* Address review

* remove knight from rotation due to it being an unmaintained map.

* minor cleanup
This commit is contained in:
Moony
2022-03-17 13:59:53 -05:00
committed by GitHub
parent afc0e3708e
commit 3721b3303b
5 changed files with 84 additions and 8 deletions

View File

@@ -71,7 +71,7 @@ namespace Content.Server.GameTicking
DefaultMap = _mapManager.CreateMap(); DefaultMap = _mapManager.CreateMap();
_mapManager.AddUninitializedMap(DefaultMap); _mapManager.AddUninitializedMap(DefaultMap);
var startTime = _gameTiming.RealTime; var startTime = _gameTiming.RealTime;
var maps = new List<GameMapPrototype>() { _gameMapManager.GetSelectedMapChecked(true) }; var maps = new List<GameMapPrototype>() { _gameMapManager.GetSelectedMapChecked(true, true) };
// Let game rules dictate what maps we should load. // Let game rules dictate what maps we should load.
RaiseLocalEvent(new LoadingMapsEvent(maps)); RaiseLocalEvent(new LoadingMapsEvent(maps));

View File

@@ -22,8 +22,11 @@ public sealed class GameMapManager : IGameMapManager
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
private readonly Queue<string> _previousMaps = new Queue<string>();
private GameMapPrototype _currentMap = default!; private GameMapPrototype _currentMap = default!;
private bool _currentMapForced; private bool _currentMapForced;
private bool _mapRotationEnabled;
private int _mapQueueDepth = 1;
public void Initialize() public void Initialize()
{ {
@@ -35,6 +38,25 @@ public sealed class GameMapManager : IGameMapManager
throw new ArgumentException($"Unknown map prototype {value} was selected!"); throw new ArgumentException($"Unknown map prototype {value} was selected!");
}, true); }, true);
_configurationManager.OnValueChanged(CCVars.GameMapForced, value => _currentMapForced = value, true); _configurationManager.OnValueChanged(CCVars.GameMapForced, value => _currentMapForced = value, true);
_configurationManager.OnValueChanged(CCVars.GameMapRotation, value => _mapRotationEnabled = value, true);
_configurationManager.OnValueChanged(CCVars.GameMapMemoryDepth, value =>
{
_mapQueueDepth = value;
// Drain excess.
while (_previousMaps.Count > _mapQueueDepth)
{
_previousMaps.Dequeue();
}
}, true);
var maps = AllVotableMaps().ToArray();
_random.Shuffle(maps);
foreach (var map in maps)
{
if (_previousMaps.Count >= _mapQueueDepth)
break;
_previousMaps.Enqueue(map.ID);
}
} }
public IEnumerable<GameMapPrototype> CurrentlyEligibleMaps() public IEnumerable<GameMapPrototype> CurrentlyEligibleMaps()
@@ -75,17 +97,18 @@ public sealed class GameMapManager : IGameMapManager
public void SelectRandomMap() public void SelectRandomMap()
{ {
var maps = CurrentlyEligibleMaps().ToList(); var maps = CurrentlyEligibleMaps().ToList();
_random.Shuffle(maps); _currentMap = _random.Pick(maps);
_currentMap = maps[0];
_currentMapForced = false; _currentMapForced = false;
} }
public GameMapPrototype GetSelectedMap() public GameMapPrototype GetSelectedMap()
{ {
return _currentMap; if (!_mapRotationEnabled || _currentMapForced)
return _currentMap;
return SelectMapInQueue() ?? CurrentlyEligibleMaps().First();
} }
public GameMapPrototype GetSelectedMapChecked(bool loud = false) public GameMapPrototype GetSelectedMapChecked(bool loud = false, bool markAsPlayed = false)
{ {
if (!_currentMapForced && !IsMapEligible(GetSelectedMap())) if (!_currentMapForced && !IsMapEligible(GetSelectedMap()))
{ {
@@ -100,7 +123,11 @@ public sealed class GameMapManager : IGameMapManager
} }
} }
return GetSelectedMap(); var map = GetSelectedMap();
if (markAsPlayed)
_previousMaps.Enqueue(map.ID);
return map;
} }
public bool CheckMapExists(string gameMap) public bool CheckMapExists(string gameMap)
@@ -127,4 +154,39 @@ public sealed class GameMapManager : IGameMapManager
else else
return gameMap.MapName; return gameMap.MapName;
} }
public int GetMapQueuePriority(string gameMapProtoName)
{
var i = 0;
foreach (var map in _previousMaps.Reverse())
{
if (map == gameMapProtoName)
return i;
i++;
}
return _mapQueueDepth;
}
public GameMapPrototype? SelectMapInQueue()
{
var eligible = CurrentlyEligibleMaps()
.Where(x => x.Votable)
.Select(x => (proto: x, weight: GetMapQueuePriority(x.ID)))
.OrderByDescending(x => x.weight).ToArray();
if (eligible.Length is 0)
return null;
var weight = eligible[0].weight;
return eligible.Where(x => x.Item2 == weight).OrderBy(x => x.proto.ID).First().proto;
}
private void EnqueueMap(string mapProtoName)
{
_previousMaps.Enqueue(mapProtoName);
while (_previousMaps.Count > _mapQueueDepth)
{
_previousMaps.Dequeue();
}
}
} }

View File

@@ -56,7 +56,7 @@ public interface IGameMapManager
/// Gets the currently selected map, double-checking if it can be used. /// Gets the currently selected map, double-checking if it can be used.
/// </summary> /// </summary>
/// <returns>selected map</returns> /// <returns>selected map</returns>
GameMapPrototype GetSelectedMapChecked(bool loud = false); GameMapPrototype GetSelectedMapChecked(bool loud = false, bool markAsPlayed = false);
/// <summary> /// <summary>
/// Checks if the given map exists /// Checks if the given map exists

View File

@@ -126,6 +126,19 @@ namespace Content.Shared.CCVar
public static readonly CVarDef<bool> public static readonly CVarDef<bool>
GameMapForced = CVarDef.Create("game.mapforced", false, CVar.SERVERONLY); GameMapForced = CVarDef.Create("game.mapforced", false, CVar.SERVERONLY);
/// <summary>
/// The depth of the queue used to calculate which map is next in rotation.
/// This is how long the game "remembers" that some map was put in play. Default is 16 rounds.
/// </summary>
public static readonly CVarDef<int>
GameMapMemoryDepth = CVarDef.Create("game.map_memory_depth", 16, CVar.SERVERONLY);
/// <summary>
/// Is map rotation enabled?
/// </summary>
public static readonly CVarDef<bool>
GameMapRotation = CVarDef.Create<bool>("game.map_rotation", true, CVar.SERVERONLY);
/// <summary> /// <summary>
/// Whether a random position offset will be applied to the station on roundstart. /// Whether a random position offset will be applied to the station on roundstart.
/// </summary> /// </summary>
@@ -562,7 +575,7 @@ namespace Content.Shared.CCVar
/// See vote.enabled, but specific to map votes /// See vote.enabled, but specific to map votes
/// </summary> /// </summary>
public static readonly CVarDef<bool> VoteMapEnabled = public static readonly CVarDef<bool> VoteMapEnabled =
CVarDef.Create("vote.map_enabled", true, CVar.SERVERONLY); CVarDef.Create("vote.map_enabled", false, CVar.SERVERONLY);
/// <summary> /// <summary>
/// The required ratio of the server that must agree for a restart round vote to go through. /// The required ratio of the server that must agree for a restart round vote to go through.

View File

@@ -88,6 +88,7 @@
!type:NanotrasenNameGenerator !type:NanotrasenNameGenerator
prefixCreator: '14' prefixCreator: '14'
mapPath: /Maps/knightship.yml mapPath: /Maps/knightship.yml
votable: false
minPlayers: 0 minPlayers: 0
maxPlayers: 8 maxPlayers: 8
overflowJobs: [] overflowJobs: []