Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 126 additions & 37 deletions MapVote.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Unity.VisualScripting;
using TMPro;
using UnityEngine.UI;
using Random = System.Random;

namespace MapVote {
[BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)]
Expand Down Expand Up @@ -46,11 +47,14 @@ internal sealed class MapVote : BaseUnityPlugin
public static NetworkedEvent? OnSyncVotes;
public static NetworkedEvent? OnSyncLastMapPlayed;
public static NetworkedEvent? OnStartCountdown;
public static NetworkedEvent? OnMapsRandomized;
public static NetworkedEvent? OnPlayerConnected;

// Configs
public static ConfigEntry<int> VotingTime;
public static ConfigEntry<bool> HideInMenu;
public static ConfigEntry<bool> NoRepeatedMaps;
public static ConfigEntry<int> VoteableLevelNumber;

// Vote Data
public static VotesDictionary CurrentVotes = new() { };
Expand All @@ -67,6 +71,7 @@ internal sealed class MapVote : BaseUnityPlugin
public static bool ShouldHookRunMangerSetRunLevel = false;

public static string? LastMapPlayed;
public static List<string> CurrentVoteLevels = new();

public static MapVote Instance;

Expand Down Expand Up @@ -126,6 +131,7 @@ public void Awake()
VotingTime = Config.Bind("General", "Voting Time", 10, new ConfigDescription("The amount of seconds until the voting ends, after the first player voted.", new AcceptableValueRange<int>(3, 30)));
HideInMenu = Config.Bind("General", "Hide in Menu", false, new ConfigDescription("When true - hides the Menu in the lobby menu and randomly selects a random map - Voting is still enabled in the truck"));
NoRepeatedMaps = Config.Bind("General", "No Repeated Maps", false, new ConfigDescription("When true - disallows votes for the most recently played map - You won't play the same map twice in a row"));
VoteableLevelNumber = Config.Bind("General", "Number of voteable levels", 0, new ConfigDescription("The number of levels that can be voted for. 0 = all levels", new AcceptableValueRange<int>(0, 10)));

CompatibilityPatches.RunPatches(Chainloader.PluginInfos.Select(x => (x.Key)).ToList());

Expand Down Expand Up @@ -157,6 +163,8 @@ internal static void Initialize()
OnSyncVotes = new NetworkedEvent("OnSyncVotes", HandleOnSyncVotes);
OnSyncLastMapPlayed = new NetworkedEvent("OnSyncLastMapPlayed", HandleOnSyncLastMapPlayed);
OnStartCountdown = new NetworkedEvent("OnStartCountdown", HandleOnStartCountdown);
OnMapsRandomized = new NetworkedEvent("OnMapsRandomized", HandleOnMapsRandomized);
OnPlayerConnected = new NetworkedEvent("OnPlayerConnected", HandleOnPlayerConnected);

if(!HideInMenu.Value)
{
Expand All @@ -178,6 +186,27 @@ public static void Reset()
OwnVoteLevel = null;
UpdateButtonLabels();
}

public static void HandleOnPlayerConnected(EventData data)
{
if (SemiFunc.IsMasterClientOrSingleplayer())
{
Logger.LogMessage("Received player connected event, sending level data");
OnMapsRandomized?.RaiseEvent(CurrentVoteLevels.ToArray(), NetworkingEvents.RaiseOthers, SendOptions.SendReliable);
}
}

private static void HandleOnMapsRandomized(EventData data)
{
if (SemiFunc.IsMasterClientOrSingleplayer())
{
return;
}
CurrentVoteLevels = ((string[])data.CustomData).ToList();
Logger.LogMessage($"Randomized maps received, generating {CurrentVoteLevels.Count} vote options");
CreateVotePopup(true);
}

private static void HandleOnSyncLastMapPlayed(EventData data)
{
string lastMap = (string)data.CustomData;
Expand Down Expand Up @@ -304,6 +333,36 @@ public static IEnumerator StartCountdown()
}
}

public static List<Level> GetLevels(bool reRandomize = true)
{
var runManager = FindObjectOfType<RunManager>();
if (!reRandomize)
{
var levels = runManager.levels.Where(l => CurrentVoteLevels.Contains(l.name)).ToList();
return levels;
}

var actualLevelNumber = runManager.levels.Count < VoteableLevelNumber.Value ? runManager.levels.Count : VoteableLevelNumber.Value;
var levelIndexes = new List<int>();
var totalLevels = runManager.levels.Count;
for (var i = 0; i < totalLevels; i++)
{
if (!HasBeenLastPlayed(runManager.levels[i].name)) levelIndexes.Add(i);
}

var random = new Random();
for (var i = levelIndexes.Count - 1; i > 0; i--)
{
var randomIndex = random.Next(0, i + 1);
(levelIndexes[i], levelIndexes[randomIndex]) = (levelIndexes[randomIndex], levelIndexes[i]);
}

var selectedIndexes = levelIndexes.Take(actualLevelNumber).ToArray();
var levelNumber = VoteableLevelNumber.Value > 0 ? VoteableLevelNumber.Value.ToString() : "all";
Logger.LogMessage($"Starting a vote for {levelNumber} levels");
return VoteableLevelNumber.Value > 0 ? runManager.levels.Where(l => runManager.levels.IndexOf(l) >= 0 && selectedIndexes.Contains(runManager.levels.IndexOf(l))).ToList() : runManager.levels;
}

