--- /dev/null
+using Impostor.Api.Games;
+using Impostor.Api.Net.Messages.Rpcs;
+
+namespace Impostor.Api.Events
+{
+ /// <summary>
+ /// This event is triggered when game options are going to be changed.
+ /// </summary>
+ /// <remarks>
+ /// Be careful when calling SyncSettingsAsync while handling this event,
+ /// as it may call this event again in some cases.
+ /// </remarks>
+ public interface IGameOptionsChangedEvent : IGameEvent
+ {
+ /// <summary>
+ /// Lists the possible reasons that the game options may have changed.
+ /// </summary>
+ public enum ChangeReason
+ {
+ /// <summary>
+ /// The options were changed by the host using
+ /// <see cref="Rpc02SyncSettings"/>.
+ /// </summary>
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ Rpc,
+
+ /// <summary>
+ /// The options were changed by a plugin using the
+ /// SyncSettingsAsync method in <see cref="IGame"/>.
+ /// </summary>
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ Api,
+ }
+
+ /// <summary>
+ /// Gets the reason that the game options have changed.
+ /// </summary>
+ public ChangeReason ChangedBy { get; }
+ }
+}
);
}
+ [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)
{
--- /dev/null
+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; }
+ }
+}
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;
}
Rpc02SyncSettings.DeserializeInto(reader, Game.Options);
+ await _eventManager.CallAsync(new GameOptionsChangedEvent(
+ Game,
+ Api.Events.IGameOptionsChangedEvent.ChangeReason.Rpc));
break;
}
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;
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)