-
-
Notifications
You must be signed in to change notification settings - Fork 136
Broadcast and filter game options #874
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
9fa3227
dfb6184
54c2562
dda5b0c
60b23c5
fec6165
9470f99
d3a1a17
5ae535f
16caf05
1b2ca9f
12d5448
9e3077a
f694b7f
8936ad1
5e8966c
7b5b445
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ public class UserINISettings | |
| public const string AUDIO = "Audio"; | ||
| public const string COMPATIBILITY = "Compatibility"; | ||
| public const string GAME_FILTERS = "GameFilters"; | ||
| public const string GAME_OPTION_FILTERS = "GameOptionFilters"; | ||
| private const string FAVORITE_MAPS = "FavoriteMaps"; | ||
|
|
||
| private const bool DEFAULT_SHOW_FRIENDS_ONLY_GAMES = false; | ||
|
|
@@ -267,6 +268,34 @@ protected UserINISettings(IniFile iniFile) | |
|
|
||
| public IntRangeSetting MaxPlayerCount { get; private set; } | ||
|
|
||
| /************************/ | ||
| /* GAME OPTION FILTERS */ | ||
| /************************/ | ||
|
|
||
| /// <summary> | ||
| /// Gets the filter value for a checkbox game option. | ||
| /// Returns 0 for "All", 1 for "On", 2 for "Off". | ||
| /// </summary> | ||
| public int GetCheckboxFilterValue(string optionName) => SettingsIni.GetIntValue(GAME_OPTION_FILTERS, optionName, 0); | ||
|
|
||
| /// <summary> | ||
| /// Sets the filter value for a checkbox game option. | ||
| /// 0 = "All", 1 = "On", 2 = "Off". | ||
| /// </summary> | ||
| public void SetCheckboxFilterValue(string optionName, int value) => SettingsIni.SetIntValue(GAME_OPTION_FILTERS, optionName, value); | ||
|
|
||
| /// <summary> | ||
| /// Gets the filter value for a dropdown game option. | ||
| /// Returns -1 for "All", or the selected index. | ||
| /// </summary> | ||
| public int GetDropdownFilterValue(string optionName) => SettingsIni.GetIntValue(GAME_OPTION_FILTERS, optionName, -1); | ||
|
|
||
| /// <summary> | ||
| /// Sets the filter value for a dropdown game option. | ||
| /// -1 = "All", or the selected index. | ||
| /// </summary> | ||
| public void SetDropdownFilterValue(string optionName, int value) => SettingsIni.SetIntValue(GAME_OPTION_FILTERS, optionName, value); | ||
|
|
||
| /********/ | ||
| /* MISC */ | ||
| /********/ | ||
|
|
@@ -424,7 +453,8 @@ public bool IsGameFiltersApplied() | |
| || HideLockedGames.Value != DEFAULT_HIDE_LOCKED_GAMES | ||
| || HidePasswordedGames.Value != DEFAULT_HIDE_PASSWORDED_GAMES | ||
| || HideIncompatibleGames.Value != DEFAULT_HIDE_INCOMPATIBLE_GAMES | ||
| || MaxPlayerCount.Value != DEFAULT_MAX_PLAYER_COUNT; | ||
| || MaxPlayerCount.Value != DEFAULT_MAX_PLAYER_COUNT | ||
| || HasGameOptionFilters(); | ||
|
|
||
| public void ResetGameFilters() | ||
| { | ||
|
|
@@ -433,6 +463,26 @@ public void ResetGameFilters() | |
| HideIncompatibleGames.Value = DEFAULT_HIDE_INCOMPATIBLE_GAMES; | ||
| HidePasswordedGames.Value = DEFAULT_HIDE_PASSWORDED_GAMES; | ||
| MaxPlayerCount.Value = DEFAULT_MAX_PLAYER_COUNT; | ||
| ResetGameOptionFilters(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Checks if any game option filters are set. | ||
| /// </summary> | ||
| private bool HasGameOptionFilters() | ||
| { | ||
| var section = SettingsIni.GetSection(GAME_OPTION_FILTERS); | ||
| return section != null && section.Keys.Count > 0; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Clears all game option filters. | ||
| /// </summary> | ||
| private void ResetGameOptionFilters() | ||
| { | ||
| var section = SettingsIni.GetSection(GAME_OPTION_FILTERS); | ||
| if (section != null) | ||
| section.RemoveAllKeys(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. section?.RemoveAllKeys();
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed, thanks. |
||
| } | ||
|
|
||
| /// <summary> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,7 +57,43 @@ public bool AllowScoring | |
|
|
||
| private string enabledSpawnIniValue = "True"; | ||
| private string disabledSpawnIniValue = "False"; | ||
|
|
||
|
|
||
| /// <summary> | ||
| /// Whether this checkbox should be included in the GAME broadcast. | ||
| /// </summary> | ||
| public bool BroadcastToLobby { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// Whether the icon should be shown in the game list. | ||
| /// </summary> | ||
| public bool IconShownInGameList { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// Whether the icon should be shown on the right side of the game list. | ||
| /// Only applies if IconShownInGameList is true. | ||
| /// </summary> | ||
| public bool IconShownInGameListOnRight { get; private set; } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clever customisation!
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! Perhaps it would be better to have both "ShownInGameListLHS" and "ShownInGameListRHS". What do you think?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure, is there a use for having both? |
||
|
|
||
| /// <summary> | ||
| /// Whether the icon should be shown in the game information panel. | ||
| /// </summary> | ||
| public bool IconShownInGameInfo { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// Whether the icon should be shown in the game filters panel. | ||
| /// </summary> | ||
| public bool IconShownInFilters { get; private set; } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I know I said previously that it would be good to have it customisable, but I am not sure now. Do you think there would be any case when the modder or game maintainer would want to have an icon in game list but not have the icon anywhere else? ATM I am not sure if this makes sense, because the game info/filters icon work as legend, so probably could omit that. Also I think we could draw the icons near game options in the lobby themselves the same way?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think we keep the settings but make it apply to the whole thing not just the icon. So "ShownInGameInfo" instead of "IconShownInGameInfo", etc. It's programmed a bit like that already - hence the missing Credits in that screenshot. I was having hassles with icons in the game lobby. I'll take another look.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the filter and the game info should be switched on by the same option, otherwise it would be pretty user unfriendly to have something in filter but not in game info, and vice versa. Same for icons in the list - if the icon is shown in the game list - it must have the same icon (working as legend) in filter and in game info, so the users don't get confused on the meaning. |
||
|
|
||
| /// <summary> | ||
| /// The texture name for the icon when setting is enabled. | ||
| /// </summary> | ||
| public string EnabledIcon { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// The texture name for the icon when setting is disabled. | ||
| /// </summary> | ||
| public string DisabledIcon { get; private set; } | ||
|
|
||
| protected override void ParseControlINIAttribute(IniFile iniFile, string key, string value) | ||
| { | ||
| switch (key) | ||
|
|
@@ -85,6 +121,27 @@ protected override void ParseControlINIAttribute(IniFile iniFile, string key, st | |
| case "MapScoringMode": | ||
| mapScoringMode = (CheckBoxMapScoringMode)Enum.Parse(typeof(CheckBoxMapScoringMode), value); | ||
| return; | ||
| case "BroadcastToLobby": | ||
| BroadcastToLobby = Conversions.BooleanFromString(value, false); | ||
| return; | ||
| case "IconShownInGameList": | ||
| IconShownInGameList = Conversions.BooleanFromString(value, false); | ||
| return; | ||
| case "IconShownInGameListOnRight": | ||
| IconShownInGameListOnRight = Conversions.BooleanFromString(value, false); | ||
| return; | ||
| case "IconShownInGameInfo": | ||
| IconShownInGameInfo = Conversions.BooleanFromString(value, false); | ||
| return; | ||
| case "IconShownInFilters": | ||
| IconShownInFilters = Conversions.BooleanFromString(value, false); | ||
| return; | ||
| case "EnabledIcon": | ||
| EnabledIcon = value; | ||
| return; | ||
| case "DisabledIcon": | ||
| DisabledIcon = value; | ||
| return; | ||
| } | ||
|
|
||
| base.ParseControlINIAttribute(iniFile, key, value); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,26 @@ public GameSessionDropDown(WindowManager windowManager) : base(windowManager) { | |
|
|
||
| private int defaultIndex; | ||
|
|
||
| /// <summary> | ||
| /// Whether this dropdown should be included in the GAME broadcast. | ||
| /// </summary> | ||
| public bool BroadcastToLobby { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// Whether the icon should be shown in the game information panel. | ||
| /// </summary> | ||
| public bool IconShownInGameInfo { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// Whether the icon should be shown in the game filters panel. | ||
| /// </summary> | ||
| public bool IconShownInFilters { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// The texture name for the icon. | ||
| /// </summary> | ||
| public string Icon { get; private set; } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think for this to be more useful, we have to have not one icon per dropdown but actually an icon per option in the dropdown. If the icon is missing -- then no icon would be shown. This would also streamline the behavior between the checkbox and the dropdown. The icon could be then drawn as ItemTexture or however the option was called.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks and fixed. |
||
|
|
||
| protected override void ParseControlINIAttribute(IniFile iniFile, string key, string value) | ||
| { | ||
| // shorthand for localization function | ||
|
|
@@ -76,6 +96,18 @@ static string Localize(XNAControl control, string attributeName, string defaultV | |
| case "OptionName": | ||
| OptionName = Localize(this, "OptionName", value); | ||
| return; | ||
| case "BroadcastToLobby": | ||
| BroadcastToLobby = Conversions.BooleanFromString(value, false); | ||
| return; | ||
| case "IconShownInGameInfo": | ||
| IconShownInGameInfo = Conversions.BooleanFromString(value, false); | ||
| return; | ||
| case "IconShownInFilters": | ||
| IconShownInFilters = Conversions.BooleanFromString(value, false); | ||
| return; | ||
| case "Icon": | ||
| Icon = value; | ||
| return; | ||
| } | ||
|
|
||
| base.ParseControlINIAttribute(iniFile, key, value); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -717,8 +717,9 @@ private void BroadcastGame() | |||
| sb.Append(0); // LoadedGameId | ||||
| sb.Append(";"); | ||||
| sb.Append(ClientConfiguration.Instance.DefaultSkillLevelIndex); // we don't know the original skill level | ||||
| sb.Append(";"); | ||||
| sb.Append(string.Empty); // MapHash | ||||
| sb.Append(";"); // Map SHA1 | ||||
| sb.Append(";"); // Packed checkbox values (empty for loaded lobbies) | ||||
| sb.Append(";"); // Dropdown indices (empty for loaded lobbies) | ||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it necessary to separate dropdowns and indices like this? They are essentially both the same, checkbox is an option with 0, 1 states, and dropdown has 0, 1, 2, ... etc. I've created an interface for game settings specifically to generalize this kind of behavior. What do you think?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not needed. That stuff was mostly copied from the existing game options broadcast which had it split. See:
RE the interface - makes sense. I was working on an outdated copy and didn't see those changes. Hopefully 60b23c5 sorts it as you imagined. |
||||
|
|
||||
| broadcastChannel.SendCTCPMessage(sb.ToString(), QueuedMessageType.SYSTEM_MESSAGE, 20); | ||||
| } | ||||
|
|
||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -191,12 +191,12 @@ public override void Initialize() | |
| btnJoinGame.Right - btnNewGame.X, btnNewGame.Y - 47 | ||
| ); | ||
|
|
||
| panelGameFilters = new GameFiltersPanel(WindowManager); | ||
| panelGameFilters = new GameFiltersPanel(WindowManager, gameLobby); | ||
| panelGameFilters.Name = nameof(panelGameFilters); | ||
| panelGameFilters.ClientRectangle = gameListRectangle; | ||
| panelGameFilters.Disable(); | ||
|
|
||
| lbGameList = new GameListBox(WindowManager, mapLoader, localGameID, HostedGameMatches); | ||
| lbGameList = new GameListBox(WindowManager, mapLoader, localGameID, gameLobby, HostedGameMatches); | ||
| lbGameList.Name = nameof(lbGameList); | ||
| lbGameList.ClientRectangle = gameListRectangle; | ||
| lbGameList.PanelBackgroundDrawMode = PanelBackgroundImageDrawMode.STRETCHED; | ||
|
|
@@ -421,6 +421,12 @@ private void TbGameSearch_InputReceived(object sender, EventArgs e) | |
| lbGameList.ViewTop = 0; | ||
| } | ||
|
|
||
|
|
||
| /// <summary> | ||
| /// Checks if a hosted game matches the current filter criteria. | ||
| /// </summary> | ||
| /// <param name="hg">The hosted game to check.</param> | ||
| /// <returns>True if the game matches the filter criteria, false otherwise.</returns> | ||
| private bool HostedGameMatches(GenericHostedGame hg) | ||
| { | ||
| // friends list takes priority over other filters below | ||
|
|
@@ -439,6 +445,9 @@ private bool HostedGameMatches(GenericHostedGame hg) | |
| if (hg.MaxPlayers > UserINISettings.Instance.MaxPlayerCount.Value) | ||
| return false; | ||
|
|
||
| if (hg is HostedCnCNetGame cncnetGame && !GameOptionsMatch(cncnetGame)) | ||
| return false; | ||
|
|
||
| string textUpper = tbGameSearch?.Text?.ToUpperInvariant(); | ||
|
|
||
| string translatedGameMode = string.IsNullOrEmpty(hg.GameMode) | ||
|
|
@@ -460,6 +469,62 @@ private bool HostedGameMatches(GenericHostedGame hg) | |
| hg.Players.Any(pl => pl.ToUpperInvariant().Equals(textUpper, StringComparison.Ordinal)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Checks if a game's broadcast options match the current filter criteria. | ||
| /// </summary> | ||
| /// <param name="game">The hosted game to check.</param> | ||
| /// <returns>True if the game matches the filter criteria, false otherwise.</returns> | ||
| private bool GameOptionsMatch(HostedCnCNetGame game) | ||
| { | ||
| // checkbox filters | ||
| var broadcastableCheckboxes = gameLobby.CheckBoxes.Where(cb => cb.BroadcastToLobby).ToList(); | ||
| if (game.BroadcastedCheckboxValues != null && broadcastableCheckboxes.Count > 0) | ||
| { | ||
| for (int i = 0; i < broadcastableCheckboxes.Count; i++) | ||
| { | ||
| if (i >= game.BroadcastedCheckboxValues.Length) | ||
| break; | ||
|
|
||
| var checkbox = broadcastableCheckboxes[i]; | ||
| int filterValue = UserINISettings.Instance.GetCheckboxFilterValue(checkbox.Name); | ||
|
|
||
| // 0 = All, 1 = On, 2 = Off | ||
| if (filterValue == 0) | ||
| continue; | ||
|
|
||
| bool isChecked = game.BroadcastedCheckboxValues[i]; | ||
|
|
||
| bool shouldBeChecked = filterValue == 1; | ||
| if (isChecked != shouldBeChecked) | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| // dropdown filters | ||
| var broadcastableDropdowns = gameLobby.DropDowns.Where(dd => dd.BroadcastToLobby).ToList(); | ||
| if (game.BroadcastedDropdownIndices != null && broadcastableDropdowns.Count > 0) | ||
| { | ||
| for (int i = 0; i < broadcastableDropdowns.Count; i++) | ||
| { | ||
| if (i >= game.BroadcastedDropdownIndices.Length) | ||
| break; | ||
|
|
||
| var dropdown = broadcastableDropdowns[i]; | ||
| int filterValue = UserINISettings.Instance.GetDropdownFilterValue(dropdown.Name); | ||
|
|
||
| // -1 = All, otherwise check for exact match | ||
| if (filterValue == -1) | ||
| continue; | ||
|
|
||
| int gameSelectedIndex = game.BroadcastedDropdownIndices[i]; | ||
|
|
||
| if (gameSelectedIndex != filterValue) | ||
| return false; | ||
| } | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this could be deduplicated I think?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 60b23c5 sorts it. |
||
|
|
||
| return true; | ||
| } | ||
|
|
||
| private void OnCnCNetGameCountUpdated(object sender, PlayerCountEventArgs e) => UpdateOnlineCount(e.PlayerCount); | ||
|
|
||
|
|
@@ -1504,7 +1569,7 @@ private void GameBroadcastChannel_CTCPReceived(object sender, ChannelCTCPEventAr | |
| string msg = e.Message.Substring(5); // Cut out GAME part | ||
| string[] splitMessage = msg.Split(new char[] { ';' }); | ||
|
|
||
| if (splitMessage.Length != 13) | ||
| if (splitMessage.Length != 15) | ||
| { | ||
| Logger.Log("Ignoring CTCP game message because of an invalid amount of parameters."); | ||
|
|
||
|
|
@@ -1551,6 +1616,43 @@ private void GameBroadcastChannel_CTCPReceived(object sender, ChannelCTCPEventAr | |
| int skillLevel = int.Parse(splitMessage[11]); | ||
| string mapHash = splitMessage[12]; | ||
|
|
||
| bool[] checkboxValues = null; | ||
| int[] dropdownIndices = null; | ||
|
|
||
| // Games with different versions may have different option counts, so ignore | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not only with different versions, but the LocalGame can be different too, as the client has the ability to monitor other LocalGames game listings |
||
| if (gameVersion == ProgramConstants.GAME_VERSION) | ||
| { | ||
| // packed checkbox values | ||
| if (!string.IsNullOrEmpty(splitMessage[13])) | ||
| { | ||
| string[] checkboxStrings = splitMessage[13].Split(','); | ||
| int[] packedCheckboxes = new int[checkboxStrings.Length]; | ||
| for (int i = 0; i < checkboxStrings.Length; i++) | ||
| packedCheckboxes[i] = int.Parse(checkboxStrings[i]); | ||
|
|
||
| int localCheckboxCount = gameLobby.CheckBoxes.Count(cb => cb.BroadcastToLobby); | ||
| checkboxValues = new bool[localCheckboxCount]; | ||
| for (int i = 0; i < localCheckboxCount; i++) | ||
| { | ||
| int packedIndex = i / 32; | ||
| int bitIndex = i % 32; | ||
| if (packedIndex < packedCheckboxes.Length) | ||
| checkboxValues[i] = (packedCheckboxes[packedIndex] & (1 << bitIndex)) != 0; | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you explain what is happening here? I see bitpacking which is a clever optimisation, but apart from that I have no idea
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
|
|
||
| // dropdown indices | ||
| if (!string.IsNullOrEmpty(splitMessage[14])) | ||
| { | ||
| string[] dropdownStrings = splitMessage[14].Split(','); | ||
| int localDropdownCount = gameLobby.DropDowns.Count(dd => dd.BroadcastToLobby); | ||
| int dropdownCount = Math.Min(dropdownStrings.Length, localDropdownCount); | ||
| dropdownIndices = new int[dropdownCount]; | ||
| for (int i = 0; i < dropdownCount; i++) | ||
| dropdownIndices[i] = int.Parse(dropdownStrings[i]); | ||
| } | ||
| } | ||
|
|
||
| CnCNetGame cncnetGame = gameCollection.GameList.Find(g => g.GameBroadcastChannel == channel.ChannelName); | ||
|
|
||
| if (cncnetGame == null) | ||
|
|
@@ -1611,6 +1713,8 @@ private void GameBroadcastChannel_CTCPReceived(object sender, ChannelCTCPEventAr | |
| game.Incompatible = cncnetGame == localGame && game.GameVersion != ProgramConstants.GAME_VERSION; | ||
| game.TunnelServer = tunnel; | ||
| game.SkillLevel = skillLevel; | ||
| game.BroadcastedCheckboxValues = checkboxValues; | ||
| game.BroadcastedDropdownIndices = dropdownIndices; | ||
|
|
||
| if (isClosed) | ||
| { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
int?andbool?sounds like a more obvious choice here. Curious to hear what you think about that.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True - nullable is better. Fixed in dda5b0c.