public static void CreateVotePopup(bool isInMenu = false)
{
MenuAPI.CloseAllPagesAddedOnTop();
Expand All @@ -319,13 +378,46 @@ public static void CreateVotePopup(bool isInMenu = false)
{
GameDirector.instance.DisableInput = true;
}

if (SemiFunc.IsMasterClientOrSingleplayer())
{
if (CurrentVoteLevels.Count != VoteableLevelNumber.Value)
{
CurrentVotes.Values.Clear();
CurrentVoteLevels = GetLevels().Select(x => x.name).ToList();
Logger.LogMessage($"{CurrentVoteLevels.Count} random maps selected, sending to clients");
}
else
{
CurrentVoteLevels = GetLevels(false).Select(x => x.name).ToList();
}

VotePopup = MenuAPI.CreateREPOPopupPage("Next map", true, !isInMenu, 0f, isInMenu ? new Vector2(40f, 0f) : new Vector2(-100f,0f));
var runManger = FindObjectOfType<RunManager>();
OnMapsRandomized?.RaiseEvent(CurrentVoteLevels.ToArray(), NetworkingEvents.RaiseOthers, SendOptions.SendReliable);
GenerateVoteOptions(isInMenu);
}
else
{
GenerateVoteOptions(isInMenu);
}

VotePopup!.AddElement(parent =>
{
VotingTimeLabel = MenuAPI.CreateREPOLabel(null, parent, new Vector2(isInMenu ? 394f : 254f, 30f));
});

var levels = runManger.levels;
VotePopup.OpenPage(true);
UpdateButtonLabels();
VotePopup.GetComponent<MenuPage>().PageStateSet(MenuPage.PageState.Active);
}

public static void GenerateVoteOptions(bool isInMenu = false)
{
var runManager = FindObjectOfType<RunManager>();
var levels = GetLevels(false);
levels.AddRange(runManager.levels.Where(l => !CurrentVoteLevels.Contains(l.name)));
VotePopup = MenuAPI.CreateREPOPopupPage("Next map", true, !isInMenu, 0f, isInMenu ? new Vector2(40f, 0f) : new Vector2(-100f,0f));
// Generate Vote Options from Levels
var counter = 0;
foreach (var (level, index) in levels.Select((level, index) => (level, index)))
{
var name = level.name;
Expand All @@ -340,7 +432,7 @@ public static void CreateVotePopup(bool isInMenu = false)
OnVoteEvent?.RaiseEvent(name, NetworkingEvents.RaiseAll, SendOptions.SendReliable);
}, parent);

if(HasBeenLastPlayed(name))
if(HasBeenLastPlayed(name) || !CurrentVoteLevels.Contains(name))
{
btn.gameObject.GetComponent<MenuButton>().disabled = true;
}
Expand All @@ -355,45 +447,42 @@ public static void CreateVotePopup(bool isInMenu = false)
VoteOptionButtons.Add(new VoteOptionButton(name, 0, btn));
return btn.rectTransform;
});
}

// Generate "Random" Vote Option
VotePopup.AddElementToScrollView(parent =>
{
var btn = MenuAPI.CreateREPOButton(null, () => {
if (DisableInput)
if (counter == CurrentVoteLevels.Count - 1 || (VoteableLevelNumber.Value == 0 && counter == levels.Count - 1))
{
// Generate "Random" Vote Option
VotePopup.AddElementToScrollView(parent =>
{
return;
}
OwnVoteLevel = VOTE_RANDOM_LABEL;
OnVoteEvent?.RaiseEvent(VOTE_RANDOM_LABEL, NetworkingEvents.RaiseAll, SendOptions.SendReliable);
}, parent);

var layoutGroup = btn.AddComponent<HorizontalLayoutGroup>();
layoutGroup.spacing = 235f;

var votesLabel = GameObject.Instantiate(btn.labelTMP.gameObject, btn.transform);
var lbl = votesLabel.GetComponent<TextMeshProUGUI>();
lbl.horizontalAlignment = HorizontalAlignmentOptions.Right;

VoteOptionButtons.Add(new VoteOptionButton(VOTE_RANDOM_LABEL, 0, btn, true));
return btn.rectTransform;
});

VotePopup.AddElement(parent =>
{
VotingTimeLabel = MenuAPI.CreateREPOLabel(null, parent, new Vector2(isInMenu ? 394f : 254f, 30f));
});

VotePopup.OpenPage(true);
UpdateButtonLabels();
VotePopup.GetComponent<MenuPage>().PageStateSet(MenuPage.PageState.Active);
var btn = MenuAPI.CreateREPOButton(null, () => {
if (DisableInput)
{
return;
}
OwnVoteLevel = VOTE_RANDOM_LABEL;
OnVoteEvent?.RaiseEvent(VOTE_RANDOM_LABEL, NetworkingEvents.RaiseAll, SendOptions.SendReliable);
}, parent);

var layoutGroup = btn.AddComponent<HorizontalLayoutGroup>();
layoutGroup.spacing = 235f;

var votesLabel = GameObject.Instantiate(btn.labelTMP.gameObject, btn.transform);
var lbl = votesLabel.GetComponent<TextMeshProUGUI>();
lbl.horizontalAlignment = HorizontalAlignmentOptions.Right;

VoteOptionButtons.Add(new VoteOptionButton(VOTE_RANDOM_LABEL, 0, btn, true));
return btn.rectTransform;
});
}
counter++;
}

}

