mirror of
https://github.com/Impostor/Impostor.git
synced 2026-10-21 05:37:28 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e857ed555 | ||
|
|
9d31752bf0 | ||
|
|
ced530b83a | ||
|
|
8f1cba8429 |
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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+150
-56
@@ -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);
|
||||
}
|
||||
@@ -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()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user