mirror of
https://github.com/Empostor/Empostor.git
synced 2026-10-19 15:51:39 +00:00
filter + upstream support
This commit is contained in:
1 parent
68ac3de0bd
commit
6498752430
54 files changed
+61942
-56
No files matched your search
@@ -85,6 +85,14 @@ public sealed class SourceGenerator : IIncrementalGenerator
|
||||
enumGenerator.Generate("SystemTypes", underlyingType: CSharpEnumUnderlyingType.Byte);
|
||||
enumGenerator.Generate("Language", sourceName: "SupportedLangs");
|
||||
enumGenerator.Generate("TaskTypes");
|
||||
enumGenerator.Generate("BoolOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("ByteOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("CategorizedOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("FloatArrayOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("FloatOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("Int32ArrayOptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("Int32OptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
enumGenerator.Generate("UInt32OptionNames", "Empostor.Api.Innersloth.GameFilters");
|
||||
|
||||
enumGenerator.Generate("RpcCalls", "Empostor.Api.Net.Inner", underlyingType: CSharpEnumUnderlyingType.Byte);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Empostor.Api.Net.Inner.Objects;
|
||||
using Empostor.Api.Net.Inner.Objects;
|
||||
|
||||
namespace Empostor.Api.Events.Meeting
|
||||
{
|
||||
@@ -13,5 +13,15 @@ namespace Empostor.Api.Events.Meeting
|
||||
/// Gets a value indicating whether meeting result is a tie.
|
||||
/// </summary>
|
||||
bool IsTie { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a Judge overruled the vote result.
|
||||
/// </summary>
|
||||
bool WasOverruled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the nonce of the winning Judge overrule, or 0 if the meeting wasn't overruled.
|
||||
/// </summary>
|
||||
ushort OverrideId { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
@@ -49,6 +50,11 @@ namespace Empostor.Api.Games
|
||||
/// </remarks>
|
||||
bool IsHostAuthoritive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mod GUID that the game was registered with through the AMCI.
|
||||
/// </summary>
|
||||
Guid? ModGuid { get; }
|
||||
|
||||
IClientPlayer? GetClientPlayer(int clientId);
|
||||
|
||||
T? FindObjectByNetId<T>(uint netId)
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"VisualTasks": 1,
|
||||
"GhostsDoTasks": 2,
|
||||
"ConfirmImpostor": 3,
|
||||
"AnonymousVotes": 4,
|
||||
"IsDefaults": 5,
|
||||
"UseFlashlight": 6,
|
||||
"SeekerFinalVents": 7,
|
||||
"SeekerFinalMap": 8,
|
||||
"SeekerPings": 9,
|
||||
"ShowCrewmateNames": 10,
|
||||
"Roles": 11,
|
||||
"ShapeshifterLeaveSkin": 1000,
|
||||
"ImpostorsCanSeeProtect": 1100,
|
||||
"NoisemakerImpostorAlert": 1300
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"MapId": 1
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"KillCooldown": 0,
|
||||
"PlayerSpeed": 1,
|
||||
"EmergencyMeetingCount": 2,
|
||||
"EmergencyMeetingCooldown": 3,
|
||||
"DiscussionTime": 4,
|
||||
"VotingTime": 5,
|
||||
"CrewmateVision": 6,
|
||||
"HidingTime": 7,
|
||||
"MaxTimeInVent": 8,
|
||||
"ImposterVision": 9,
|
||||
"FinalHideTime": 10,
|
||||
"FinalHideImposterSpeed": 11
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"KillDistances": 1
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"KillCooldown": 1,
|
||||
"PlayerSpeedMod": 2,
|
||||
"ImpostorLightMod": 3,
|
||||
"CrewLightMod": 4,
|
||||
"CrewmateTimeInVent": 100,
|
||||
"FinalEscapeTime": 101,
|
||||
"EscapeTime": 102,
|
||||
"SeekerFinalSpeed": 103,
|
||||
"MaxPingTime": 104,
|
||||
"CrewmateFlashlightSize": 105,
|
||||
"ImpostorFlashlightSize": 106,
|
||||
"ShapeshifterCooldown": 1000,
|
||||
"ShapeshifterDuration": 1001,
|
||||
"ProtectionDurationSeconds": 1100,
|
||||
"GuardianAngelCooldown": 1101,
|
||||
"ScientistCooldown": 1200,
|
||||
"ScientistBatteryCharge": 1201,
|
||||
"EngineerCooldown": 1300,
|
||||
"EngineerInVentMaxTime": 1301,
|
||||
"PhantomCooldown": 1500,
|
||||
"PhantomDuration": 1501,
|
||||
"TrackerCooldown": 1550,
|
||||
"TrackerDuration": 1551,
|
||||
"TrackerDelay": 1552,
|
||||
"NoisemakerAlertDuration": 1600,
|
||||
"ViperDissolveTime": 1601,
|
||||
"DetectiveSuspectLimit": 1602,
|
||||
"JudgeTaskRequirementPercentage": 1620
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"MaxImpostors": 1,
|
||||
"MinPlayers": 2
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"NumImpostors": 1,
|
||||
"KillDistance": 2,
|
||||
"NumEmergencyMeetings": 3,
|
||||
"EmergencyCooldown": 4,
|
||||
"DiscussionTime": 5,
|
||||
"VotingTime": 6,
|
||||
"MaxImpostors": 7,
|
||||
"MinPlayers": 8,
|
||||
"MaxPlayers": 9,
|
||||
"NumCommonTasks": 10,
|
||||
"NumShortTasks": 11,
|
||||
"NumLongTasks": 12,
|
||||
"TaskBarMode": 13,
|
||||
"Tag": 14,
|
||||
"CrewmatesRemainingForVitals": 100,
|
||||
"CrewmateVentUses": 101,
|
||||
"ImpostorPlayerID": 102,
|
||||
"RulePreset": 200
|
||||
}
|
||||
@@ -11,5 +11,6 @@
|
||||
"Phantom": 9,
|
||||
"Tracker": 10,
|
||||
"Detective": 12,
|
||||
"Viper": 18
|
||||
"Viper": 18,
|
||||
"Judge": 19
|
||||
}
|
||||
@@ -59,5 +59,6 @@
|
||||
"CheckVanish": 62,
|
||||
"StartVanish": 63,
|
||||
"CheckAppear": 64,
|
||||
"StartAppear": 65
|
||||
"StartAppear": 65,
|
||||
"QueueOverruleVotes": 66
|
||||
}
|
||||
@@ -626,6 +626,14 @@
|
||||
"SanctionSelfHarmPromotion": 1720,
|
||||
"SanctionRepeatOffender10": 1721,
|
||||
"SanctionUnknown": 1722,
|
||||
"ReplicatorAbility": 1800,
|
||||
"ReplicatorRole": 1801,
|
||||
"ReplicatorBlurb": 1802,
|
||||
"ReplicatorBlurbMed": 1803,
|
||||
"ReplicatorBlurbLong": 1804,
|
||||
"ReplicatorCooldown": 1805,
|
||||
"ReplicatorDuration": 1806,
|
||||
"ReplicatorQuantity": 1807,
|
||||
"ScreenShakeOption": 1900,
|
||||
"FeaturedItems": 1901,
|
||||
"FeaturedBundles": 1902,
|
||||
@@ -1576,6 +1584,14 @@
|
||||
"XboxCommsCreateGameError": 3702,
|
||||
"XboxCommsSearchError": 3703,
|
||||
"XboxCommsJoinError": 3704,
|
||||
"WatchAdBody": 3800,
|
||||
"WatchAdTitle": 3801,
|
||||
"WatchAd": 3802,
|
||||
"RewardAdTitle": 3803,
|
||||
"RewardAdBody1": 3804,
|
||||
"RewardAdBody2": 3805,
|
||||
"WatchAdErrorBody": 3806,
|
||||
"WatchAdErrorTitle": 3807,
|
||||
"DetectiveRole": 4000,
|
||||
"DetectiveAbility": 4001,
|
||||
"DetectiveAbilityNotes": 4002,
|
||||
@@ -1623,5 +1639,28 @@
|
||||
"DetectiveNotesPrepositionMaybe": 4079,
|
||||
"DetectiveNotesTitlePostIt": 4080,
|
||||
"DetectiveNotesPostIt": 4081,
|
||||
"DetectiveNotesSuspectNumber": 4082
|
||||
"DetectiveNotesSuspectNumber": 4082,
|
||||
"AprilFoolsMode": 4083,
|
||||
"MatchInfoGuideTitle": 4084,
|
||||
"MatchInfoGuidePlayersLabel": 4085,
|
||||
"MatchInfoGuideNoRolesMessage": 4086,
|
||||
"JudgeRole": 4090,
|
||||
"JudgeAbility": 4091,
|
||||
"JudgeBlurb": 4092,
|
||||
"JudgeBlurbMed": 4093,
|
||||
"JudgeBlurbLong": 4094,
|
||||
"JudgeTaskRequirement": 4095,
|
||||
"RolesHelp_Judge_01": 4096,
|
||||
"RolesHelp_Judge_02": 4097,
|
||||
"JudgeAbilityUnlocked": 4098,
|
||||
"JudgeAbilityProgress": 4099,
|
||||
"JudgeCourtAdjourned": 4100,
|
||||
"JudgeHasSpoken": 4101,
|
||||
"StatsJudgeSuccessfulOverrules": 4102,
|
||||
"StatsJudgeFailedOverrules": 4103,
|
||||
"QCJudges": 4104,
|
||||
"QCJudgeOverrule": 4105,
|
||||
"QCJudgePlease": 4106,
|
||||
"QCJudgeWillOverrule": 4107,
|
||||
"JudgeBeatYouToIt": 4108
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"Invalid": 0,
|
||||
"Keywords": 1
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dumpostorVersion": "1.0.1-dev",
|
||||
"gameVersion": "2025.10.14",
|
||||
"gameVersion": "2026.8.18",
|
||||
"platformType": "StandaloneItch"
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"hatIds": [
|
||||
"hat_pk04_beanie",
|
||||
"hat_pk05_Cheese",
|
||||
"hat_pk05_Cherry",
|
||||
"hat_Chocolate",
|
||||
"hat_Zipper",
|
||||
"hat_pk02_Crown",
|
||||
"hat_babybean",
|
||||
"hat_Sorry",
|
||||
"hat_mareLwyd",
|
||||
"hat_fishingHat",
|
||||
"hat_schnapp",
|
||||
"hat_pk03_Traffic",
|
||||
"hat_Pot",
|
||||
"hat_AbominalHat",
|
||||
"hat_tombstone",
|
||||
"hat_pk04_MiniCrewmate",
|
||||
"hat_pk05_Wizardhat",
|
||||
"hat_pk05_Ellryhat",
|
||||
"hat_pk05_EllieToppat"
|
||||
],
|
||||
"visorIds": [
|
||||
"visor_pk01_AngeryVisor",
|
||||
"visor_pk01_DumStickerVisor",
|
||||
"visor_vr_Vr-Black",
|
||||
"visor_pk01_PaperMaskVisor",
|
||||
"visor_wash"
|
||||
],
|
||||
"petIds": [
|
||||
"pet_YuleGoatPet",
|
||||
"pet_HamPet",
|
||||
"pet_Lava",
|
||||
"pet_Snow",
|
||||
"pet_Bush"
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class BoolGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "bool";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required List<bool> AcceptedValues { get; set; }
|
||||
|
||||
[JsonPropertyName("OptionEnum")]
|
||||
public required BoolOptionNames OptionEnum { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class CategorizedGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "cat";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required List<int> AcceptedValues { get; set; }
|
||||
|
||||
[JsonPropertyName("OptionEnum")]
|
||||
public required CategorizedOptionNames OptionEnum { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class ChatModeGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "chat";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required byte AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class GameFilter
|
||||
{
|
||||
[JsonConstructor]
|
||||
public GameFilter(string optionType, string key, string subFilterString)
|
||||
{
|
||||
this.OptionType = optionType;
|
||||
this.Key = key;
|
||||
this.SubFilterString = subFilterString;
|
||||
this.SubFilter = ResolveSubFilter(OptionType, SubFilterString);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public ISubFilter SubFilter { get; set; }
|
||||
|
||||
[JsonPropertyName("OptionType")]
|
||||
public required string OptionType { get; set; }
|
||||
|
||||
[JsonPropertyName("Key")]
|
||||
public required string Key { get; set; }
|
||||
|
||||
[JsonPropertyName("SubFilterString")]
|
||||
public required string SubFilterString { get; set; }
|
||||
|
||||
public void ModifySubFilter(ISubFilter subFilter)
|
||||
{
|
||||
this.SubFilter = subFilter;
|
||||
}
|
||||
|
||||
[OnSerializing]
|
||||
internal void OnSerializing(StreamingContext context)
|
||||
{
|
||||
this.SubFilterString = JsonSerializer.Serialize(this.SubFilter);
|
||||
}
|
||||
|
||||
[OnDeserialized]
|
||||
internal void OnDeserialized(StreamingContext context)
|
||||
{
|
||||
this.SubFilter = this.ResolveSubFilter(this.OptionType, this.SubFilterString);
|
||||
}
|
||||
|
||||
private ISubFilter ResolveSubFilter(string type, string filterString)
|
||||
{
|
||||
if (type != null)
|
||||
{
|
||||
switch (type.ToLower())
|
||||
{
|
||||
case "int":
|
||||
return JsonSerializer.Deserialize<IntGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for IntGameFilter");
|
||||
case "platform":
|
||||
return JsonSerializer.Deserialize<PlatformGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for PlatformGameFilter");
|
||||
case "cat":
|
||||
return JsonSerializer.Deserialize<CategorizedGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for CategorizedGameFilter");
|
||||
case "languages":
|
||||
return JsonSerializer.Deserialize<LanguageFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for LanguageFilter");
|
||||
case "chat":
|
||||
return JsonSerializer.Deserialize<ChatModeGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for ChatModeGameFilter");
|
||||
case "map":
|
||||
return JsonSerializer.Deserialize<MapGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for MapGameFilter");
|
||||
case "bool":
|
||||
return JsonSerializer.Deserialize<BoolGameFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for BoolGameFilter");
|
||||
case "mod":
|
||||
return JsonSerializer.Deserialize<ModFilter>(filterString)
|
||||
?? throw new InvalidOperationException("Deserialization returned null for ModFilter");
|
||||
default:
|
||||
throw new InvalidOperationException("No type matches subfilter");
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Must provide type for sub filter");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class GameFilterSet
|
||||
{
|
||||
[JsonConstructor]
|
||||
public GameFilterSet(GameModes gameMode, List<GameFilter> filters)
|
||||
{
|
||||
GameMode = gameMode;
|
||||
Filters = filters;
|
||||
}
|
||||
|
||||
[JsonPropertyName("GameMode")]
|
||||
public required GameModes GameMode { get; set; }
|
||||
|
||||
[JsonPropertyName("Filters")]
|
||||
public required List<GameFilter> Filters { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class GameFiltersList
|
||||
{
|
||||
[JsonPropertyName("FilterSets")]
|
||||
public required List<GameFilterSet> FilterSets { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
public interface ISubFilter
|
||||
{
|
||||
string FilterType { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class IntGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "int";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required List<int> AcceptedValues { get; set; }
|
||||
|
||||
[JsonPropertyName("OptionEnum")]
|
||||
public required Int32OptionNames OptionEnum { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class LanguageFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "languages";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required uint AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class MapGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "map";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required byte AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
// https://github.com/Innersloth-LLC/AmongUsModdingInformation
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class ModFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "mod";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required Guid AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Empostor.Api.Innersloth.GameFilters
|
||||
{
|
||||
[Serializable]
|
||||
public class PlatformGameFilter : ISubFilter
|
||||
{
|
||||
[JsonPropertyName("FilterType")]
|
||||
public string FilterType { get; } = "platform";
|
||||
|
||||
[JsonPropertyName("AcceptedValues")]
|
||||
public required uint AcceptedValues { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ namespace Empostor.Api.Innersloth.GameOptions;
|
||||
|
||||
public class HideNSeekGameOptions : IGameOptions
|
||||
{
|
||||
public const int LatestVersion = 10;
|
||||
public const int LatestVersion = 11;
|
||||
|
||||
public HideNSeekGameOptions(byte version = LatestVersion)
|
||||
{
|
||||
@@ -107,6 +107,11 @@ public class HideNSeekGameOptions : IGameOptions
|
||||
|
||||
public void Deserialize(IMessageReader reader)
|
||||
{
|
||||
if (Version > LatestVersion)
|
||||
{
|
||||
IGameOptions.ThrowUnknownVersion<HideNSeekGameOptions>(Version);
|
||||
}
|
||||
|
||||
if (Version >= 8)
|
||||
{
|
||||
SpecialMode = (SpecialGameModes)reader.ReadByte();
|
||||
@@ -142,11 +147,6 @@ public class HideNSeekGameOptions : IGameOptions
|
||||
{
|
||||
Tag = (GameTags)reader.ReadByte();
|
||||
}
|
||||
|
||||
if (Version > LatestVersion)
|
||||
{
|
||||
IGameOptions.ThrowUnknownVersion<HideNSeekGameOptions>(Version);
|
||||
}
|
||||
}
|
||||
|
||||
public void Serialize(IMessageWriter writer)
|
||||
|
||||
@@ -49,6 +49,8 @@ public interface IGameOptions
|
||||
/// </summary>
|
||||
public bool IsDefaults { get; set; }
|
||||
|
||||
public GameTags Tag { get; set; }
|
||||
|
||||
public void Serialize(IMessageWriter writer);
|
||||
|
||||
public static void EnsureVersionIsModular<TCaller>(byte version)
|
||||
|
||||
@@ -145,6 +145,8 @@ public class LegacyGameOptionsData : IGameOptions
|
||||
/// </summary>
|
||||
public bool IsDefaults { get; set; } = true;
|
||||
|
||||
public GameTags Tag { get; set; } = 0;
|
||||
|
||||
public static LegacyGameOptionsData Deserialize(IMessageReader reader, byte version)
|
||||
{
|
||||
var options = new LegacyGameOptionsData(version);
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Empostor.Api.Innersloth.GameOptions;
|
||||
|
||||
public class NormalGameOptions : IGameOptions
|
||||
{
|
||||
public const int LatestVersion = 10;
|
||||
public const int LatestVersion = 11;
|
||||
|
||||
public NormalGameOptions(byte version = LatestVersion)
|
||||
{
|
||||
@@ -144,6 +144,11 @@ public class NormalGameOptions : IGameOptions
|
||||
|
||||
public void Deserialize(IMessageReader reader)
|
||||
{
|
||||
if (Version > LatestVersion)
|
||||
{
|
||||
IGameOptions.ThrowUnknownVersion<NormalGameOptions>(Version);
|
||||
}
|
||||
|
||||
if (Version >= 8)
|
||||
{
|
||||
SpecialMode = (SpecialGameModes)reader.ReadByte();
|
||||
@@ -184,11 +189,6 @@ public class NormalGameOptions : IGameOptions
|
||||
}
|
||||
|
||||
RoleOptions.Deserialize(reader);
|
||||
|
||||
if (Version > LatestVersion)
|
||||
{
|
||||
IGameOptions.ThrowUnknownVersion<NormalGameOptions>(Version);
|
||||
}
|
||||
}
|
||||
|
||||
public void Serialize(IMessageWriter writer)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace Empostor.Api.Innersloth.GameOptions.RoleOptions;
|
||||
|
||||
public class JudgeRoleOptions : IRoleOptions
|
||||
{
|
||||
public JudgeRoleOptions(byte version)
|
||||
{
|
||||
Version = version;
|
||||
}
|
||||
|
||||
public byte Version { get; }
|
||||
|
||||
public RoleTypes Type => RoleTypes.Judge;
|
||||
|
||||
public float JudgeTaskRequirementPercentage { get; set; } = 50f;
|
||||
|
||||
public static JudgeRoleOptions Deserialize(IMessageReader reader, byte version)
|
||||
{
|
||||
var options = new JudgeRoleOptions(version);
|
||||
|
||||
options.JudgeTaskRequirementPercentage = (int)reader.ReadByte();
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public void Serialize(IMessageWriter writer)
|
||||
{
|
||||
writer.Write((byte)JudgeTaskRequirementPercentage);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ public class RoleOptionsCollection
|
||||
RoleTypes.Tracker => TrackerRoleOptions.Deserialize(roleOptionsReader, Version),
|
||||
RoleTypes.Detective => DetectiveRoleOptions.Deserialize(roleOptionsReader, Version),
|
||||
RoleTypes.Viper => ViperRoleOptions.Deserialize(roleOptionsReader, Version),
|
||||
RoleTypes.Judge => JudgeRoleOptions.Deserialize(roleOptionsReader, Version),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(roleType), roleType, null),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Empostor.Api.Events.Player;
|
||||
|
||||
@@ -6,6 +6,24 @@ namespace Empostor.Api.Net.Inner.Objects
|
||||
{
|
||||
public interface IInnerMeetingHud : IInnerNetObject
|
||||
{
|
||||
public interface IJudgeOverrule
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the player id of the Judge that queued this overrule.
|
||||
/// </summary>
|
||||
byte JudgePlayerId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player id of the player the Judge wants to overrule.
|
||||
/// </summary>
|
||||
byte OverruledPlayerId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the overrule nonce used to match this overrule to the voting result.
|
||||
/// </summary>
|
||||
ushort OverruleNonce { get; }
|
||||
}
|
||||
|
||||
public interface IPlayerVoteArea
|
||||
{
|
||||
/// <summary>
|
||||
@@ -55,5 +73,10 @@ namespace Empostor.Api.Net.Inner.Objects
|
||||
/// Gets the player that started the meeting.
|
||||
/// </summary>
|
||||
IInnerPlayerInfo? Reporter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Judge overrules queued in this meeting.
|
||||
/// </summary>
|
||||
IReadOnlyCollection<IJudgeOverrule> JudgeOverrules { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using Empostor.Api.Innersloth;
|
||||
using Empostor.Api.Innersloth.GameOptions;
|
||||
|
||||
namespace Empostor.Api.Net.Messages.C2S
|
||||
{
|
||||
/// <summary>
|
||||
/// Special serialization rules for AU MCI HostGame messages.
|
||||
/// <see href="https://github.com/Innersloth-LLC/AmongUsModdingInformation?tab=readme-ov-file#all-client-mod-client-identification"/>
|
||||
/// for more information.
|
||||
/// </summary>
|
||||
public static class Message25HostModdedGameC2S
|
||||
{
|
||||
public static void Serialize(IMessageWriter writer, IGameOptions gameOptions, CrossplayFlags crossplayFlags, GameFilterOptions gameFilterOptions)
|
||||
{
|
||||
Message00HostGameC2S.Serialize(writer, gameOptions, crossplayFlags, gameFilterOptions);
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out IGameOptions gameOptions, out CrossplayFlags crossplayFlags, out GameFilterOptions gameFilterOptions, out Guid modGuid)
|
||||
{
|
||||
Message00HostGameC2S.Deserialize(reader, out gameOptions, out crossplayFlags, out gameFilterOptions);
|
||||
modGuid = new Guid(reader.ReadBytes(16).Span);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Empostor.Api.Net.Messages
|
||||
public const byte QueryPlatformIds = 22;
|
||||
public const byte QueryLobbyInfo = 23;
|
||||
public const byte EndGameHostMigration = 24;
|
||||
public const byte HostModdedGame = 25;
|
||||
public const byte PackedGameDataTo = 26;
|
||||
|
||||
private static readonly Dictionary<byte, string> FlagCache;
|
||||
|
||||
@@ -2,6 +2,8 @@ namespace Empostor.Api.Net.Messages.Rpcs
|
||||
{
|
||||
public static class Rpc23VotingComplete
|
||||
{
|
||||
// Pre 18.0 version
|
||||
// TODO clean up when 17.4 and below are unsupported
|
||||
public static void Serialize(IMessageWriter writer, byte[] states, byte playerId, bool tie)
|
||||
{
|
||||
writer.WriteBytesAndSize(states);
|
||||
@@ -9,6 +11,15 @@ namespace Empostor.Api.Net.Messages.Rpcs
|
||||
writer.Write(tie);
|
||||
}
|
||||
|
||||
public static void Serialize(IMessageWriter writer, byte[] states, byte playerId, bool tie, bool wasOverruled, ushort overrideId)
|
||||
{
|
||||
writer.WriteBytesAndSize(states);
|
||||
writer.Write(playerId);
|
||||
writer.Write(tie);
|
||||
writer.Write(wasOverruled);
|
||||
writer.Write(overrideId);
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out IMessageReader[] states, out byte playerId, out bool tie)
|
||||
{
|
||||
var length = reader.ReadPackedInt32();
|
||||
@@ -21,5 +32,21 @@ namespace Empostor.Api.Net.Messages.Rpcs
|
||||
playerId = reader.ReadByte();
|
||||
tie = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out IMessageReader[] states, out byte playerId, out bool tie, out bool wasOverruled, out ushort overrideId)
|
||||
{
|
||||
var length = reader.ReadPackedInt32();
|
||||
states = new IMessageReader[length];
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
states[i] = reader.ReadMessage();
|
||||
}
|
||||
|
||||
playerId = reader.ReadByte();
|
||||
tie = reader.ReadBoolean();
|
||||
|
||||
wasOverruled = reader.ReadBoolean();
|
||||
overrideId = reader.ReadUInt16();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Empostor.Api.Net.Messages.Rpcs
|
||||
{
|
||||
public static class Rpc66QueueOverruleVotes
|
||||
{
|
||||
public static void Serialize(IMessageWriter writer, byte judgePlayerId, byte targetPlayerId, ushort overruleNonce)
|
||||
{
|
||||
writer.Write(judgePlayerId);
|
||||
writer.Write(targetPlayerId);
|
||||
writer.Write(overruleNonce);
|
||||
}
|
||||
|
||||
public static void Deserialize(IMessageReader reader, out byte judgePlayerId, out byte targetPlayerId, out ushort overruleNonce)
|
||||
{
|
||||
judgePlayerId = reader.ReadByte();
|
||||
targetPlayerId = reader.ReadByte();
|
||||
overruleNonce = reader.ReadUInt16();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Empostor.Api.Events.Meeting;
|
||||
using Empostor.Api.Events.Meeting;
|
||||
using Empostor.Api.Games;
|
||||
using Empostor.Api.Net.Inner.Objects;
|
||||
|
||||
@@ -6,12 +6,14 @@ namespace Empostor.Server.Events.Meeting
|
||||
{
|
||||
public class MeetingEndedEvent : IMeetingEndedEvent
|
||||
{
|
||||
public MeetingEndedEvent(IGame game, IInnerMeetingHud meetingHud, IInnerPlayerControl? exiled, bool isTie)
|
||||
public MeetingEndedEvent(IGame game, IInnerMeetingHud meetingHud, IInnerPlayerControl? exiled, bool isTie, bool wasOverruled = false, ushort overrideId = 0)
|
||||
{
|
||||
Game = game;
|
||||
MeetingHud = meetingHud;
|
||||
Exiled = exiled;
|
||||
IsTie = isTie;
|
||||
WasOverruled = wasOverruled;
|
||||
OverrideId = overrideId;
|
||||
}
|
||||
|
||||
public IGame Game { get; }
|
||||
@@ -21,5 +23,9 @@ namespace Empostor.Server.Events.Meeting
|
||||
public IInnerPlayerControl? Exiled { get; }
|
||||
|
||||
public bool IsTie { get; }
|
||||
|
||||
public bool WasOverruled { get; }
|
||||
|
||||
public ushort OverrideId { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Empostor.Server.Http;
|
||||
|
||||
[Route("/api/filters")]
|
||||
public sealed class FiltersController : ControllerBase
|
||||
{
|
||||
private static readonly string[] Value = new[]
|
||||
{
|
||||
"Tags",
|
||||
|
||||
// "PlayerSpeed",
|
||||
// "Roles",
|
||||
// "KillCooldown",
|
||||
// "VotingTime",
|
||||
"NumImposters",
|
||||
|
||||
// "VisualTasks",
|
||||
// "AnonymousVotes",
|
||||
// "ConfirmEjects",
|
||||
// "DiscussionTime",
|
||||
// "EmergencyCooldown",
|
||||
// "NumEmergencyMeetings",
|
||||
// "NumCommonTasks",
|
||||
// "NumShortTasks",
|
||||
// "NumLongTasks",
|
||||
// "KillDistance",
|
||||
};
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult GetFilters([FromHeader] AuthenticationHeaderValue authorization)
|
||||
{
|
||||
if (authorization.Scheme != "Bearer" || authorization.Parameter == null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
TokenController.TokenResponse? token = null;
|
||||
try
|
||||
{
|
||||
token = JsonSerializer.Deserialize<TokenController.TokenResponse>(Convert.FromBase64String(authorization.Parameter));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
filters = Value,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,15 @@ using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Web;
|
||||
using Empostor.Api.Config;
|
||||
using Empostor.Api.Games;
|
||||
using Empostor.Api.Games.Managers;
|
||||
using Empostor.Api.Innersloth;
|
||||
using Empostor.Api.Innersloth.GameFilters;
|
||||
using Empostor.Server.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Empostor.Server.Http;
|
||||
@@ -21,15 +25,18 @@ public sealed class GamesController : ControllerBase
|
||||
private readonly IGameManager _gameManager;
|
||||
private readonly ListingManager _listingManager;
|
||||
private readonly ServerConfig _serverConfig;
|
||||
private readonly ILogger<GamesController> _logger;
|
||||
|
||||
public GamesController(
|
||||
IGameManager gameManager,
|
||||
ListingManager listingManager,
|
||||
IOptions<ServerConfig> serverConfig)
|
||||
IOptions<ServerConfig> serverConfig,
|
||||
ILogger<GamesController> logger)
|
||||
{
|
||||
_gameManager = gameManager;
|
||||
_listingManager = listingManager;
|
||||
_serverConfig = serverConfig.Value;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -108,26 +115,54 @@ public sealed class GamesController : ControllerBase
|
||||
|
||||
/// <summary>
|
||||
/// Filtered lobby list (Among Us 16.0.0+).
|
||||
/// No support filter conditions on the latest version.
|
||||
/// </summary>
|
||||
[HttpGet("filtered")]
|
||||
public IActionResult ShowFilteredLobbies()
|
||||
public IActionResult ShowFilteredLobbies([FromQuery] string filter)
|
||||
{
|
||||
var port = GetDeltaPort();
|
||||
var listings = _gameManager.Games
|
||||
.Where(g => g.IsPublic)
|
||||
.Select(g => GameListing.From(g, port))
|
||||
.ToList();
|
||||
|
||||
return Ok(new
|
||||
if (string.IsNullOrEmpty(filter))
|
||||
{
|
||||
games = listings,
|
||||
metadata = new
|
||||
return BadRequest(new MatchmakerResponse(new MatchmakerError(DisconnectReason.ServerError, "filter query parameter not provided")));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var decodedFilter = HttpUtility.UrlDecode(filter);
|
||||
var filtersList = JsonSerializer.Deserialize<GameFiltersList>(decodedFilter);
|
||||
|
||||
// filterSets wont be null. It must at least have ChatFilter and LangFilter
|
||||
// Vanilla game only builds one filterSet and InnerSloth officials only handles first one (though you can send multiple filter sets. sloths only handle the first)
|
||||
if (filtersList == null || filtersList.FilterSets.Count != 1
|
||||
|| filtersList.FilterSets[0].Filters.Count < 2
|
||||
|| !filtersList.FilterSets[0].Filters.Any(x => x.OptionType == "languages")
|
||||
|| !filtersList.FilterSets[0].Filters.Any(x => x.OptionType == "chat"))
|
||||
{
|
||||
allGamesCount = _gameManager.Games.Count(),
|
||||
matchingGamesCount = listings.Count,
|
||||
},
|
||||
});
|
||||
return BadRequest(new MatchmakerResponse(new MatchmakerError(DisconnectReason.ServerError, "Invalid filterSets")));
|
||||
}
|
||||
|
||||
var filteredGames = _listingManager.FindListingsV2(HttpContext, filtersList);
|
||||
var port = GetDeltaPort();
|
||||
var gameListings = filteredGames.Select(g => GameListing.From(g, port)).ToList();
|
||||
|
||||
var response = new
|
||||
{
|
||||
games = gameListings,
|
||||
metadata = new
|
||||
{
|
||||
allGamesCount = _gameManager.Games.Count(),
|
||||
matchingGamesCount = gameListings.Count,
|
||||
},
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
return BadRequest(new MatchmakerResponse(new MatchmakerError(DisconnectReason.ServerError, "Unable to deserialize filter json" + ex)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new MatchmakerResponse(new MatchmakerError(DisconnectReason.ServerError, "Unknown exception caught in filter" + ex)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -236,10 +271,18 @@ public sealed class GamesController : ControllerBase
|
||||
private class MatchmakerError
|
||||
{
|
||||
[SetsRequiredMembers]
|
||||
public MatchmakerError(DisconnectReason reason) { Reason = reason; }
|
||||
public MatchmakerError(DisconnectReason reason, string message = "")
|
||||
{
|
||||
Reason = reason;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
[JsonPropertyName("Reason")]
|
||||
public required DisconnectReason Reason { get; init; }
|
||||
|
||||
[JsonPropertyName("Message")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public required string Message { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
private class FindGameByCodeResponse
|
||||
|
||||
@@ -4,6 +4,7 @@ using Empostor.Api.Config;
|
||||
using Empostor.Api.Games;
|
||||
using Empostor.Api.Games.Managers;
|
||||
using Empostor.Api.Http;
|
||||
using Empostor.Api.Innersloth.GameFilters;
|
||||
using Empostor.Api.Net.Manager;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -45,6 +46,11 @@ public sealed class ListingManager
|
||||
continue;
|
||||
}
|
||||
|
||||
if (game.ModGuid != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_compatibilityConfig.AllowVersionMixing &&
|
||||
game.Host != null &&
|
||||
_compatibilityManager.CanJoinGame(game.Host.Client.GameVersion, gameVersion) != GameJoinError.None)
|
||||
@@ -89,6 +95,134 @@ public sealed class ListingManager
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IGame> FindListingsV2(HttpContext ctx, GameFiltersList filtersList, int maxListings = 10)
|
||||
{
|
||||
if (filtersList == null || filtersList.FilterSets.Count == 0)
|
||||
{
|
||||
return Enumerable.Empty<IGame>();
|
||||
}
|
||||
|
||||
var result = new List<IGame>();
|
||||
var resultCount = 0;
|
||||
|
||||
// Apply custom filters from API
|
||||
var filters = _listingFilters.Select(f => f.GetFilter(ctx)).ToArray();
|
||||
|
||||
foreach (var game in _gameManager.Games)
|
||||
{
|
||||
if (!game.IsPublic || game.GameState != GameStates.NotStarted || game.PlayerCount >= game.Options.MaxPlayers)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!filters.All(filter => filter(game)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var matchesAnyFilterSet = false;
|
||||
foreach (var filterSet in filtersList.FilterSets)
|
||||
{
|
||||
// Not sure how to handle normal fools and seek fools
|
||||
if (game.Options.GameMode != filterSet.GameMode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (game.ModGuid != null && !filterSet.Filters.Any(f => f.OptionType == "mod"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var matchesAllFilters = true;
|
||||
|
||||
// Hard coded to only check map, languages, impostornum, chatmode, tags
|
||||
foreach (var filter in filterSet.Filters)
|
||||
{
|
||||
switch (filter.OptionType)
|
||||
{
|
||||
case "map":
|
||||
if (filter.SubFilter is MapGameFilter mapFilter && ((1 << (int)game.Options.Map) & mapFilter.AcceptedValues) == 0)
|
||||
{
|
||||
matchesAllFilters = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case "languages":
|
||||
if (filter.SubFilter is LanguageFilter langFilter && game.Options.Keywords != (GameKeywords)langFilter.AcceptedValues)
|
||||
{
|
||||
matchesAllFilters = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case "int":
|
||||
if (filter.SubFilter is IntGameFilter intFilter)
|
||||
{
|
||||
switch (intFilter.OptionEnum)
|
||||
{
|
||||
case Int32OptionNames.NumImpostors:
|
||||
if (!intFilter.AcceptedValues.Contains(game.Options.NumImpostors))
|
||||
{
|
||||
matchesAllFilters = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case Int32OptionNames.Tag:
|
||||
if (!intFilter.AcceptedValues.Contains((int)game.Options.Tag))
|
||||
{
|
||||
matchesAllFilters = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case "chat":
|
||||
if (filter.SubFilter is ChatModeGameFilter chatFilter && game.Host != null)
|
||||
{
|
||||
if (((byte)game.Host.Client.ChatMode & chatFilter.AcceptedValues) == 0)
|
||||
{
|
||||
matchesAllFilters = false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case "mod":
|
||||
if (filter.SubFilter is ModFilter modFilter && game.ModGuid != modFilter.AcceptedValues)
|
||||
{
|
||||
matchesAllFilters = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!matchesAllFilters)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchesAllFilters)
|
||||
{
|
||||
matchesAnyFilterSet = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchesAnyFilterSet)
|
||||
{
|
||||
result.Add(game);
|
||||
if (++resultCount >= maxListings)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool IsGameDesired(IGame game, int map, int impostorCount, GameKeywords language)
|
||||
{
|
||||
if ((map & (1 << (int)game.Options.Map)) == 0)
|
||||
|
||||
@@ -5,6 +5,7 @@ using Empostor.Api;
|
||||
using Empostor.Api.Config;
|
||||
using Empostor.Api.Events.Managers;
|
||||
using Empostor.Api.Games;
|
||||
using Empostor.Api.Innersloth.GameOptions;
|
||||
using Empostor.Api.Net;
|
||||
using Empostor.Api.Net.Custom;
|
||||
using Empostor.Api.Net.Messages;
|
||||
@@ -48,6 +49,11 @@ namespace Empostor.Server.Net
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Player != null && Player.Game.ModGuid != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Player != null && Player.IsHost)
|
||||
{
|
||||
var isHostCheatingAllowed = _antiCheatConfig.AllowCheatingHosts switch
|
||||
@@ -125,12 +131,25 @@ namespace Empostor.Server.Net
|
||||
switch (flag)
|
||||
{
|
||||
case MessageFlags.HostGame:
|
||||
case MessageFlags.HostModdedGame:
|
||||
{
|
||||
// Read game settings.
|
||||
Message00HostGameC2S.Deserialize(reader, out var gameOptions, out _, out var gameFilterOptions);
|
||||
IGameOptions gameOptions;
|
||||
GameFilterOptions gameFilterOptions;
|
||||
Guid? modGuid = null;
|
||||
|
||||
if (flag == MessageFlags.HostModdedGame)
|
||||
{
|
||||
Message25HostModdedGameC2S.Deserialize(reader, out gameOptions, out _, out gameFilterOptions, out var parsedModGuid);
|
||||
modGuid = parsedModGuid;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read game settings.
|
||||
Message00HostGameC2S.Deserialize(reader, out gameOptions, out _, out gameFilterOptions);
|
||||
}
|
||||
|
||||
// Create game.
|
||||
var game = await _gameManager.CreateAsync(this, gameOptions, gameFilterOptions);
|
||||
var game = await _gameManager.CreateAsync(this, gameOptions, gameFilterOptions, modGuid);
|
||||
|
||||
if (game == null)
|
||||
{
|
||||
@@ -138,6 +157,11 @@ namespace Empostor.Server.Net
|
||||
return;
|
||||
}
|
||||
|
||||
if (modGuid != null)
|
||||
{
|
||||
_logger.LogInformation("Client {Name} ({Id}) hosted a modded game with mod GUID {ModGuid}.", Name, Id, modGuid);
|
||||
}
|
||||
|
||||
// Code in the packet below will be used in JoinGame.
|
||||
using (var writer = MessageWriter.Get(MessageType.Reliable))
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Empostor.Api.Net.Inner.Objects;
|
||||
|
||||
@@ -9,5 +9,7 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
IReadOnlyCollection<IInnerMeetingHud.IPlayerVoteArea> IInnerMeetingHud.PlayerStates => Array.AsReadOnly(_playerStates);
|
||||
|
||||
IInnerPlayerInfo? IInnerMeetingHud.Reporter => Reporter;
|
||||
|
||||
IReadOnlyCollection<IInnerMeetingHud.IJudgeOverrule> IInnerMeetingHud.JudgeOverrules => JudgeOverrules;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Empostor.Api.Net.Inner.Objects;
|
||||
|
||||
namespace Empostor.Server.Net.Inner.Objects
|
||||
{
|
||||
internal partial class InnerMeetingHud
|
||||
{
|
||||
public sealed class JudgeOverrule : IInnerMeetingHud.IJudgeOverrule
|
||||
{
|
||||
public JudgeOverrule(byte judgePlayerId, byte overruledPlayerId, ushort overruleNonce)
|
||||
{
|
||||
JudgePlayerId = judgePlayerId;
|
||||
OverruledPlayerId = overruledPlayerId;
|
||||
OverruleNonce = overruleNonce;
|
||||
}
|
||||
|
||||
public byte JudgePlayerId { get; set; }
|
||||
|
||||
public byte OverruledPlayerId { get; set; }
|
||||
|
||||
public ushort OverruleNonce { get; set; }
|
||||
|
||||
public static JudgeOverrule Deserialize(IMessageReader reader)
|
||||
{
|
||||
var inner = reader.ReadMessage();
|
||||
return new JudgeOverrule(inner.Tag, inner.ReadByte(), inner.ReadUInt16());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using Empostor.Api.Innersloth.GameOptions;
|
||||
using Empostor.Api.Net;
|
||||
using Empostor.Api.Net.Custom;
|
||||
using Empostor.Api.Net.Inner;
|
||||
using Empostor.Api.Net.Inner.Objects;
|
||||
using Empostor.Api.Net.Messages.Rpcs;
|
||||
using Empostor.Server.Events.Meeting;
|
||||
using Empostor.Server.Events.Player;
|
||||
@@ -21,10 +22,12 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
{
|
||||
internal partial class InnerMeetingHud : InnerNetObject
|
||||
{
|
||||
private static readonly GameVersion JudgeMinVersion = new GameVersion(2026, 7, 15); // 18.0
|
||||
|
||||
private readonly ILogger<InnerMeetingHud> _logger;
|
||||
private readonly IEventManager _eventManager;
|
||||
|
||||
private readonly CancellationTokenSource _timerToken;
|
||||
private readonly List<JudgeOverrule> _judgeOverrules = new();
|
||||
|
||||
[AllowNull]
|
||||
private PlayerVoteArea[] _playerStates;
|
||||
@@ -64,6 +67,8 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
|
||||
public InnerPlayerInfo? Reporter { get; private set; }
|
||||
|
||||
public IReadOnlyCollection<IInnerMeetingHud.IJudgeOverrule> JudgeOverrules => _judgeOverrules;
|
||||
|
||||
public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@@ -107,6 +112,19 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sender.Client.GameVersion >= JudgeMinVersion)
|
||||
{
|
||||
var overruleQueueListLength = reader.ReadPackedInt32();
|
||||
if (overruleQueueListLength > 0)
|
||||
{
|
||||
_judgeOverrules.Clear();
|
||||
for (var i = 0; i < overruleQueueListLength; i++)
|
||||
{
|
||||
_judgeOverrules.Add(JudgeOverrule.Deserialize(reader));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
|
||||
@@ -121,6 +139,7 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
}
|
||||
|
||||
Rpc22Close.Deserialize(reader);
|
||||
_judgeOverrules.Clear();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -131,10 +150,23 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
return false;
|
||||
}
|
||||
|
||||
Rpc23VotingComplete.Deserialize(reader, out var states, out var playerId, out var tie);
|
||||
foreach (var messageReader in states)
|
||||
if (sender.Client.GameVersion >= JudgeMinVersion)
|
||||
{
|
||||
messageReader.Dispose();
|
||||
Rpc23VotingComplete.Deserialize(reader, out var states, out var playerId, out var tie, out var wasOverruled, out var overrideId);
|
||||
|
||||
foreach (var messageReader in states)
|
||||
{
|
||||
messageReader.Dispose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Rpc23VotingComplete.Deserialize(reader, out var states, out var playerId, out var tie);
|
||||
|
||||
foreach (var messageReader in states)
|
||||
{
|
||||
messageReader.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// This would be a nice place to implement an anti cheat.
|
||||
@@ -160,6 +192,12 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
break;
|
||||
}
|
||||
|
||||
case RpcCalls.QueueOverruleVotes:
|
||||
{
|
||||
Rpc66QueueOverruleVotes.Deserialize(reader, out var judgePlayerId, out var targetPlayerId, out var overruleNonce);
|
||||
return await HandleQueueOverruleVotesAsync(sender, target, judgePlayerId, targetPlayerId, overruleNonce);
|
||||
}
|
||||
|
||||
default:
|
||||
return await base.HandleRpcAsync(sender, target, call, reader);
|
||||
}
|
||||
@@ -220,6 +258,87 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
return true;
|
||||
}
|
||||
|
||||
private async ValueTask<bool> HandleQueueOverruleVotesAsync(ClientPlayer sender, ClientPlayer? target, byte judgePlayerId, byte targetPlayerId, ushort overruleNonce)
|
||||
{
|
||||
if (!await ValidateRole(RpcCalls.QueueOverruleVotes, sender, sender.Character?.PlayerInfo, RoleTypes.Judge) ||
|
||||
!await ValidateTarget(RpcCalls.QueueOverruleVotes, sender, target) ||
|
||||
!await ValidateHost(RpcCalls.QueueOverruleVotes, target!))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// A Judge can only queue an overrule for themself.
|
||||
if (judgePlayerId != sender.Character!.PlayerId)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.QueueOverruleVotes, CheatCategory.Ownership, "Client sent a Judge overrule for another player"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The client only generates nonces in the 1..65535 range.
|
||||
if (overruleNonce == 0)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.QueueOverruleVotes, CheatCategory.GameFlow, "Client sent a Judge overrule with an uninitialized nonce"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// A Judge can only overrule once per meeting.
|
||||
if (_judgeOverrules.Any(overrule => overrule.JudgePlayerId == judgePlayerId))
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.QueueOverruleVotes, CheatCategory.GameFlow, "Client sent more than one Judge overrule in the same meeting"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The overruled player must exist.
|
||||
if (Game.GameNet.GameData!.GetPlayerById(targetPlayerId) == null)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(RpcCalls.QueueOverruleVotes, CheatCategory.InvalidObject, "Client sent a Judge overrule for an unknown player"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AddOrUpdateJudgeOverrule(judgePlayerId, targetPlayerId, overruleNonce);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AddOrUpdateJudgeOverrule(byte judgePlayerId, byte targetPlayerId, ushort overruleNonce)
|
||||
{
|
||||
var existing = _judgeOverrules.Find(overrule => overrule.JudgePlayerId == judgePlayerId);
|
||||
if (existing != null)
|
||||
{
|
||||
existing.OverruledPlayerId = targetPlayerId;
|
||||
existing.OverruleNonce = overruleNonce;
|
||||
}
|
||||
else
|
||||
{
|
||||
_judgeOverrules.Add(new JudgeOverrule(judgePlayerId, targetPlayerId, overruleNonce));
|
||||
}
|
||||
}
|
||||
|
||||
private JudgeOverrule? GetWinningOverrule()
|
||||
{
|
||||
var gameData = Game.GameNet.GameData!;
|
||||
foreach (var overrule in _judgeOverrules)
|
||||
{
|
||||
var judge = gameData.GetPlayerById(overrule.JudgePlayerId);
|
||||
var target = gameData.GetPlayerById(overrule.OverruledPlayerId);
|
||||
|
||||
if (judge != null && target != null && !judge.Disconnected && !target.Disconnected)
|
||||
{
|
||||
return overrule;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async ValueTask CheckForEndVotingAsync()
|
||||
{
|
||||
if (_playerStates.All(ps => ps.IsDead || ps.DidVote))
|
||||
@@ -286,13 +405,35 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
var max = MaxPair(self, out var tie);
|
||||
var exiled = tie ? null : Game.GameNet.GameData!.GetPlayerById(max.Key)?.Controller;
|
||||
|
||||
// exile or others?
|
||||
var wasOverruled = false;
|
||||
ushort overrideId = 0;
|
||||
|
||||
var winningOverrule = GetWinningOverrule();
|
||||
if (winningOverrule != null)
|
||||
{
|
||||
wasOverruled = true;
|
||||
overrideId = winningOverrule.OverruleNonce;
|
||||
|
||||
var overruled = Game.GameNet.GameData!.GetPlayerById(winningOverrule.OverruledPlayerId);
|
||||
if (overruled != null && overruled.IsImpostor)
|
||||
{
|
||||
exiled = overruled.Controller;
|
||||
}
|
||||
else
|
||||
{
|
||||
var judge = Game.GameNet.GameData!.GetPlayerById(winningOverrule.JudgePlayerId);
|
||||
exiled = judge?.Controller;
|
||||
}
|
||||
}
|
||||
|
||||
if (exiled != null && exiled.PlayerInfo != null)
|
||||
{
|
||||
exiled.PlayerInfo.LastDeathReason = DeathReason.Exile;
|
||||
await _eventManager.CallAsync(new PlayerExileEvent(Game, Game.GetClientPlayer(exiled!.OwnerId)!, exiled));
|
||||
}
|
||||
|
||||
await _eventManager.CallAsync(new MeetingEndedEvent(Game, this, exiled, tie));
|
||||
await _eventManager.CallAsync(new MeetingEndedEvent(Game, this, exiled, tie, wasOverruled, overrideId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,9 +51,9 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
|
||||
public bool Disconnected { get; internal set; }
|
||||
|
||||
public bool IsImpostor => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.ImpostorGhost or RoleTypes.Phantom;
|
||||
public bool IsImpostor => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.ImpostorGhost or RoleTypes.Phantom or RoleTypes.Viper;
|
||||
|
||||
public bool CanVent => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom or RoleTypes.Engineer;
|
||||
public bool CanVent => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom or RoleTypes.Viper or RoleTypes.Engineer;
|
||||
|
||||
public bool IsDead { get; internal set; }
|
||||
|
||||
|
||||
@@ -44,6 +44,11 @@ internal class CompatibilityManager : ICompatibilityManager
|
||||
new GameVersion(2026, 3, 18), // 17.4 (2026-06-05, build 7044, pc only)
|
||||
new GameVersion(2026, 4, 23), // 17.4 (2026-06-05, build 7045, mobile only)
|
||||
},
|
||||
new[]
|
||||
{
|
||||
new GameVersion(2026, 7, 15), // 18.0 (2026-08-18, build 7238, pc only)
|
||||
new GameVersion(2026, 7, 16), // 18.0 (2026-08-18, build 7238, mobile only)
|
||||
},
|
||||
};
|
||||
|
||||
private readonly List<CompatibilityGroup> _compatibilityGroups = new();
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace Empostor.Server.Net.Manager
|
||||
await _eventManager.CallAsync(new GameDestroyedEvent(game));
|
||||
}
|
||||
|
||||
public async ValueTask<IGame?> CreateAsync(IClient? owner, IGameOptions options, GameFilterOptions filterOptions)
|
||||
public async ValueTask<IGame?> CreateAsync(IClient? owner, IGameOptions options, GameFilterOptions filterOptions, Guid? modGuid = null)
|
||||
{
|
||||
if (owner != null && !_gamesCreatedBy.TryAdd(owner, null))
|
||||
{
|
||||
@@ -96,10 +96,10 @@ namespace Empostor.Server.Net.Manager
|
||||
return null;
|
||||
}
|
||||
|
||||
var (success, game) = await TryCreateAsync(options, filterOptions, owner, @event.GameCode);
|
||||
var (success, game) = await TryCreateAsync(options, filterOptions, owner, @event.GameCode, modGuid);
|
||||
for (var i = 0; i < 10 && !success; i++)
|
||||
{
|
||||
(success, game) = await TryCreateAsync(options, filterOptions, owner);
|
||||
(success, game) = await TryCreateAsync(options, filterOptions, owner, modGuid: modGuid);
|
||||
}
|
||||
|
||||
if (owner != null)
|
||||
@@ -120,10 +120,12 @@ namespace Empostor.Server.Net.Manager
|
||||
return CreateAsync(null, options, filterOptions);
|
||||
}
|
||||
|
||||
private async ValueTask<(bool Success, Game? Game)> TryCreateAsync(IGameOptions options, GameFilterOptions filterOptions, IClient? owner, GameCode? desiredGameCode = null)
|
||||
private async ValueTask<(bool Success, Game? Game)> TryCreateAsync(IGameOptions options, GameFilterOptions filterOptions, IClient? owner, GameCode? desiredGameCode = null, Guid? modGuid = null)
|
||||
{
|
||||
var gameCode = desiredGameCode ?? _gameCodeFactory.Create();
|
||||
var game = ActivatorUtilities.CreateInstance<Game>(_serviceProvider, _publicIp, gameCode, options, filterOptions);
|
||||
var game = modGuid.HasValue
|
||||
? ActivatorUtilities.CreateInstance<Game>(_serviceProvider, _publicIp, gameCode, options, filterOptions, modGuid.Value)
|
||||
: ActivatorUtilities.CreateInstance<Game>(_serviceProvider, _publicIp, gameCode, options, filterOptions);
|
||||
if (!_games.TryAdd(gameCode, game))
|
||||
{
|
||||
return (false, null);
|
||||
|
||||
@@ -46,7 +46,8 @@ namespace Empostor.Server.Net.State
|
||||
IEventManager eventManager,
|
||||
ICompatibilityManager compatibilityManager,
|
||||
IOptions<CompatibilityConfig> compatibilityConfig,
|
||||
IOptions<TimeoutConfig> timeoutConfig)
|
||||
IOptions<TimeoutConfig> timeoutConfig,
|
||||
Guid? modGuid = null)
|
||||
{
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
@@ -60,6 +61,7 @@ namespace Empostor.Server.Net.State
|
||||
GameNet = new GameNet();
|
||||
Options = options;
|
||||
FilterOptions = filterOptions;
|
||||
ModGuid = modGuid;
|
||||
_clientManager = clientManager;
|
||||
_eventManager = eventManager;
|
||||
_compatibilityManager = compatibilityManager;
|
||||
@@ -96,6 +98,8 @@ namespace Empostor.Server.Net.State
|
||||
|
||||
public bool IsHostAuthoritive => Host != null && Host.Client.GameVersion.HasDisableServerAuthorityFlag;
|
||||
|
||||
public Guid? ModGuid { get; }
|
||||
|
||||
internal GameNet GameNet { get; }
|
||||
|
||||
public bool TryGetPlayer(int id, [MaybeNullWhen(false)] out ClientPlayer player)
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Empostor.Server.Recorder
|
||||
await base.HandleMessageAsync(reader, messageType);
|
||||
|
||||
// Player created a game.
|
||||
if (reader.Tag == MessageFlags.HostGame)
|
||||
if (reader.Tag is MessageFlags.HostGame or MessageFlags.HostModdedGame)
|
||||
{
|
||||
_createdGame = true;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user