public static void UpdateButtonLabels()
{
VoteOptionButtons.ForEach(b =>
{
b.UpdateLabel(false, HasBeenLastPlayed(b.Level));
if (HasBeenLastPlayed(b.Level) || (VoteableLevelNumber.Value > 0 && !CurrentVoteLevels.Contains(b.Level) && !b.IsRandomButton)) b.Disable();
b.UpdateLabel();
});
}

Expand Down
4 changes: 4 additions & 0 deletions Patches/SteamManagerPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ public static void PostfixJoiningPlayer()
MapVote.OnSyncVotes?.RaiseEvent(MapVote.CurrentVotes.Values, NetworkingEvents.RaiseAll, SendOptions.SendReliable);
MapVote.OnSyncLastMapPlayed?.RaiseEvent(MapVote.LastMapPlayed, NetworkingEvents.RaiseOthers, SendOptions.SendReliable);
}
else
{
MapVote.OnPlayerConnected?.RaiseEvent("", NetworkingEvents.RaiseAll, SendOptions.SendReliable);
}
}
}
}
29 changes: 22 additions & 7 deletions VoteOptionButton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ internal sealed class VoteOptionButton
public string Level { get; set; }
public REPOButton Button { get; set; }
public bool IsRandomButton { get; set; }
public VoteOptionButton(string _level, int _votes, REPOButton _button, bool _isRandomButton = false)
public bool Disabled { get; set; }
public VoteOptionButton(string _level, int _votes, REPOButton _button, bool _isRandomButton = false, bool _isDisabled = false)
{
Level = _level;
Button = _button;
IsRandomButton = _isRandomButton;
Disabled = _isDisabled;
}

public int GetVotes(Dictionary<int, string> votes)
Expand All @@ -34,28 +36,28 @@ public int GetVotes(Dictionary<int, string> votes)
return votesNum;
}

public void UpdateLabel(bool _highlight = false, bool _disabled = false)
public void UpdateLabel(bool _highlight = false)
{
var votes = MapVote.CurrentVotes.Values;
var ownVote = MapVote.OwnVoteLevel == Level;

var playerCount = Math.Max(Math.Min(GameDirector.instance.PlayerList.Count, 12), 4);
var votesCount = GetVotes(votes);
Color mainColor = _disabled ? Color.gray : (_highlight == true ? Color.green : ownVote ? Color.yellow : Color.white);
Color mainColor = Disabled ? Color.gray : (_highlight == true ? Color.green : ownVote ? Color.yellow : Color.white);

StringBuilder sb = new();

if (_disabled) sb.Append("<s>");
if (Disabled) sb.Append("<s>");

sb.Append($"<mspace=0.25em>[{Utilities.ColorString((ownVote || _highlight ? "X" : " "), mainColor)}]</mspace> ");
if(!_disabled) sb.Append($"<color={LevelColorDictionary.GetColor(Level)}>");
if(!Disabled) sb.Append($"<color={LevelColorDictionary.GetColor(Level)}>");
sb.Append($"{(IsRandomButton ? MapVote.VOTE_RANDOM_LABEL : Utilities.RemoveLevelPrefix(Level))}");
if (!_disabled)
if (!Disabled)
{
sb.Append("</color>");
}

if (_disabled) sb.Append("</s>");
if (Disabled) sb.Append("</s>");

var votesLabel = Button.transform.GetChild(1);
votesLabel.GetComponent<TextMeshProUGUI>().text = $"{Utilities.ColorString(new string('I', votesCount), Color.green)}{Utilities.ColorString(new string('I', playerCount - votesCount), Color.white)}";
Expand All @@ -64,4 +66,17 @@ public void UpdateLabel(bool _highlight = false, bool _disabled = false)
$"{sb.ToString()}";
}
}

public static class VoteOptionButtonExtensions
{
internal static void Disable(this VoteOptionButton button)
{
button.Disabled = true;
}

internal static void Enable(this VoteOptionButton button)
{
button.Disabled = false;
}
}
}