From: miniduikboot Date: Sun, 26 Nov 2023 22:01:54 +0000 (+0100) Subject: Implement event to detect game option changes X-Git-Tag: v1.9.0~6^2~6 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=5edb40938832ce3d9404632b3316cda904d37f8f;p=rhonda%2Fimpostor.git Implement event to detect game option changes This can be triggered by both RPC 02 and a(nother) plugin calling SyncSettings to force certain settings on the host. Users of this API could cause this event to recurse by calling SyncSettingsAsync inside an event handler, so we prevent such reasonable foreseeable misuse. --- diff --git a/src/Impostor.Api/Events/Game/IGameOptionsChangedEvent.cs b/src/Impostor.Api/Events/Game/IGameOptionsChangedEvent.cs new file mode 100644 index 0000000..14d7b0c --- /dev/null +++ b/src/Impostor.Api/Events/Game/IGameOptionsChangedEvent.cs @@ -0,0 +1,51 @@ +using Impostor.Api.Games; +using Impostor.Api.Net.Messages.Rpcs; + +namespace Impostor.Api.Events +{ + /// + /// This event is triggered when game options are going to be changed. + /// + /// + /// Be careful when calling SyncSettingsAsync while handling this event, + /// as it may call this event again in some cases. + /// + public interface IGameOptionsChangedEvent : IGameEvent + { + /// + /// Lists the possible reasons that the game options may have changed. + /// + public enum ChangeReason + { + /// + /// The options were changed by the host using + /// . + /// + /// + /// This event does not change the message sent to other players, + /// so changes made to Game.Options are not synced until + /// SyncSettingsAsync is called afterwards. + /// + Rpc, + + /// + /// The options were changed by a plugin using the + /// SyncSettingsAsync method in . + /// + /// + /// This event is called after serializing the settings, which + /// means that changes made by event handlers will not be synced + /// over. + /// + /// We strongly recommend against calling SyncSettingsAsync while + /// handling this event. + /// + Api, + } + + /// + /// Gets the reason that the game options have changed. + /// + public ChangeReason ChangedBy { get; } + } +} diff --git a/src/Impostor.Plugins.Example/Handlers/GameEventListener.cs b/src/Impostor.Plugins.Example/Handlers/GameEventListener.cs index 404e0a8..90dd0ee 100644 --- a/src/Impostor.Plugins.Example/Handlers/GameEventListener.cs +++ b/src/Impostor.Plugins.Example/Handlers/GameEventListener.cs @@ -79,6 +79,16 @@ namespace Impostor.Plugins.Example.Handlers ); } + [EventListener] + public async void OnGameOptionsChanged(IGameOptionsChangedEvent e) + { + _logger.LogInformation( + "Game {code} > new options because of {source}", + e.Game.Code, + e.ChangedBy + ); + } + [EventListener] public void OnPlayerJoined(IGamePlayerJoinedEvent e) { diff --git a/src/Impostor.Server/Events/Game/GameOptionsChangedEvent.cs b/src/Impostor.Server/Events/Game/GameOptionsChangedEvent.cs new file mode 100644 index 0000000..075de31 --- /dev/null +++ b/src/Impostor.Server/Events/Game/GameOptionsChangedEvent.cs @@ -0,0 +1,19 @@ +using Impostor.Api.Events; +using Impostor.Api.Games; +using static Impostor.Api.Events.IGameOptionsChangedEvent; + +namespace Impostor.Server.Events +{ + public class GameOptionsChangedEvent : IGameOptionsChangedEvent + { + public GameOptionsChangedEvent(IGame game, ChangeReason changedBy) + { + Game = game; + ChangedBy = changedBy; + } + + public ChangeReason ChangedBy { get; } + + public IGame Game { get; } + } +} diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs index 06b0485..9b3539d 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs @@ -15,6 +15,7 @@ using Impostor.Api.Net.Inner; using Impostor.Api.Net.Inner.Objects; using Impostor.Api.Net.Messages.Rpcs; using Impostor.Api.Utils; +using Impostor.Server.Events; using Impostor.Server.Events.Player; using Impostor.Server.Net.Inner.Objects.Components; using Impostor.Server.Net.State; @@ -142,6 +143,9 @@ namespace Impostor.Server.Net.Inner.Objects } Rpc02SyncSettings.DeserializeInto(reader, Game.Options); + await _eventManager.CallAsync(new GameOptionsChangedEvent( + Game, + Api.Events.IGameOptionsChangedEvent.ChangeReason.Rpc)); break; } diff --git a/src/Impostor.Server/Net/State/Game.Api.cs b/src/Impostor.Server/Net/State/Game.Api.cs index b8a081e..4ed4279 100644 --- a/src/Impostor.Server/Net/State/Game.Api.cs +++ b/src/Impostor.Server/Net/State/Game.Api.cs @@ -6,12 +6,16 @@ using Impostor.Api.Net; using Impostor.Api.Net.Inner; using Impostor.Api.Net.Messages; using Impostor.Hazel; +using Impostor.Server.Events; using Impostor.Server.Net.Inner; +using Microsoft.Extensions.Logging; namespace Impostor.Server.Net.State { internal partial class Game : IGame { + private bool alreadyCallingOptionsChangedEvent = false; + IClientPlayer? IGame.Host => Host; IGameNet IGame.GameNet => GameNet; @@ -59,6 +63,20 @@ namespace Impostor.Server.Net.State writer.EndMessage(); await SendToAllAsync(writer); + + // Prevent bad plugins from causing a server crash by recursing into this function + if (alreadyCallingOptionsChangedEvent) + { + _logger.LogError("Plugin called SyncSettingsAsync while processing a GameOptionsChangedEvent, aborting to prevent recursion"); + } + else + { + alreadyCallingOptionsChangedEvent = true; + await _eventManager.CallAsync(new GameOptionsChangedEvent( + this, + Api.Events.IGameOptionsChangedEvent.ChangeReason.Api)); + alreadyCallingOptionsChangedEvent = false; + } } public async ValueTask SetPrivacyAsync(bool isPublic)