Compare commits

..
4 Commits
Author SHA1 Message Date
js6pak 3e857ed555 Fix plugin host disposal 2021-06-13 21:35:04 +02:00
js6pak 9d31752bf0 Add PluginManager 2021-06-12 15:51:39 +02:00
ced530b83a Added player voted event (#181)
Co-authored-by: AeonLucid <aeonlucid@outlook.com>
Co-authored-by: js6pak <kubastaron@hotmail.com>
2021-06-11 12:33:38 +00:00
8f1cba8429 Using MapData for Tasks (#395)
Co-authored-by: John Tran <john.tran.84@live.com.au>
Co-authored-by: js6pak <kubastaron@hotmail.com>
2021-06-10 18:54:34 +00:00
45 changed files with 1292 additions and 191 deletions

No files matched your search

@@ -1,6 +1,17 @@
namespace Impostor.Api.Events.Meeting
using Impostor.Api.Net.Inner.Objects;
namespace Impostor.Api.Events.Meeting
{
public interface IMeetingEndedEvent : IMeetingEvent
{
/// <summary>
/// Gets the exiled player.
/// </summary>
IInnerPlayerControl? Exiled { get; }
/// <summary>
/// Gets a value indicating whether meeting result is a tie.
/// </summary>
bool IsTie { get; }
}
}
@@ -0,0 +1,24 @@
using Impostor.Api.Net.Inner.Objects;
namespace Impostor.Api.Events.Player
{
public enum VoteType : sbyte
{
ForceSkip = -2,
Skip = -1,
Player = 0,
}
public interface IPlayerVotedEvent : IPlayerEvent
{
/// <summary>
/// Gets the player he voted for.
/// </summary>
IInnerPlayerControl? VotedFor { get; }
/// <summary>
/// Gets the Vote type.
/// </summary>
VoteType VoteType { get; }
}
}
+15
View File
@@ -0,0 +1,15 @@
namespace Impostor.Api.Innersloth
{
public interface ITask
{
int Id { get; }
string Name { get; }
TaskTypes Type { get; }
TaskCategories Category { get; }
bool IsVisual { get; }
}
}
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Impostor.Api.Innersloth.Maps.Tasks;
using Impostor.Api.Innersloth.Maps.Vents;
namespace Impostor.Api.Innersloth.Maps
@@ -8,8 +9,9 @@ namespace Impostor.Api.Innersloth.Maps
public class AirshipData : IMapData
{
private readonly IReadOnlyDictionary<int, IVent> _vents;
private readonly IReadOnlyDictionary<int, ITask> _tasks;
public AirshipData()
internal AirshipData()
{
var vents = new[]
{
@@ -29,10 +31,61 @@ namespace Impostor.Api.Innersloth.Maps
Vents = vents.ToDictionary(x => x.Id, x => x).AsReadOnly();
_vents = vents.ToDictionary(x => (int)x.Id, x => (IVent)x).AsReadOnly();
var tasks = new[]
{
new AirshipTask(AirshipTask.Ids.ElectricalFixWiring, TaskTypes.FixWiring, TaskCategories.CommonTask),
new AirshipTask(AirshipTask.Ids.MeetingRoomEnterIDCode, TaskTypes.EnterIdCode, TaskCategories.CommonTask),
new AirshipTask(AirshipTask.Ids.ElectricalResetBreakers, TaskTypes.ResetBreakers, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.VaultRoomDownloadData, TaskTypes.UploadData, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.BrigDownloadData, TaskTypes.UploadData, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.CargoBayDownloadData, TaskTypes.UploadData, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.GapRoomDownloadData, TaskTypes.UploadData, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.RecordsDownloadData, TaskTypes.UploadData, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.CargoBayUnlockSafe, TaskTypes.UnlockSafe, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.VentilationStartFans, TaskTypes.StartFans, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.MainHallEmptyGarbage, TaskTypes.EmptyGarbage, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.MedicalEmptyGarbage, TaskTypes.EmptyGarbage, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.KitchenEmptyGarbage, TaskTypes.EmptyGarbage, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.MainHallDevelopPhotos, TaskTypes.DevelopPhotos, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.CargoBayFuelEngines, TaskTypes.FuelEngines, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.SecurityRewindTapes, TaskTypes.RewindTapes, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.VaultRoomPolishRuby, TaskTypes.PolishRuby, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.ElectricalCalibrateDistributor, TaskTypes.CalibrateDistributor, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.CockpitStabilizeSteering, TaskTypes.StabilizeSteering, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.ArmoryDownloadData, TaskTypes.UploadData, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.CockpitDownloadData, TaskTypes.UploadData, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.CommsDownloadData, TaskTypes.UploadData, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.MedicalDownloadData, TaskTypes.UploadData, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.ViewingDeckDownloadData, TaskTypes.UploadData, TaskCategories.LongTask),
new AirshipTask(AirshipTask.Ids.ElectricalDivertPowerToArmory, TaskTypes.DivertPower, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.ElectricalDivertPowerToCockpit, TaskTypes.DivertPower, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.ElectricalDivertPowerToGapRoom, TaskTypes.DivertPower, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.ElectricalDivertPowerToMainHall, TaskTypes.DivertPower, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.ElectricalDivertPowerToMeetingRoom, TaskTypes.DivertPower, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.ElectricalDivertPowerToShowers, TaskTypes.DivertPower, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.ElectricalDivertPowerToEngine, TaskTypes.DivertPower, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.ShowersPickUpTowels, TaskTypes.PickUpTowels, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.LoungeCleanToilet, TaskTypes.CleanToilet, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.VaultRoomDressMannequin, TaskTypes.DressMannequin, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.RecordsSortRecords, TaskTypes.SortRecords, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.ArmoryPutAwayPistols, TaskTypes.PutAwayPistols, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.ArmoryPutAwayRifles, TaskTypes.PutAwayRifles, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.MainHallDecontaminate, TaskTypes.Decontaminate, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.KitchenMakeBurger, TaskTypes.MakeBurger, TaskCategories.ShortTask),
new AirshipTask(AirshipTask.Ids.ShowersFixShower, TaskTypes.FixShower, TaskCategories.ShortTask),
};
Tasks = tasks.ToDictionary(x => x.Id, x => x).AsReadOnly();
_tasks = tasks.ToDictionary(x => (int)x.Id, x => (ITask)x).AsReadOnly();
}
public IReadOnlyDictionary<AirshipVent.Ids, AirshipVent> Vents { get; }
IReadOnlyDictionary<int, IVent> IMapData.Vents => _vents;
public IReadOnlyDictionary<AirshipTask.Ids, AirshipTask> Tasks { get; }
IReadOnlyDictionary<int, ITask> IMapData.Tasks => _tasks;
}
}
@@ -13,5 +13,7 @@ namespace Impostor.Api.Innersloth.Maps
}.AsReadOnly();
IReadOnlyDictionary<int, IVent> Vents { get; }
IReadOnlyDictionary<int, ITask> Tasks { get; }
}
}
+40 -1
View File
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Impostor.Api.Innersloth.Maps.Tasks;
using Impostor.Api.Innersloth.Maps.Vents;
namespace Impostor.Api.Innersloth.Maps
@@ -8,8 +9,9 @@ namespace Impostor.Api.Innersloth.Maps
public class MiraData : IMapData
{
private readonly IReadOnlyDictionary<int, IVent> _vents;
private readonly IReadOnlyDictionary<int, ITask> _tasks;
public MiraData()
internal MiraData()
{
var vents = new[]
{
@@ -28,10 +30,47 @@ namespace Impostor.Api.Innersloth.Maps
Vents = vents.ToDictionary(x => x.Id, x => x).AsReadOnly();
_vents = vents.ToDictionary(x => (int)x.Id, x => (IVent)x).AsReadOnly();
var tasks = new[]
{
new MiraTask(MiraTask.Ids.HallwayFixWiring, TaskTypes.FixWiring, TaskCategories.CommonTask),
new MiraTask(MiraTask.Ids.AdminEnterIDCode, TaskTypes.EnterIdCode, TaskCategories.CommonTask),
new MiraTask(MiraTask.Ids.MedbaySubmitScan, TaskTypes.SubmitScan, TaskCategories.LongTask, true),
new MiraTask(MiraTask.Ids.BalconyClearAsteroids, TaskTypes.ClearAsteroids, TaskCategories.LongTask),
new MiraTask(MiraTask.Ids.ElectricalDivertPowerToAdmin, TaskTypes.DivertPower, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.ElectricalDivertPowerToCafeteria, TaskTypes.DivertPower, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.ElectricalDivertPowerToCommunications, TaskTypes.DivertPower, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.ElectricalDivertPowerToLaunchpad, TaskTypes.DivertPower, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.ElectricalDivertPowerToMedbay, TaskTypes.DivertPower, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.ElectricalDivertPowerToOffice, TaskTypes.DivertPower, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.StorageWaterPlants, TaskTypes.WaterPlants, TaskCategories.LongTask),
new MiraTask(MiraTask.Ids.ReactorStartReactor, TaskTypes.StartReactor, TaskCategories.LongTask),
new MiraTask(MiraTask.Ids.ElectricalDivertPowerToGreenhouse, TaskTypes.DivertPower, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.AdminChartCourse, TaskTypes.ChartCourse, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.GreenhouseCleanO2Filter, TaskTypes.Filter, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.LaunchpadFuelEngines, TaskTypes.FuelEngines, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.LaboratoryAssembleArtifact, TaskTypes.AssembleArtifact, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.LaboratorySortSamples, TaskTypes.SortSamples, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.AdminPrimeShields, TaskTypes.PrimeShields, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.CafeteriaEmptyGarbage, TaskTypes.EmptyGarbage, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.BalconyMeasureWeather, TaskTypes.MeasureWeather, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.ElectricalDivertPowerToLaboratory, TaskTypes.DivertPower, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.CafeteriaBuyBeverage, TaskTypes.BuyBeverage, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.OfficeProcessData, TaskTypes.ProcessData, TaskCategories.ShortTask),
new MiraTask(MiraTask.Ids.LaunchpadRunDiagnostics, TaskTypes.RunDiagnostics, TaskCategories.LongTask),
new MiraTask(MiraTask.Ids.ReactorUnlockManifolds, TaskTypes.UnlockManifolds, TaskCategories.ShortTask),
};
Tasks = tasks.ToDictionary(x => x.Id, x => x).AsReadOnly();
_tasks = tasks.ToDictionary(x => (int)x.Id, x => (ITask)x).AsReadOnly();
}
public IReadOnlyDictionary<MiraVent.Ids, MiraVent> Vents { get; }
IReadOnlyDictionary<int, IVent> IMapData.Vents => _vents;
public IReadOnlyDictionary<MiraTask.Ids, MiraTask> Tasks { get; }
IReadOnlyDictionary<int, ITask> IMapData.Tasks => _tasks;
}
}
+47 -1
View File
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Impostor.Api.Innersloth.Maps.Tasks;
using Impostor.Api.Innersloth.Maps.Vents;
namespace Impostor.Api.Innersloth.Maps
@@ -8,8 +9,9 @@ namespace Impostor.Api.Innersloth.Maps
public class PolusData : IMapData
{
private readonly IReadOnlyDictionary<int, IVent> _vents;
private readonly IReadOnlyDictionary<int, ITask> _tasks;
public PolusData()
internal PolusData()
{
var vents = new[]
{
@@ -29,10 +31,54 @@ namespace Impostor.Api.Innersloth.Maps
Vents = vents.ToDictionary(x => x.Id, x => x).AsReadOnly();
_vents = vents.ToDictionary(x => (int)x.Id, x => (IVent)x).AsReadOnly();
var tasks = new[]
{
new PolusTask(PolusTask.Ids.OfficeSwipeCard, TaskTypes.SwipeCard, TaskCategories.CommonTask),
new PolusTask(PolusTask.Ids.DropshipInsertKeys, TaskTypes.InsertKeys, TaskCategories.CommonTask),
new PolusTask(PolusTask.Ids.OfficeScanBoardingPass, TaskTypes.ScanBoardingPass, TaskCategories.CommonTask),
new PolusTask(PolusTask.Ids.ElectricalFixWiring, TaskTypes.FixWiring, TaskCategories.CommonTask),
new PolusTask(PolusTask.Ids.WeaponsDownloadData, TaskTypes.UploadData, TaskCategories.ShortTask),
new PolusTask(PolusTask.Ids.OfficeDownloadData, TaskTypes.UploadData, TaskCategories.ShortTask),
new PolusTask(PolusTask.Ids.ElectricalDownloadData, TaskTypes.UploadData, TaskCategories.ShortTask),
new PolusTask(PolusTask.Ids.SpecimenRoomDownloadData, TaskTypes.UploadData, TaskCategories.ShortTask),
new PolusTask(PolusTask.Ids.O2DownloadData, TaskTypes.UploadData, TaskCategories.ShortTask),
new PolusTask(PolusTask.Ids.SpecimenRoomStartReactor, TaskTypes.StartReactor, TaskCategories.LongTask),
new PolusTask(PolusTask.Ids.StorageFuelEngines, TaskTypes.FuelEngines, TaskCategories.LongTask),
new PolusTask(PolusTask.Ids.BoilerRoomOpenWaterways, TaskTypes.OpenWaterways, TaskCategories.LongTask),
new PolusTask(PolusTask.Ids.MedbayInspectSample, TaskTypes.InspectSample, TaskCategories.LongTask),
new PolusTask(PolusTask.Ids.BoilerRoomReplaceWaterJug, TaskTypes.ReplaceWaterJug, TaskCategories.LongTask),
new PolusTask(PolusTask.Ids.OutsideFixWeatherNodeNode_GI, TaskTypes.ActivateWeatherNodes, TaskCategories.LongTask),
new PolusTask(PolusTask.Ids.OutsideFixWeatherNodeNode_IRO, TaskTypes.ActivateWeatherNodes, TaskCategories.LongTask),
new PolusTask(PolusTask.Ids.OutsideFixWeatherNodeNode_PD, TaskTypes.ActivateWeatherNodes, TaskCategories.LongTask),
new PolusTask(PolusTask.Ids.OutsideFixWeatherNodeNode_TB, TaskTypes.ActivateWeatherNodes, TaskCategories.LongTask),
new PolusTask(PolusTask.Ids.CommunicationsRebootWiFi, TaskTypes.RebootWifi, TaskCategories.LongTask),
new PolusTask(PolusTask.Ids.O2MonitorTree, TaskTypes.MonitorOxygen, TaskCategories.ShortTask),
new PolusTask(PolusTask.Ids.SpecimenRoomUnlockManifolds, TaskTypes.UnlockManifolds, TaskCategories.ShortTask),
new PolusTask(PolusTask.Ids.SpecimenRoomStoreArtifacts, TaskTypes.StoreArtifact, TaskCategories.ShortTask),
new PolusTask(PolusTask.Ids.O2FillCanisters, TaskTypes.FillCanisters, TaskCategories.ShortTask),
new PolusTask(PolusTask.Ids.O2EmptyGarbage, TaskTypes.EmptyGarbage, TaskCategories.ShortTask),
new PolusTask(PolusTask.Ids.DropshipChartCourse, TaskTypes.ChartCourse, TaskCategories.ShortTask),
new PolusTask(PolusTask.Ids.MedbaySubmitScan, TaskTypes.SubmitScan, TaskCategories.ShortTask, true),
new PolusTask(PolusTask.Ids.WeaponsClearAsteroids, TaskTypes.ClearAsteroids, TaskCategories.ShortTask, true),
new PolusTask(PolusTask.Ids.OutsideFixWeatherNodeNode_CA, TaskTypes.ActivateWeatherNodes, TaskCategories.LongTask),
new PolusTask(PolusTask.Ids.OutsideFixWeatherNodeNode_MLG, TaskTypes.ActivateWeatherNodes, TaskCategories.LongTask),
new PolusTask(PolusTask.Ids.LaboratoryAlignTelescope, TaskTypes.AlignTelescope, TaskCategories.ShortTask),
new PolusTask(PolusTask.Ids.LaboratoryRepairDrill, TaskTypes.RepairDrill, TaskCategories.ShortTask),
new PolusTask(PolusTask.Ids.LaboratoryRecordTemperature, TaskTypes.RecordTemperature, TaskCategories.ShortTask),
new PolusTask(PolusTask.Ids.OutsideRecordTemperature, TaskTypes.RecordTemperature, TaskCategories.ShortTask),
};
Tasks = tasks.ToDictionary(x => x.Id, x => x).AsReadOnly();
_tasks = tasks.ToDictionary(x => (int)x.Id, x => (ITask)x).AsReadOnly();
}
public IReadOnlyDictionary<PolusVent.Ids, PolusVent> Vents { get; }
IReadOnlyDictionary<int, IVent> IMapData.Vents => _vents;
public IReadOnlyDictionary<PolusTask.Ids, PolusTask> Tasks { get; }
IReadOnlyDictionary<int, ITask> IMapData.Tasks => _tasks;
}
}
+43 -1
View File
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Impostor.Api.Innersloth.Maps.Tasks;
using Impostor.Api.Innersloth.Maps.Vents;
namespace Impostor.Api.Innersloth.Maps
@@ -8,8 +9,9 @@ namespace Impostor.Api.Innersloth.Maps
public class SkeldData : IMapData
{
private readonly IReadOnlyDictionary<int, IVent> _vents;
private readonly IReadOnlyDictionary<int, ITask> _tasks;
public SkeldData()
internal SkeldData()
{
var vents = new[]
{
@@ -31,10 +33,50 @@ namespace Impostor.Api.Innersloth.Maps
Vents = vents.ToDictionary(x => x.Id, x => x).AsReadOnly();
_vents = vents.ToDictionary(x => (int)x.Id, x => (IVent)x).AsReadOnly();
var tasks = new[]
{
new SkeldTask(SkeldTask.Ids.AdminSwipeCard, TaskTypes.SwipeCard, TaskCategories.CommonTask),
new SkeldTask(SkeldTask.Ids.ElectricalFixWiring, TaskTypes.FixWiring, TaskCategories.CommonTask),
new SkeldTask(SkeldTask.Ids.WeaponsClearAsteroids, TaskTypes.ClearAsteroids, TaskCategories.LongTask, true),
new SkeldTask(SkeldTask.Ids.EnginesAlignEngineOutput, TaskTypes.AlignEngineOutput, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.MedbaySubmitScan, TaskTypes.SubmitScan, TaskCategories.LongTask, true),
new SkeldTask(SkeldTask.Ids.MedbayInspectSample, TaskTypes.InspectSample, TaskCategories.LongTask),
new SkeldTask(SkeldTask.Ids.StorageFuelEngines, TaskTypes.FuelEngines, TaskCategories.LongTask),
new SkeldTask(SkeldTask.Ids.ReactorStartReactor, TaskTypes.StartReactor, TaskCategories.LongTask),
new SkeldTask(SkeldTask.Ids.O2EmptyChute, TaskTypes.EmptyChute, TaskCategories.LongTask, true),
new SkeldTask(SkeldTask.Ids.CafeteriaEmptyGarbage, TaskTypes.EmptyGarbage, TaskCategories.LongTask, true),
new SkeldTask(SkeldTask.Ids.CommunicationsDownloadData, TaskTypes.UploadData, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.ElectricalCalibrateDistributor, TaskTypes.CalibrateDistributor, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.NavigationChartCourse, TaskTypes.ChartCourse, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.O2CleanO2Filter, TaskTypes.Filter, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.ReactorUnlockManifolds, TaskTypes.UnlockManifolds, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.ElectricalDownloadData, TaskTypes.UploadData, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.NavigationStabilizeSteering, TaskTypes.StabilizeSteering, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.WeaponsDownloadData, TaskTypes.UploadData, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.ShieldsPrimeShields, TaskTypes.PrimeShields, TaskCategories.ShortTask, true),
new SkeldTask(SkeldTask.Ids.CafeteriaDownloadData, TaskTypes.UploadData, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.NavigationDownloadData, TaskTypes.UploadData, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.ElectricalDivertPowerToShields, TaskTypes.DivertPower, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.ElectricalDivertPowerToWeapons, TaskTypes.DivertPower, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.ElectricalDivertPowerToCommunications, TaskTypes.DivertPower, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.ElectricalDivertPowerToUpperEngine, TaskTypes.DivertPower, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.ElectricalDivertPowerToO2, TaskTypes.DivertPower, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.ElectricalDivertPowerToNavigation, TaskTypes.DivertPower, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.ElectricalDivertPowerToLowerEngine, TaskTypes.DivertPower, TaskCategories.ShortTask),
new SkeldTask(SkeldTask.Ids.ElectricalDivertPowerToSecurity, TaskTypes.DivertPower, TaskCategories.ShortTask),
};
Tasks = tasks.ToDictionary(x => x.Id, x => x).AsReadOnly();
_tasks = tasks.ToDictionary(x => (int)x.Id, x => (ITask)x).AsReadOnly();
}
public IReadOnlyDictionary<SkeldVent.Ids, SkeldVent> Vents { get; }
IReadOnlyDictionary<int, IVent> IMapData.Vents => _vents;
public IReadOnlyDictionary<SkeldTask.Ids, SkeldTask> Tasks { get; }
IReadOnlyDictionary<int, ITask> IMapData.Tasks => _tasks;
}
}
@@ -0,0 +1,70 @@
namespace Impostor.Api.Innersloth.Maps.Tasks
{
public class AirshipTask : ITask
{
internal AirshipTask(Ids id, TaskTypes type, TaskCategories category, bool isVisual = false)
{
Id = id;
Name = id.ToString();
Type = type;
Category = category;
IsVisual = isVisual;
}
public enum Ids
{
ElectricalFixWiring = 0,
MeetingRoomEnterIDCode = 1,
ElectricalResetBreakers = 2,
VaultRoomDownloadData = 3,
BrigDownloadData = 4,
CargoBayDownloadData = 5,
GapRoomDownloadData = 6,
RecordsDownloadData = 7,
CargoBayUnlockSafe = 8,
VentilationStartFans = 9,
MainHallEmptyGarbage = 10,
MedicalEmptyGarbage = 11,
KitchenEmptyGarbage = 12,
MainHallDevelopPhotos = 13,
CargoBayFuelEngines = 14,
SecurityRewindTapes = 15,
VaultRoomPolishRuby = 16,
ElectricalCalibrateDistributor = 17,
CockpitStabilizeSteering = 18,
ArmoryDownloadData = 19,
CockpitDownloadData = 20,
CommsDownloadData = 21,
MedicalDownloadData = 22,
ViewingDeckDownloadData = 23,
ElectricalDivertPowerToArmory = 24,
ElectricalDivertPowerToCockpit = 25,
ElectricalDivertPowerToGapRoom = 26,
ElectricalDivertPowerToMainHall = 27,
ElectricalDivertPowerToMeetingRoom = 28,
ElectricalDivertPowerToShowers = 29,
ElectricalDivertPowerToEngine = 30,
ShowersPickUpTowels = 31,
LoungeCleanToilet = 32,
VaultRoomDressMannequin = 33,
RecordsSortRecords = 34,
ArmoryPutAwayPistols = 35,
ArmoryPutAwayRifles = 36,
MainHallDecontaminate = 37,
KitchenMakeBurger = 38,
ShowersFixShower = 39,
}
public Ids Id { get; }
int ITask.Id => (int)Id;
public string Name { get; }
public TaskTypes Type { get; }
public TaskCategories Category { get; }
public bool IsVisual { get; }
}
}
@@ -0,0 +1,56 @@
namespace Impostor.Api.Innersloth.Maps.Tasks
{
public class MiraTask : ITask
{
internal MiraTask(Ids id, TaskTypes type, TaskCategories category, bool isVisual = false)
{
Id = id;
Name = id.ToString();
Type = type;
Category = category;
IsVisual = isVisual;
}
public enum Ids
{
HallwayFixWiring = 0,
AdminEnterIDCode = 1,
MedbaySubmitScan = 2,
BalconyClearAsteroids = 3,
ElectricalDivertPowerToAdmin = 4,
ElectricalDivertPowerToCafeteria = 5,
ElectricalDivertPowerToCommunications = 6,
ElectricalDivertPowerToLaunchpad = 7,
ElectricalDivertPowerToMedbay = 8,
ElectricalDivertPowerToOffice = 9,
StorageWaterPlants = 10,
ReactorStartReactor = 11,
ElectricalDivertPowerToGreenhouse = 12,
AdminChartCourse = 13,
GreenhouseCleanO2Filter = 14,
LaunchpadFuelEngines = 15,
LaboratoryAssembleArtifact = 16,
LaboratorySortSamples = 17,
AdminPrimeShields = 18,
CafeteriaEmptyGarbage = 19,
BalconyMeasureWeather = 20,
ElectricalDivertPowerToLaboratory = 21,
CafeteriaBuyBeverage = 22,
OfficeProcessData = 23,
LaunchpadRunDiagnostics = 24,
ReactorUnlockManifolds = 25,
}
public Ids Id { get; }
int ITask.Id => (int)Id;
public string Name { get; }
public TaskTypes Type { get; }
public TaskCategories Category { get; }
public bool IsVisual { get; }
}
}
@@ -0,0 +1,63 @@
namespace Impostor.Api.Innersloth.Maps.Tasks
{
public class PolusTask : ITask
{
internal PolusTask(Ids id, TaskTypes type, TaskCategories category, bool isVisual = false)
{
Id = id;
Name = id.ToString();
Type = type;
Category = category;
IsVisual = isVisual;
}
public enum Ids
{
OfficeSwipeCard = 0,
DropshipInsertKeys = 1,
OfficeScanBoardingPass = 2,
ElectricalFixWiring = 3,
WeaponsDownloadData = 4,
OfficeDownloadData = 5,
ElectricalDownloadData = 6,
SpecimenRoomDownloadData = 7,
O2DownloadData = 8,
SpecimenRoomStartReactor = 9,
StorageFuelEngines = 10,
BoilerRoomOpenWaterways = 11,
MedbayInspectSample = 12,
BoilerRoomReplaceWaterJug = 13,
OutsideFixWeatherNodeNode_GI = 14,
OutsideFixWeatherNodeNode_IRO = 15,
OutsideFixWeatherNodeNode_PD = 16,
OutsideFixWeatherNodeNode_TB = 17,
CommunicationsRebootWiFi = 18,
O2MonitorTree = 19,
SpecimenRoomUnlockManifolds = 20,
SpecimenRoomStoreArtifacts = 21,
O2FillCanisters = 22,
O2EmptyGarbage = 23,
DropshipChartCourse = 24,
MedbaySubmitScan = 25,
WeaponsClearAsteroids = 26,
OutsideFixWeatherNodeNode_CA = 27,
OutsideFixWeatherNodeNode_MLG = 28,
LaboratoryAlignTelescope = 29,
LaboratoryRepairDrill = 30,
LaboratoryRecordTemperature = 31,
OutsideRecordTemperature = 32,
}
public Ids Id { get; }
int ITask.Id => (int)Id;
public string Name { get; }
public TaskTypes Type { get; }
public TaskCategories Category { get; }
public bool IsVisual { get; }
}
}
@@ -0,0 +1,59 @@
namespace Impostor.Api.Innersloth.Maps.Tasks
{
public class SkeldTask : ITask
{
internal SkeldTask(Ids id, TaskTypes type, TaskCategories category, bool isVisual = false)
{
Id = id;
Name = id.ToString();
Type = type;
Category = category;
IsVisual = isVisual;
}
public enum Ids
{
AdminSwipeCard = 0,
ElectricalFixWiring = 1,
WeaponsClearAsteroids = 2,
EnginesAlignEngineOutput = 3,
MedbaySubmitScan = 4,
MedbayInspectSample = 5,
StorageFuelEngines = 6,
ReactorStartReactor = 7,
O2EmptyChute = 8,
CafeteriaEmptyGarbage = 9,
CommunicationsDownloadData = 10,
ElectricalCalibrateDistributor = 11,
NavigationChartCourse = 12,
O2CleanO2Filter = 13,
ReactorUnlockManifolds = 14,
ElectricalDownloadData = 15,
NavigationStabilizeSteering = 16,
WeaponsDownloadData = 17,
ShieldsPrimeShields = 18,
CafeteriaDownloadData = 19,
NavigationDownloadData = 20,
ElectricalDivertPowerToShields = 21,
ElectricalDivertPowerToWeapons = 22,
ElectricalDivertPowerToCommunications = 23,
ElectricalDivertPowerToUpperEngine = 24,
ElectricalDivertPowerToO2 = 25,
ElectricalDivertPowerToNavigation = 26,
ElectricalDivertPowerToLowerEngine = 27,
ElectricalDivertPowerToSecurity = 28,
}
public Ids Id { get; }
int ITask.Id => (int)Id;
public string Name { get; }
public TaskTypes Type { get; }
public TaskCategories Category { get; }
public bool IsVisual { get; }
}
}
@@ -0,0 +1,9 @@
namespace Impostor.Api.Innersloth
{
public enum TaskCategories : byte
{
CommonTask,
LongTask,
ShortTask,
}
}
+18 -1
View File
@@ -1,4 +1,4 @@
namespace Impostor.Api.Innersloth
namespace Impostor.Api.Innersloth
{
public enum TaskTypes : uint
{
@@ -45,5 +45,22 @@
AlignTelescope = 40,
RecordTemperature = 41,
RebootWifi = 42,
PolishRuby = 43,
ResetBreakers = 44,
Decontaminate = 45,
MakeBurger = 46,
UnlockSafe = 47,
SortRecords = 48,
PutAwayPistols = 49,
FixShower = 50,
CleanToilet = 51,
DressMannequin = 52,
PickUpTowels = 53,
RewindTapes = 54,
StartFans = 55,
DevelopPhotos = 56,
GetBiggolSword = 57,
PutAwayRifles = 58,
StopCharles = 59,
}
}
@@ -1,6 +1,59 @@
namespace Impostor.Api.Net.Inner.Objects
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Impostor.Api.Events.Player;
namespace Impostor.Api.Net.Inner.Objects
{
public interface IInnerMeetingHud : IInnerNetObject
{
public interface IPlayerVoteArea
{
/// <summary>
/// Gets the player info of this vote state.
/// </summary>
IInnerPlayerInfo TargetPlayer { get; }
/// <summary>
/// Gets a value indicating whether the player is a dead.
/// </summary>
bool IsDead { get; }
/// <summary>
/// Gets a value indicating whether the player started this meeting.
/// </summary>
bool DidReport { get; }
/// <summary>
/// Gets a value indicating whether the player voted.
/// </summary>
bool DidVote { get; }
/// <summary>
/// Gets the vote type.
/// </summary>
/// <remarks>
/// Null when <see cref="DidVote" /> is false.
/// </remarks>
[MemberNotNullWhen(true, nameof(DidVote))]
public VoteType? VoteType { get; }
/// <summary>
/// Gets the player voted for.
/// </summary>
/// <remarks>
/// Null when <see cref="VoteType" /> isn't <see cref="F:Impostor.Api.Events.Player.VoteType.Player"/>.
/// </remarks>
public IInnerPlayerControl? VotedFor { get; }
}
/// <summary>
/// Gets states of players taking part in this meeting.
/// </summary>
IReadOnlyCollection<IPlayerVoteArea> PlayerStates { get; }
/// <summary>
/// Gets the player that started the meeting.
/// </summary>
IInnerPlayerInfo? Reporter { get; }
}
}
@@ -1,4 +1,5 @@
using Impostor.Api.Innersloth;
using System.Threading.Tasks;
using Impostor.Api.Innersloth;
namespace Impostor.Api.Net.Inner.Objects
{
@@ -6,8 +7,14 @@ namespace Impostor.Api.Net.Inner.Objects
{
uint Id { get; }
TaskTypes Type { get; }
ITask Task { get; }
bool Complete { get; }
/// <summary>
/// Sets the task as complete.
/// </summary>
/// <returns>Task that must be awaited.</returns>
ValueTask CompleteAsync();
}
}
@@ -0,0 +1,42 @@
using System;
namespace Impostor.Api.Plugins
{
public interface IPluginInformation
{
/// <summary>
/// Gets the unique id of this plugin.
/// </summary>
string Id { get; }
/// <summary>
/// Gets the human readable name of this plugin.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the author of this plugin.
/// </summary>
string Author { get; }
/// <summary>
/// Gets the version of this plugin.
/// </summary>
string Version { get; }
/// <summary>
/// Gets the optional startup object of this plugin.
/// </summary>
IPluginStartup? Startup { get; }
/// <summary>
/// Gets the instance of this plugin.
/// </summary>
IPlugin? Instance { get; }
/// <summary>
/// Gets the instance type of this plugin.
/// </summary>
Type PluginType { get; }
}
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Impostor.Api.Plugins
{
public interface IPluginManager
{
IReadOnlyList<IPluginInformation> Plugins { get; }
}
}
@@ -1,3 +1,4 @@
using System.Linq;
using System.Threading.Tasks;
using Impostor.Api.Games.Managers;
using Impostor.Api.Innersloth;
@@ -11,17 +12,21 @@ namespace Impostor.Plugins.Example
{
private readonly ILogger<ExamplePlugin> _logger;
private readonly IGameManager _gameManager;
private readonly IPluginManager _pluginManager;
public ExamplePlugin(ILogger<ExamplePlugin> logger, IGameManager gameManager)
public ExamplePlugin(ILogger<ExamplePlugin> logger, IGameManager gameManager, IPluginManager pluginManager)
{
_logger = logger;
_gameManager = gameManager;
_pluginManager = pluginManager;
}
public override async ValueTask EnableAsync()
{
_logger.LogInformation("Example is being enabled.");
_logger.LogInformation("Following plugins are installed: {@plugins}", _pluginManager.Plugins.Select(x => $"{x.Id} ({x.Version})"));
var game = await _gameManager.CreateAsync(new GameOptionsData());
if (game == null)
{
@@ -22,7 +22,19 @@ namespace Impostor.Plugins.Example.Handlers
[EventListener]
public void OnMeetingEnded(IMeetingEndedEvent e)
{
_logger.LogInformation("Meeting > ended");
_logger.LogInformation("Meeting > ended, exiled: {exiled}, tie: {tie}", e.Exiled?.PlayerInfo.PlayerName, e.IsTie);
foreach (var playerState in e.MeetingHud.PlayerStates)
{
if (playerState.IsDead)
{
_logger.LogInformation("- {player} is dead", playerState.TargetPlayer.PlayerName);
}
else
{
_logger.LogInformation("- {player} voted for {voteType} {votedFor}", playerState.TargetPlayer.PlayerName, playerState.VoteType, playerState.VotedFor?.PlayerInfo.PlayerName);
}
}
}
}
}
@@ -84,6 +84,14 @@ namespace Impostor.Plugins.Example.Handlers
await e.PlayerControl.NetworkTransform.SnapToAsync(new Vector2(1, 1));
}
if (e.Message == "completetasks")
{
foreach (var task in e.PlayerControl.PlayerInfo.Tasks)
{
await task.CompleteAsync();
}
}
await e.PlayerControl.SetNameAsync(e.Message);
await e.PlayerControl.SendChatAsync(e.Message);
}
@@ -111,5 +119,17 @@ namespace Impostor.Plugins.Example.Handlers
{
_logger.LogInformation("Player {player} vented to {vent}", e.PlayerControl.PlayerInfo.PlayerName, e.NewVent.Name);
}
[EventListener]
public void OnPlayerVoted(IPlayerVotedEvent e)
{
_logger.LogDebug("Player {player} voted for {type} {votedFor}", e.PlayerControl.PlayerInfo.PlayerName, e.VoteType, e.VotedFor?.PlayerInfo.PlayerName);
}
[EventListener]
public void OnPlayerCompletedTaskEvent(IPlayerCompletedTaskEvent e)
{
_logger.LogInformation("Player {player} completed {task}, {type}, {category}, visual {visual}", e.PlayerControl.PlayerInfo.PlayerName, e.Task.Task.Name, e.Task.Task.Type, e.Task.Task.Category, e.Task.Task.IsVisual);
}
}
}
@@ -1,9 +1,11 @@
using System.Collections.Generic;
namespace Impostor.Server.Plugins
namespace Impostor.Server.Config
{
public class PluginConfig
public class PluginLoaderConfig
{
public const string Section = "PluginLoader";
public List<string> Paths { get; set; } = new List<string>();
public List<string> LibraryPaths { get; set; } = new List<string>();
@@ -6,14 +6,20 @@ namespace Impostor.Server.Events.Meeting
{
public class MeetingEndedEvent : IMeetingEndedEvent
{
public MeetingEndedEvent(IGame game, IInnerMeetingHud meetingHud)
public MeetingEndedEvent(IGame game, IInnerMeetingHud meetingHud, IInnerPlayerControl? exiled, bool isTie)
{
Game = game;
MeetingHud = meetingHud;
Exiled = exiled;
IsTie = isTie;
}
public IGame Game { get; }
public IInnerMeetingHud MeetingHud { get; }
public IInnerPlayerControl? Exiled { get; }
public bool IsTie { get; }
}
}
@@ -0,0 +1,29 @@
using Impostor.Api.Events.Player;
using Impostor.Api.Games;
using Impostor.Api.Net;
using Impostor.Api.Net.Inner.Objects;
namespace Impostor.Server.Events.Player
{
public class PlayerVotedEvent : IPlayerVotedEvent
{
public PlayerVotedEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl, VoteType voteType, IInnerPlayerControl? votedFor)
{
Game = game;
ClientPlayer = clientPlayer;
PlayerControl = playerControl;
VoteType = voteType;
VotedFor = votedFor;
}
public IGame Game { get; }
public IClientPlayer ClientPlayer { get; }
public IInnerPlayerControl PlayerControl { get; }
public IInnerPlayerControl? VotedFor { get; }
public VoteType VoteType { get; }
}
}
+3 -1
View File
@@ -26,10 +26,12 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="5.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="5.0.4" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="5.0.7" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="4.1.2" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.333">
<PrivateAssets>all</PrivateAssets>
+1 -2
View File
@@ -1,5 +1,4 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using Impostor.Api;
@@ -0,0 +1,39 @@
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Net.Inner;
using Impostor.Api.Net.Messages.Rpcs;
using Impostor.Server.Events.Player;
namespace Impostor.Server.Net.Inner.Objects
{
internal partial class InnerGameData
{
public partial class TaskInfo
{
public async ValueTask CompleteAsync()
{
if (_playerInfo.Controller == null)
{
throw new ImpostorException("Can't complete a task that doesn't have a player assigned");
}
var player = _playerInfo.Controller;
if (Complete)
{
throw new ImpostorException("Can't complete a task that is already completed");
}
Complete = true;
// Send RPC.
using var writer = player.Game.StartRpc(player.NetId, RpcCalls.CompleteTask);
Rpc01CompleteTask.Serialize(writer, Id);
await player.Game.FinishRpcAsync(writer);
// Notify plugins.
await _eventManager.CallAsync(new PlayerCompletedTaskEvent(player.Game, player.Game.GetClientPlayer(player.OwnerId)!, player, this));
}
}
}
}
@@ -1,4 +1,5 @@
using Impostor.Api.Innersloth;
using Impostor.Api.Events.Managers;
using Impostor.Api.Innersloth;
using Impostor.Api.Net.Inner.Objects;
using Impostor.Api.Net.Messages;
@@ -6,17 +7,28 @@ namespace Impostor.Server.Net.Inner.Objects
{
internal partial class InnerGameData
{
public class TaskInfo : ITaskInfo
public partial class TaskInfo : ITaskInfo
{
private readonly InnerPlayerInfo _playerInfo;
private readonly IEventManager _eventManager;
public TaskInfo(InnerPlayerInfo playerInfo, IEventManager eventManager, uint id, ITask task)
{
_playerInfo = playerInfo;
_eventManager = eventManager;
Id = id;
Task = task;
}
public uint Id { get; internal set; }
public ITask Task { get; internal set; }
public bool Complete { get; internal set; }
public TaskTypes Type { get; internal set; }
public void Serialize(IMessageWriter writer)
{
writer.WritePacked((uint)Id);
writer.WritePacked(Id);
writer.Write(Complete);
}
@@ -1,8 +1,10 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Events.Managers;
using Impostor.Api.Innersloth.Maps;
using Impostor.Api.Net;
using Impostor.Api.Net.Custom;
using Impostor.Api.Net.Inner;
@@ -19,11 +21,13 @@ namespace Impostor.Server.Net.Inner.Objects
internal partial class InnerGameData : InnerNetObject, IInnerGameData
{
private readonly ILogger<InnerGameData> _logger;
private readonly IEventManager _eventManager;
private readonly ConcurrentDictionary<byte, InnerPlayerInfo> _allPlayers;
public InnerGameData(ICustomMessageManager<ICustomRpc> customMessageManager, Game game, ILogger<InnerGameData> logger, IServiceProvider serviceProvider) : base(customMessageManager, game)
public InnerGameData(ICustomMessageManager<ICustomRpc> customMessageManager, Game game, ILogger<InnerGameData> logger, IEventManager eventManager, IServiceProvider serviceProvider) : base(customMessageManager, game)
{
_logger = logger;
_eventManager = eventManager;
_allPlayers = new ConcurrentDictionary<byte, InnerPlayerInfo>();
Components.Add(this);
@@ -133,6 +137,11 @@ namespace Impostor.Server.Net.Inner.Objects
return null;
}
internal void RemovePlayer(InnerPlayerControl control)
{
_allPlayers.TryRemove(control.PlayerInfo.PlayerId, out _);
}
private void SetTasks(byte playerId, ReadOnlyMemory<byte> taskTypeIds)
{
var player = GetPlayerById(playerId);
@@ -149,12 +158,15 @@ namespace Impostor.Server.Net.Inner.Objects
player.Tasks = new List<TaskInfo>(taskTypeIds.Length);
foreach (var taskId in taskTypeIds.ToArray())
var taskId = 0u;
foreach (var taskTypeId in taskTypeIds.Span)
{
player.Tasks.Add(new TaskInfo
{
Id = taskId,
});
player.Tasks.Add(new TaskInfo(
player,
_eventManager,
taskId++,
Game.GameNet!.ShipStatus!.Data.Tasks[taskTypeId]
));
}
}
}
@@ -1,8 +1,13 @@
using Impostor.Api.Net.Inner.Objects;
using System;
using System.Collections.Generic;
using Impostor.Api.Net.Inner.Objects;
namespace Impostor.Server.Net.Inner.Objects
{
internal partial class InnerMeetingHud : IInnerMeetingHud
{
IReadOnlyCollection<IInnerMeetingHud.IPlayerVoteArea> IInnerMeetingHud.PlayerStates => Array.AsReadOnly(_playerStates);
IInnerPlayerInfo? IInnerMeetingHud.Reporter => Reporter;
}
}
@@ -1,25 +1,32 @@
using Impostor.Api.Net.Messages;
using Impostor.Api.Events.Player;
using Impostor.Api.Net.Inner.Objects;
using Impostor.Api.Net.Messages;
using TVoteType = Impostor.Api.Events.Player.VoteType;
namespace Impostor.Server.Net.Inner.Objects
{
internal partial class InnerMeetingHud
{
public class PlayerVoteArea
public class PlayerVoteArea : IInnerMeetingHud.IPlayerVoteArea
{
private const byte VoteMask = 15;
private const byte ReportedBit = 32;
private const byte VotedBit = 64;
private const byte DeadBit = 128;
public PlayerVoteArea(InnerMeetingHud parent, byte targetPlayerId)
private sbyte _votedForId;
public PlayerVoteArea(InnerMeetingHud parent, InnerPlayerInfo targetPlayer, bool isDead)
{
Parent = parent;
TargetPlayerId = targetPlayerId;
TargetPlayer = targetPlayer;
IsDead = isDead;
}
public InnerMeetingHud Parent { get; }
public byte TargetPlayerId { get; }
public InnerPlayerInfo TargetPlayer { get; }
public bool IsDead { get; private set; }
@@ -27,22 +34,77 @@ namespace Impostor.Server.Net.Inner.Objects
public bool DidReport { get; private set; }
public sbyte VotedFor { get; private set; }
public void Deserialize(IMessageReader reader)
public sbyte VotedForId
{
var num = reader.ReadByte();
get => _votedForId;
VotedFor = (sbyte)((num & VoteMask) - 1);
IsDead = (num & DeadBit) > 0;
DidVote = (num & VotedBit) > 0;
DidReport = (num & ReportedBit) > 0;
private set
{
_votedForId = value;
if (DidVote)
{
switch ((VoteType)value)
{
case TVoteType.ForceSkip:
case TVoteType.Skip:
VoteType = (VoteType)value;
break;
default:
VoteType = TVoteType.Player;
VotedFor = Parent.Game.GameNet.GameData!.GetPlayerById((byte)value)?.Controller;
break;
}
}
else
{
VoteType = null;
VotedFor = null;
}
}
}
internal void SetDead(bool didReport, bool isDead)
public VoteType? VoteType { get; private set; }
public IInnerPlayerControl? VotedFor { get; private set; }
IInnerPlayerInfo IInnerMeetingHud.IPlayerVoteArea.TargetPlayer => TargetPlayer;
internal void Deserialize(IMessageReader reader, bool updateVote)
{
DidReport = didReport;
var state = reader.ReadByte();
DeserializeState(state, out var votedForId, out var isDead, out var didVote, out var didReport);
IsDead = isDead;
DidReport = didReport;
if (updateVote)
{
DidVote = didVote;
VotedForId = votedForId;
}
}
internal void SetVotedFor(sbyte votedFor)
{
DidVote = true;
VotedForId = votedFor;
}
private static void DeserializeState(byte state, out sbyte votedForId, out bool isDead, out bool didVote, out bool didReport)
{
votedForId = (sbyte)((state & VoteMask) - 1);
// Among Us's meeting system is really scuffed, we need this to ensure that ForceSkip state is correct (this will break any 14+ players game)
if (votedForId == ((sbyte)TVoteType.ForceSkip + 1 & 14))
{
votedForId = (sbyte)TVoteType.ForceSkip;
}
isDead = (state & DeadBit) > 0;
didVote = (state & VotedBit) > 0;
didReport = (state & ReportedBit) > 0;
}
}
}
@@ -1,9 +1,11 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Events.Managers;
using Impostor.Api.Events.Player;
using Impostor.Api.Innersloth;
using Impostor.Api.Net;
using Impostor.Api.Net.Custom;
@@ -22,6 +24,8 @@ namespace Impostor.Server.Net.Inner.Objects
private readonly ILogger<InnerMeetingHud> _logger;
private readonly IEventManager _eventManager;
private readonly CancellationTokenSource _timerToken;
[AllowNull]
private PlayerVoteArea[] _playerStates;
@@ -32,9 +36,24 @@ namespace Impostor.Server.Net.Inner.Objects
_playerStates = null;
Components.Add(this);
_timerToken = new CancellationTokenSource();
_ = Task.Run(async () =>
{
try
{
await Task.Delay(TimeSpan.FromSeconds(Game.Options.DiscussionTime + Game.Options.VotingTime), _timerToken.Token);
}
catch (TaskCanceledException)
{
return;
}
await HandleVotingCompleteAsync();
});
}
public byte ReporterId { get; private set; }
public InnerPlayerInfo? Reporter { get; private set; }
public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
{
@@ -50,15 +69,15 @@ namespace Impostor.Server.Net.Inner.Objects
if (initialState)
{
PopulateButtons(0);
PopulateButtons();
foreach (var playerState in _playerStates)
{
playerState.Deserialize(reader);
playerState.Deserialize(reader, false);
if (playerState.DidReport)
{
ReporterId = playerState.TargetPlayerId;
Reporter = playerState.TargetPlayer;
}
}
}
@@ -70,7 +89,18 @@ namespace Impostor.Server.Net.Inner.Objects
{
if ((num & 1 << i) != 0)
{
_playerStates[i].Deserialize(reader);
var playerVoteArea = _playerStates[i];
var clientPlayer = Game.Players.SingleOrDefault(x => x.Character?.PlayerId == playerVoteArea.TargetPlayer.PlayerId);
var isHost = (clientPlayer?.IsHost ?? false) && playerVoteArea.VoteType != VoteType.ForceSkip;
playerVoteArea.Deserialize(reader, isHost);
if (isHost)
{
await HandleVoteAsync(playerVoteArea);
await CheckForEndVotingAsync();
}
}
}
}
@@ -99,14 +129,17 @@ namespace Impostor.Server.Net.Inner.Objects
}
Rpc23VotingComplete.Deserialize(reader, out var states, out var playerId, out var tie);
await HandleVotingComplete(sender, states, playerId, tie);
// This would be a nice place to implement an anti cheat.
// But for whatever reason host sends VotingComplete before sending his vote.
// Also every client executes his own VotingComplete after other client players CastVote rpc, like wtf.
break;
}
case RpcCalls.CastVote:
{
Rpc24CastVote.Deserialize(reader, out var playerId, out var suspectPlayerId);
return await HandleCastVote(sender, target, playerId, suspectPlayerId);
return await HandleCastVoteAsync(sender, target, playerId, suspectPlayerId);
}
case RpcCalls.ClearVote:
@@ -127,34 +160,24 @@ namespace Impostor.Server.Net.Inner.Objects
return true;
}
private void PopulateButtons(byte reporter)
private void PopulateButtons()
{
_playerStates = Game.GameNet.GameData!.Players
.Select(x =>
{
var area = new PlayerVoteArea(this, x.Key);
area.SetDead(x.Value.PlayerId == reporter, x.Value.Disconnected || x.Value.IsDead);
return area;
})
_playerStates = Game.GameNet.GameData!.Players.Values
.OrderBy(x => x.Controller?.NetId) // The host player hold MeetingHud players list sorted by NetId
.Select(x => new PlayerVoteArea(this, x, x.Disconnected || x.IsDead))
.ToArray();
}
private async ValueTask HandleVotingComplete(ClientPlayer sender, ReadOnlyMemory<byte> states, byte playerId, bool tie)
private async ValueTask HandleVoteAsync(PlayerVoteArea playerState)
{
if (playerId != byte.MaxValue)
if (playerState.DidVote && !playerState.IsDead)
{
var player = Game.GameNet.GameData!.GetPlayerById(playerId);
if (player?.Controller != null)
{
player.Controller.Die(DeathReason.Exile);
await _eventManager.CallAsync(new PlayerExileEvent(Game, sender, player.Controller));
}
var player = playerState.TargetPlayer.Controller!;
await _eventManager.CallAsync(new PlayerVotedEvent(Game, Game.GetClientPlayer(player!.OwnerId)!, player, playerState.VoteType!.Value, playerState.VotedFor));
}
await _eventManager.CallAsync(new MeetingEndedEvent(Game, this));
}
private async ValueTask<bool> HandleCastVote(ClientPlayer sender, ClientPlayer? target, byte playerId, sbyte suspectPlayerId)
private async ValueTask<bool> HandleCastVoteAsync(ClientPlayer sender, ClientPlayer? target, byte playerId, sbyte suspectPlayerId)
{
if (sender.IsHost)
{
@@ -179,7 +202,91 @@ namespace Impostor.Server.Net.Inner.Objects
}
}
if (!sender.IsHost)
{
var playerVoteArea = _playerStates.Single(x => x.TargetPlayer.PlayerId == playerId);
playerVoteArea.SetVotedFor(suspectPlayerId);
await HandleVoteAsync(playerVoteArea);
await CheckForEndVotingAsync();
}
return true;
}
private async ValueTask CheckForEndVotingAsync()
{
if (_playerStates.All(ps => ps.IsDead || ps.DidVote))
{
await HandleVotingCompleteAsync();
}
}
private byte[] CalculateVotes()
{
byte[] array = new byte[_playerStates.Max(x => x.TargetPlayer.PlayerId) + 2];
foreach (var playerVoteArea in _playerStates)
{
if (playerVoteArea.DidVote)
{
var index = playerVoteArea.VotedForId + 1;
if (index >= 0 && index < array.Length)
{
array[index] += 1;
}
}
}
return array;
}
private int IndexOfMax<T>(T[] self, Func<T, int> comparer, out bool tie)
{
tie = false;
var num = int.MinValue;
var result = -1;
for (var i = 0; i < self.Length; i++)
{
var num2 = comparer.Invoke(self[i]);
if (num2 > num)
{
result = i;
num = num2;
tie = false;
}
else if (num2 == num)
{
tie = true;
result = -1;
}
}
return result;
}
private async ValueTask HandleVotingCompleteAsync()
{
_timerToken.Cancel();
foreach (var playerVoteArea in _playerStates)
{
if (!playerVoteArea.DidVote)
{
playerVoteArea.SetVotedFor((sbyte)VoteType.ForceSkip);
await HandleVoteAsync(playerVoteArea);
}
}
byte[] self = this.CalculateVotes();
var maxIdx = IndexOfMax(self, p => p, out var tie) - 1;
var exiled = Game.GameNet.GameData!.GetPlayerById((byte)maxIdx)?.Controller;
if (exiled != null)
{
exiled.Die(DeathReason.Exile);
await _eventManager.CallAsync(new PlayerExileEvent(Game, Game.GetClientPlayer(exiled!.OwnerId)!, exiled));
}
await _eventManager.CallAsync(new MeetingEndedEvent(Game, this, exiled, tie));
}
}
}
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using Impostor.Api.Events.Managers;
using Impostor.Api.Games;
using Impostor.Api.Innersloth;
using Impostor.Api.Innersloth.Customization;
using Impostor.Api.Net.Messages;
using Impostor.Api.Utils;
using Impostor.Server.Net.State;
namespace Impostor.Server.Net.Inner.Objects
{
@@ -78,7 +80,6 @@ namespace Impostor.Server.Net.Inner.Objects
for (var i = 0; i < taskCount; i++)
{
Tasks[i] ??= new InnerGameData.TaskInfo();
Tasks[i].Deserialize(reader);
}
}
+6 -2
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Innersloth;
using Impostor.Api.Net.Inner;
using Impostor.Api.Net.Messages;
using Impostor.Api.Unity;
@@ -381,6 +382,11 @@ namespace Impostor.Server.Net.State
case InnerPlayerControl control:
{
if (GameState != GameStates.Started)
{
GameNet.GameData?.RemovePlayer(control);
}
// Remove InnerPlayerControl <-> IClientPlayer.
if (TryGetPlayer(control.OwnerId, out var player))
{
@@ -414,8 +420,6 @@ namespace Impostor.Server.Net.State
}
_allObjectsFast.Remove(obj.NetId);
obj.NetId = uint.MaxValue;
}
}
}
+1 -1
View File
@@ -39,7 +39,7 @@ namespace Impostor.Server.Net.State
_logger.LogInformation("{0} - Player {1} ({2}) has left.", Code, player.Client.Name, playerId);
if (GameState == GameStates.Starting || GameState == GameStates.Started)
if (GameState == GameStates.Starting || GameState == GameStates.Started || GameState == GameStates.NotStarted)
{
if (player.Character?.PlayerInfo != null)
{
@@ -8,17 +8,20 @@ namespace Impostor.Server.Plugins
{
private Assembly? _assembly;
public AssemblyInformation(AssemblyName assemblyName, string path, bool isPlugin)
public AssemblyInformation(AssemblyName assemblyName, string path, bool isPlugin, bool isDefaultAssembly)
{
AssemblyName = assemblyName;
Path = path;
IsPlugin = isPlugin;
IsDefaultAssembly = isDefaultAssembly;
}
public string Path { get; }
public bool IsPlugin { get; }
public bool IsDefaultAssembly { get; }
public AssemblyName AssemblyName { get; }
public Assembly Load(AssemblyLoadContext context)
@@ -9,6 +9,8 @@ namespace Impostor.Server.Plugins
bool IsPlugin { get; }
bool IsDefaultAssembly { get; }
Assembly Load(AssemblyLoadContext context);
}
}
@@ -7,16 +7,19 @@ namespace Impostor.Server.Plugins
{
private readonly Assembly _assembly;
public LoadedAssemblyInformation(Assembly assembly)
public LoadedAssemblyInformation(Assembly assembly, bool isDefaultAssembly)
{
AssemblyName = assembly.GetName();
_assembly = assembly;
IsDefaultAssembly = isDefaultAssembly;
}
public AssemblyName AssemblyName { get; }
public bool IsPlugin => false;
public bool IsDefaultAssembly { get; }
public Assembly Load(AssemblyLoadContext context)
{
return _assembly;
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Loader;
namespace Impostor.Server.Plugins
{
public class PluginAssemblyLoadContext : AssemblyLoadContext, IDisposable
{
private readonly IDictionary<string, IAssemblyInformation> _assembliesByName;
public PluginAssemblyLoadContext(IDictionary<string, IAssemblyInformation> assembliesByName)
: base(true)
{
_assembliesByName = assembliesByName;
}
public void Dispose()
{
_assembliesByName.Clear();
this.Unload();
}
protected override Assembly? Load(AssemblyName name)
{
return name.Name != null && _assembliesByName.TryGetValue(name.Name, out var assemblyInfo)
? assemblyInfo.Load(this)
: null;
}
}
}
@@ -6,7 +6,7 @@ using Impostor.Api.Plugins;
namespace Impostor.Server.Plugins
{
public class PluginInformation
public class PluginInformation : IPluginInformation
{
private readonly ImpostorPluginAttribute _attribute;
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Impostor.Api.Plugins;
@@ -13,20 +12,20 @@ namespace Impostor.Server.Plugins
{
private readonly ILogger<PluginLoaderService> _logger;
private readonly IServiceProvider _serviceProvider;
private readonly List<PluginInformation> _plugins;
private readonly PluginManager _pluginManager;
public PluginLoaderService(ILogger<PluginLoaderService> logger, IServiceProvider serviceProvider, List<PluginInformation> plugins)
public PluginLoaderService(ILogger<PluginLoaderService> logger, IServiceProvider serviceProvider, PluginManager pluginManager)
{
_logger = logger;
_serviceProvider = serviceProvider;
_plugins = plugins;
_pluginManager = pluginManager;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Loading plugins.");
_logger.LogInformation("Enabling plugins.");
foreach (var plugin in _plugins)
foreach (var plugin in _pluginManager.Plugins)
{
_logger.LogInformation("Enabling plugin {0}.", plugin);
@@ -38,10 +37,10 @@ namespace Impostor.Server.Plugins
}
_logger.LogInformation(
_plugins.Count == 1
? "Loaded {0} plugin."
: "Loaded {0} plugins.",
_plugins.Count
_pluginManager.Plugins.Count == 1
? "Enabled {0} plugin."
: "Enabled {0} plugins.",
_pluginManager.Plugins.Count
);
}
@@ -49,7 +48,7 @@ namespace Impostor.Server.Plugins
{
// Disable all plugins with a valid instance set.
// In the case of a failed startup, some can be null.
foreach (var plugin in _plugins)
foreach (var plugin in _pluginManager.Plugins)
{
if (plugin.Instance != null)
{
@@ -57,6 +56,7 @@ namespace Impostor.Server.Plugins
// Disable plugin.
await plugin.Instance.DisableAsync();
plugin.Instance = null;
}
}
}
@@ -4,24 +4,72 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading;
using System.Threading.Tasks;
using Impostor.Api.Plugins;
using Impostor.Server.Config;
using Impostor.Server.Plugins.Proxies;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Hosting;
using Serilog;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Impostor.Server.Plugins
{
public static class PluginLoader
public class PluginManager : IPluginManager, IHostedService
{
private static readonly ILogger Logger = Log.ForContext(typeof(PluginLoader));
private readonly ILogger<PluginManager> _logger;
private readonly IOptions<PluginLoaderConfig> _pluginConfig;
private readonly IConfiguration _configuration;
private readonly IServiceProvider _serviceProvider;
private readonly ServiceProxyCollection _serviceProxyCollection;
public static IHostBuilder UsePluginLoader(this IHostBuilder builder, PluginConfig config)
private PluginAssemblyLoadContext? _currentContext;
public PluginManager(ILogger<PluginManager> logger, IOptions<PluginLoaderConfig> pluginConfig, IConfiguration configuration, IServiceProvider serviceProvider, ServiceProxyCollection serviceProxyCollection)
{
var assemblyInfos = new List<IAssemblyInformation>();
var context = AssemblyLoadContext.Default;
_logger = logger;
_pluginConfig = pluginConfig;
_configuration = configuration;
_serviceProvider = serviceProvider;
_serviceProxyCollection = serviceProxyCollection;
}
public IHost? CurrentHost { get; private set; }
public List<PluginInformation> Plugins { get; } = new List<PluginInformation>();
IReadOnlyList<IPluginInformation> IPluginManager.Plugins => Plugins.AsReadOnly();
public Task StartAsync(CancellationToken cancellationToken)
{
CurrentHost = CreatePluginHost();
return CurrentHost.StartAsync(cancellationToken);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
if (CurrentHost != null)
{
await CurrentHost.StopAsync(cancellationToken);
if (CurrentHost is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else
{
CurrentHost.Dispose();
}
}
}
private IDictionary<string, IAssemblyInformation> GetAssemblyInformation()
{
// Add the plugins and libraries.
var config = _pluginConfig.Value;
var pluginPaths = new List<string>(config.Paths);
var libraryPaths = new List<string>(config.LibraryPaths);
CheckPaths(pluginPaths);
@@ -35,38 +83,70 @@ namespace Impostor.Server.Plugins
var matcher = new Matcher(StringComparison.OrdinalIgnoreCase);
matcher.AddInclude("*.dll");
matcher.AddExclude("Impostor.Api.dll");
matcher.AddExclude("Impostor.Server.dll");
RegisterAssemblies(pluginPaths, matcher, assemblyInfos, true);
RegisterAssemblies(libraryPaths, matcher, assemblyInfos, false);
var assembliesByName = AssemblyLoadContext.Default.Assemblies
.Where(a => a.GetName().Name != null)
.ToDictionary(
a => a.GetName().Name!,
a => (IAssemblyInformation)new LoadedAssemblyInformation(a, true));
// Register the resolver to the current context.
// TODO: Move this to a new context so we can unload/reload plugins.
context.Resolving += (loadContext, name) =>
RegisterAssemblies(assembliesByName, pluginPaths, matcher, true);
RegisterAssemblies(assembliesByName, libraryPaths, matcher, false);
return assembliesByName;
}
private IHost CreatePluginHost()
{
var assembliesByName = GetAssemblyInformation();
_currentContext ??= new PluginAssemblyLoadContext(assembliesByName);
var pluginHostBuilder = new HostBuilder()
.UseConsoleLifetime(options => options.SuppressStatusMessages = true)
.UseContentRoot(_configuration.GetValue<string>(HostDefaults.ContentRootKey))
.ConfigureHostConfiguration(builder => { builder.AddConfiguration(_configuration); })
.UseServiceProviderFactory(_ => new DefaultServiceProviderFactory());
Plugins.Clear();
Plugins.AddRange(GetPluginInformation(assembliesByName, _currentContext));
foreach (var plugin in Plugins)
{
Logger.Verbose("Loading assembly {0} v{1}", name.Name, name.Version);
plugin.Startup?.ConfigureHost(pluginHostBuilder);
}
// Some plugins may be referencing another Impostor.Api version and try to load it.
// We want to only use the one shipped with the server.
if (name.Name == "Impostor.Api")
pluginHostBuilder.ConfigureServices(services =>
{
services.AddHostedService<PluginLoaderService>();
foreach (var (type, lifetime) in _serviceProxyCollection.ServiceTypes)
{
return typeof(IPlugin).Assembly;
services.Add(new ServiceDescriptor(
type,
_ => _serviceProvider.GetService(type)!,
lifetime));
}
var info = assemblyInfos.FirstOrDefault(a => a.AssemblyName.Name == name.Name);
foreach (var plugin in Plugins)
{
plugin.Startup?.ConfigureServices(services);
}
});
return info?.Load(loadContext);
};
return pluginHostBuilder.Build();
}
// TODO: Catch uncaught exceptions.
var assemblies = assemblyInfos
private List<PluginInformation> GetPluginInformation(IDictionary<string, IAssemblyInformation> assembliesByName, PluginAssemblyLoadContext context)
{
var plugins = new List<PluginInformation>();
var pluginAssemblies = assembliesByName
.Values
.Where(a => a.IsPlugin)
.Select(a => context.LoadFromAssemblyName(a.AssemblyName))
.ToList();
// Find all plugins.
var plugins = new List<PluginInformation>();
foreach (var assembly in assemblies)
foreach (var assembly in pluginAssemblies)
{
// Find plugin startup.
var pluginStartup = assembly
@@ -76,7 +156,7 @@ namespace Impostor.Server.Plugins
if (pluginStartup.Count > 1)
{
Logger.Warning("A plugin may only define zero or one IPluginStartup implementation ({0}).", assembly);
_logger.LogWarning("A plugin may only define zero or one IPluginStartup implementation ({0}).", assembly);
continue;
}
@@ -91,7 +171,7 @@ namespace Impostor.Server.Plugins
if (plugin.Count != 1)
{
Logger.Warning("A plugin must define exactly one IPlugin or PluginBase implementation ({0}).", assembly);
_logger.LogWarning("A plugin must define exactly one IPlugin or PluginBase implementation ({0}).", assembly);
continue;
}
@@ -104,61 +184,75 @@ namespace Impostor.Server.Plugins
plugin.Single()));
}
var orderedPlugins = LoadOrderPlugins(plugins);
foreach (var plugin in orderedPlugins)
{
plugin.Startup?.ConfigureHost(builder);
}
builder.ConfigureServices(services =>
{
services.AddHostedService(provider => ActivatorUtilities.CreateInstance<PluginLoaderService>(provider, orderedPlugins));
foreach (var plugin in orderedPlugins)
{
plugin.Startup?.ConfigureServices(services);
}
});
return builder;
return OrderPlugins(plugins);
}
private static void CheckPaths(IEnumerable<string> paths)
private void CheckPaths(IEnumerable<string> paths)
{
foreach (var path in paths)
{
if (!Directory.Exists(path))
{
Logger.Warning("Path {path} was specified in the PluginLoader configuration, but this folder doesn't exist!", path);
_logger.LogWarning("Path {path} was specified in the PluginLoader configuration, but this folder doesn't exist!", path);
}
}
}
private static void RegisterAssemblies(
private void RegisterAssemblies(
IDictionary<string, IAssemblyInformation> assemblies,
IEnumerable<string> paths,
Matcher matcher,
ICollection<IAssemblyInformation> assemblyInfos,
bool isPlugin)
{
foreach (var path in paths.SelectMany(matcher.GetResultsInFullPath))
{
AssemblyName assemblyName;
AssemblyName name;
try
{
assemblyName = AssemblyName.GetAssemblyName(path);
name = AssemblyName.GetAssemblyName(path);
}
catch (BadImageFormatException)
{
continue;
}
assemblyInfos.Add(new AssemblyInformation(assemblyName, path, isPlugin));
if (name.Name == null)
{
_logger.LogWarning(
"Skipped {0} because the assembly name could not be resolved",
path);
continue;
}
if (assemblies.TryGetValue(name.Name, out var loaded))
{
var version = name.Version;
var loadedVersion = loaded.AssemblyName.Version;
if (loadedVersion != null && version != null && version.Major != loadedVersion.Major)
{
_logger.LogError(
"Assembly {0} is already loaded with version {1} while loading version {2}",
name.Name,
loadedVersion,
version);
}
else
{
_logger.LogDebug("Skipped assembly {0} because it's already loaded", name.Name);
}
continue;
}
_logger.LogTrace("Registered assembly {name} v{version} ({path})", name.Name, name.Version, path);
assemblies.Add(name.Name, new AssemblyInformation(name, path, isPlugin, false));
}
}
private static List<PluginInformation> LoadOrderPlugins(IEnumerable<PluginInformation> plugins)
private List<PluginInformation> OrderPlugins(IEnumerable<PluginInformation> plugins)
{
var pluginDictionary = new Dictionary<string, PluginInformation>();
var hardDependencies = new Dictionary<string, List<string>>();
@@ -208,7 +302,7 @@ namespace Impostor.Server.Plugins
return ordered;
}
private static List<string> CheckHardDependencies(
private List<string> CheckHardDependencies(
List<string> plugins,
IReadOnlyDictionary<string, List<string>> hardDependencies)
{
@@ -221,7 +315,7 @@ namespace Impostor.Server.Plugins
foreach (var dependency in hardDependencies[plugin].Where(dependency => !plugins.Contains(dependency)))
{
Logger.Error(
_logger.LogError(
"The plugin {plugin} has defined the plugin {dependency} as a hard dependency but its not present! {plugin} will not loaded.",
plugin,
dependency,
@@ -239,7 +333,7 @@ namespace Impostor.Server.Plugins
return plugins;
}
private static void RecursiveOrder(
private void RecursiveOrder(
string plugin,
IReadOnlyDictionary<string, List<string>> dependencyGraph,
ICollection<string> processed,
@@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Impostor.Server.Plugins.Proxies
{
public class ServiceProxyCollection
{
public ServiceProxyCollection(IReadOnlyList<ServiceRegistration> serviceTypes)
{
ServiceTypes = serviceTypes;
}
public IReadOnlyList<ServiceRegistration> ServiceTypes { get; }
}
}
@@ -0,0 +1,7 @@
using System;
using Microsoft.Extensions.DependencyInjection;
namespace Impostor.Server.Plugins.Proxies
{
public record ServiceRegistration(Type Type, ServiceLifetime Lifetime);
}
+59 -46
View File
@@ -1,12 +1,15 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Impostor.Api.Events;
using Impostor.Api.Events.Managers;
using Impostor.Api.Games;
using Impostor.Api.Games.Managers;
using Impostor.Api.Net.Custom;
using Impostor.Api.Net.Manager;
using Impostor.Api.Net.Messages;
using Impostor.Api.Plugins;
using Impostor.Api.Utils;
using Impostor.Hazel.Extensions;
using Impostor.Server.Config;
@@ -18,11 +21,13 @@ using Impostor.Server.Net.Manager;
using Impostor.Server.Net.Messages;
using Impostor.Server.Net.Redirector;
using Impostor.Server.Plugins;
using Impostor.Server.Plugins.Proxies;
using Impostor.Server.Recorder;
using Impostor.Server.Utils;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Serilog;
using Serilog.Events;
@@ -31,38 +36,16 @@ namespace Impostor.Server
{
internal static class Program
{
private static int Main(string[] args)
private static async Task<int> Main(string[] args)
{
#if DEBUG
var logLevel = LogEventLevel.Debug;
#else
var logLevel = LogEventLevel.Information;
#endif
if (args.Contains("--verbose"))
{
logLevel = LogEventLevel.Verbose;
}
else if (args.Contains("--errors-only"))
{
logLevel = LogEventLevel.Error;
}
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Is(logLevel)
#if DEBUG
.MinimumLevel.Override("Microsoft", LogEventLevel.Debug)
#else
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
#endif
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
.CreateBootstrapLogger();
try
{
Log.Information("Starting Impostor v{0}", DotnetUtils.Version);
CreateHostBuilder(args).Build().Run();
await CreateHostBuilder(args).RunConsoleAsync();
return 0;
}
catch (Exception ex)
@@ -76,25 +59,8 @@ namespace Impostor.Server
}
}
private static IConfiguration CreateConfiguration(string[] args)
{
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.SetBasePath(Directory.GetCurrentDirectory());
configurationBuilder.AddJsonFile("config.json", true);
configurationBuilder.AddJsonFile("config.Development.json", true);
configurationBuilder.AddEnvironmentVariables(prefix: "IMPOSTOR_");
configurationBuilder.AddCommandLine(args);
return configurationBuilder.Build();
}
private static IHostBuilder CreateHostBuilder(string[] args)
{
var configuration = CreateConfiguration(args);
var pluginConfig = configuration.GetSection("PluginLoader")
.Get<PluginConfig>() ?? new PluginConfig();
return Host.CreateDefaultBuilder(args)
.UseContentRoot(Directory.GetCurrentDirectory())
#if DEBUG
@@ -104,7 +70,10 @@ namespace Impostor.Server
#endif
.ConfigureAppConfiguration(builder =>
{
builder.AddConfiguration(configuration);
builder.AddJsonFile("config.json", true);
builder.AddJsonFile("config.Development.json", true);
builder.AddEnvironmentVariables(prefix: "IMPOSTOR_");
builder.AddCommandLine(args);
})
.ConfigureServices((host, services) =>
{
@@ -134,6 +103,7 @@ namespace Impostor.Server
services.Configure<AnnouncementsServerConfig>(host.Configuration.GetSection(AnnouncementsServerConfig.Section));
services.Configure<AuthServerConfig>(host.Configuration.GetSection(AuthServerConfig.Section));
services.Configure<ServerRedirectorConfig>(host.Configuration.GetSection(ServerRedirectorConfig.Section));
services.Configure<PluginLoaderConfig>(host.Configuration.GetSection(PluginLoaderConfig.Section));
if (redirector.Enabled)
{
@@ -228,10 +198,53 @@ namespace Impostor.Server
{
services.AddHostedService<AuthService>();
}
services.AddSingleton<PluginManager>();
services.AddSingleton<IPluginManager>(p => p.GetRequiredService<PluginManager>());
services.AddHostedService(p => p.GetRequiredService<PluginManager>());
})
.UseSerilog()
.UseConsoleLifetime()
.UsePluginLoader(pluginConfig);
.UseSerilog((context, loggerConfiguration) =>
{
#if DEBUG
var logLevel = LogEventLevel.Debug;
#else
var logLevel = LogEventLevel.Information;
#endif
if (args.Contains("--verbose"))
{
logLevel = LogEventLevel.Verbose;
}
else if (args.Contains("--errors-only"))
{
logLevel = LogEventLevel.Error;
}
loggerConfiguration
.MinimumLevel.Is(logLevel)
#if DEBUG
.MinimumLevel.Override("Microsoft", LogEventLevel.Debug)
#else
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
#endif
.Enrich.FromLogContext()
.WriteTo.Console()
.ReadFrom.Configuration(context.Configuration);
})
.ConfigureServices(services =>
{
services.AddTransient(serviceProvider => serviceProvider.GetRequiredService<PluginManager>().CurrentHost?.Services.GetServices<IEventListener>() ?? Enumerable.Empty<IEventListener>());
var apiAssembly = typeof(IPlugin).Assembly;
var serilogAssembly = typeof(Serilog.ILogger).Assembly;
services.AddSingleton(new ServiceProxyCollection(
services
.Where(t => t.ServiceType.Assembly == apiAssembly || t.ServiceType.Assembly == serilogAssembly || t.ServiceType == typeof(PluginManager) || t.ServiceType == typeof(ILoggerFactory))
.Select(t => new ServiceRegistration(t.ServiceType, t.Lifetime))
.Distinct()
.ToArray()));
});
}
}
}