From 5507c4d43fc515c979ec42b2f609a42b714c8b10 Mon Sep 17 00:00:00 2001 From: Ahmad <55556ahmad@gmail.com> Date: Mon, 13 Jul 2026 12:15:25 +0300 Subject: [PATCH] feat: implement custom round end conditions and escape-based win triggers for UCR roles --- EndConditionsExtension/Config.cs | 57 ++- .../Elements/CustomTeamEndCondition.cs | 29 ++ .../Elements/EndCondition.cs | 51 ++- .../Elements/EscapeWinCondition.cs | 29 ++ .../EndConditionsExtension.csproj | 424 +++--------------- EndConditionsExtension/Handler.cs | 263 +++++++---- EndConditionsExtension/Plugin.cs | 83 ++-- .../Structures/IEndCondition.cs | 28 +- 8 files changed, 420 insertions(+), 544 deletions(-) create mode 100644 EndConditionsExtension/Elements/CustomTeamEndCondition.cs create mode 100644 EndConditionsExtension/Elements/EscapeWinCondition.cs diff --git a/EndConditionsExtension/Config.cs b/EndConditionsExtension/Config.cs index d96070b..f794954 100644 --- a/EndConditionsExtension/Config.cs +++ b/EndConditionsExtension/Config.cs @@ -1,24 +1,33 @@ -using EndConditionsExtension.Elements; -using Exiled.API.Interfaces; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EndConditionsExtension -{ - internal class Config : IConfig - { - [Description("Is the plugin enabled?")] - public bool IsEnabled { get; set; } - [Description("Do enable the debug (developer) mode?")] - public bool Debug { get; set; } - [Description("A list of conditions for each CustomRole")] - public Dictionary EndConditions { get; set; } = new() - { - { 1, new() } - }; - } -} +using EndConditionsExtension.Elements; +using System.Collections.Generic; +using System.ComponentModel; + +namespace EndConditionsExtension +{ + public class Config + { + [Description("Is the plugin enabled?")] + public bool IsEnabled { get; set; } = true; + + [Description("Enable debug (developer) mode?")] + public bool Debug { get; set; } = false; + + [Description("Traditional per-role-ID end conditions (Key = Role ID)")] + public Dictionary EndConditions { get; set; } = new() + { + { 1, new EndCondition() } + }; + + [Description("Custom-team based end conditions (Key = CustomTeamId name)")] + public Dictionary CustomTeamEndConditions { get; set; } = new() + { + { "SerpentHand", new CustomTeamEndCondition() } + }; + + [Description("Escape-based win conditions (Key = Identifier)")] + public Dictionary EscapeWinConditions { get; set; } = new() + { + { "SerpentHandEscape", new EscapeWinCondition() } + }; + } +} diff --git a/EndConditionsExtension/Elements/CustomTeamEndCondition.cs b/EndConditionsExtension/Elements/CustomTeamEndCondition.cs new file mode 100644 index 0000000..0248bbc --- /dev/null +++ b/EndConditionsExtension/Elements/CustomTeamEndCondition.cs @@ -0,0 +1,29 @@ +using PlayerRoles; +using PlayerRoles.RoleAssign; +using System.Collections.Generic; +using System.ComponentModel; + +namespace EndConditionsExtension.Elements +{ + public class CustomTeamEndCondition + { + [Description("The custom team identifier this applies to (e.g., 'SerpentHand')")] + public string CustomTeamId { get; set; } = "SerpentHand"; + + [Description("List of base teams that are friendly to this custom team")] + public List FriendlyTeams { get; set; } = new() + { + Team.ChaosInsurgency, + Team.SCPs + }; + + [Description("Does this team need to be the only team left alive (along with friendlies)?")] + public bool MustBeLastStanding { get; set; } = true; + + [Description("Maximum number of non-friendly players allowed alive for the round to end (Requires MustBeLastStanding = false)")] + public int MaxEnemyPlayers { get; set; } = 0; + + [Description("Which team wins the round when this condition is met?")] + public LeadingTeam WinningTeam { get; set; } = LeadingTeam.ChaosInsurgency; + } +} diff --git a/EndConditionsExtension/Elements/EndCondition.cs b/EndConditionsExtension/Elements/EndCondition.cs index f22f140..3544402 100644 --- a/EndConditionsExtension/Elements/EndCondition.cs +++ b/EndConditionsExtension/Elements/EndCondition.cs @@ -1,24 +1,27 @@ -using EndConditionsExtension.Structures; -using Exiled.API.Enums; -using PlayerRoles; -using System.Collections.Generic; -using System.ComponentModel; - -namespace EndConditionsExtension.Elements -{ - internal class EndCondition : IEndCondition - { - [Description("Decide if to end the round must remain only the CustomRole's team, no matter the number")] - public bool MustRemainOnlyOneTeam { get; set; } = false; - [Description("If the must_remain_only_one_team bool is false here you can decide which teams are needed to end the round and the maximum number of members that they can have. Leave it empty to allow all roles\n# You don't need to include here the role team")] - public Dictionary RemainingTeams { get; set; } = new() - { - { Team.ClassD, 5 }, - { Team.Scientists, 1 } - }; - [Description("Here you can decide how many people are needed to keep the round going (this will be effective if the first bool is false) and evaluates all of the roles in the remaining_teams dictionary")] - public int MaxPlayersToEnd { get; set; } - [Description("Set the team who will win if this condition will be true")] - public LeadingTeam WinningTeam { get; set; } = LeadingTeam.Draw; - } -} +using EndConditionsExtension.Structures; +using PlayerRoles; +using PlayerRoles.RoleAssign; +using System.Collections.Generic; +using System.ComponentModel; + +namespace EndConditionsExtension.Elements +{ + public class EndCondition : IEndCondition + { + [Description("Decide if to end the round must remain only the CustomRole's team, no matter the number")] + public bool MustRemainOnlyOneTeam { get; set; } = false; + + [Description("If the must_remain_only_one_team bool is false here you can decide which teams are needed to end the round and the maximum number of members that they can have. Leave it empty to allow all roles\n# You don't need to include here the role team")] + public Dictionary RemainingTeams { get; set; } = new() + { + { Team.ClassD, 5 }, + { Team.Scientists, 1 } + }; + + [Description("Here you can decide how many people are needed to keep the round going (this will be effective if the first bool is false) and evaluates all of the roles in the remaining_teams dictionary")] + public int MaxPlayersToEnd { get; set; } + + [Description("Set the team who will win if this condition will be true")] + public LeadingTeam WinningTeam { get; set; } = LeadingTeam.Draw; + } +} diff --git a/EndConditionsExtension/Elements/EscapeWinCondition.cs b/EndConditionsExtension/Elements/EscapeWinCondition.cs new file mode 100644 index 0000000..1e5d371 --- /dev/null +++ b/EndConditionsExtension/Elements/EscapeWinCondition.cs @@ -0,0 +1,29 @@ +using PlayerRoles.RoleAssign; +using System.ComponentModel; + +namespace EndConditionsExtension.Elements +{ + public class EscapeWinCondition + { + [Description("The custom team identifier this applies to (e.g., 'SerpentHand')")] + public string CustomTeamId { get; set; } = "SerpentHand"; + + [Description("How many players of this custom team must escape to trigger the win condition")] + public int RequiredEscapes { get; set; } = 1; + + [Description("Does at least one SCP need to be alive for the escape to count towards the win condition?")] + public bool RequireScpAlive { get; set; } = true; + + [Description("How many SCPs must be alive when the required escapes is reached? (Requires RequireScpAlive = true)")] + public int RequireScpEscortCount { get; set; } = 1; + + [Description("Which team wins the round when this condition is met?")] + public LeadingTeam WinningTeam { get; set; } = LeadingTeam.ChaosInsurgency; + + [Description("How many points to award the winning team (if scoring is enabled)")] + public int AwardPoints { get; set; } = 2; + + [Description("Optional broadcast to send to all players when this condition is met (Leave empty to disable)")] + public string Broadcast { get; set; } = "The Serpent's Hand has escaped with an SCP! They win!"; + } +} diff --git a/EndConditionsExtension/EndConditionsExtension.csproj b/EndConditionsExtension/EndConditionsExtension.csproj index a665d7f..d2ff3cf 100644 --- a/EndConditionsExtension/EndConditionsExtension.csproj +++ b/EndConditionsExtension/EndConditionsExtension.csproj @@ -1,372 +1,54 @@ - - - - - Debug - AnyCPU - {1B70255B-DB3D-45DB-A1B1-F23A05141E0C} - Library - Properties - EndConditionsExtension - EndConditionsExtension - v4.8.1 - 512 - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\x64\Debug\ - DEBUG;TRACE - full - x64 - 11 - prompt - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - 11 - prompt - - - - ..\..\..\..\Downloads\Master\Assembly-CSharp-firstpass.dll - - - ..\..\..\..\Downloads\Master\BouncyCastle.Cryptography.dll - - - ..\..\..\..\Downloads\Master\Caress.dll - - - ..\..\..\..\Downloads\Master\DnsClient.dll - - - ..\..\..\..\Downloads\Master\Facepunch.Steamworks.Win64.dll - - - ..\..\..\..\Downloads\references\references\Mirror.dll - - - ..\..\..\..\Downloads\Master\Mirror.Components.dll - - - ..\..\..\..\Downloads\Master\Mono.Posix.dll - - - ..\..\..\..\Downloads\Master\Mono.Security.dll - - - ..\..\..\..\Downloads\Master\netstandard.dll - - - ..\..\..\..\Downloads\Master\Pooling.dll - - - - - - - - - - - ..\..\..\..\Downloads\UncomplicatedCustomRoles.dll - - - ..\..\..\..\Downloads\Master\Unity.Burst.dll - - - ..\..\..\..\Downloads\Master\Unity.Burst.Unsafe.dll - - - ..\..\..\..\Downloads\Master\Unity.Mathematics.dll - - - ..\..\..\..\Downloads\Master\Unity.ProBuilder.dll - - - ..\..\..\..\Downloads\Master\Unity.ProBuilder.Csg.dll - - - ..\..\..\..\Downloads\Master\Unity.ProBuilder.KdTree.dll - - - ..\..\..\..\Downloads\Master\Unity.ProBuilder.Poly2Tri.dll - - - ..\..\..\..\Downloads\Master\Unity.ProBuilder.Stl.dll - - - ..\..\..\..\Downloads\Master\Unity.RenderPipelines.Core.Runtime.dll - - - ..\..\..\..\Downloads\Master\Unity.RenderPipelines.Core.ShaderLibrary.dll - - - ..\..\..\..\Downloads\Master\Unity.RenderPipelines.HighDefinition.Config.Runtime.dll - - - ..\..\..\..\Downloads\Master\Unity.RenderPipelines.HighDefinition.Runtime.dll - - - ..\..\..\..\Downloads\Master\Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary.dll - - - ..\..\..\..\Downloads\Master\Unity.TextMeshPro.dll - - - ..\..\..\..\Downloads\Master\Unity.Timeline.dll - - - ..\..\..\..\Downloads\Master\Unity.VisualEffectGraph.Runtime.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.AccessibilityModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.AIModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.AndroidJNIModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.AnimationModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.ARModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.AssetBundleModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.AudioModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.ClothModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.ClusterInputModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.ClusterRendererModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.CoreModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.CrashReportingModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.DirectorModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.DSPGraphModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.GameCenterModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.GIModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.GridModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.HotReloadModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.ImageConversionModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.IMGUIModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.InputLegacyModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.InputModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.JSONSerializeModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.LocalizationModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.NVIDIAModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.ParticleSystemModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.PerformanceReportingModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.Physics2DModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.PhysicsModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.ProfilerModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.ScreenCaptureModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.SharedInternalsModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.SpriteMaskModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.SpriteShapeModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.StreamingModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.SubstanceModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.SubsystemsModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.TerrainModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.TerrainPhysicsModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.TextCoreFontEngineModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.TextCoreTextEngineModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.TextRenderingModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.TilemapModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.TLSModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UI.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UIElementsModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UIElementsNativeModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UIModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UmbraModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UNETModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UnityAnalyticsCommonModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UnityAnalyticsModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UnityConnectModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UnityCurlModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UnityTestProtocolModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UnityWebRequestAssetBundleModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UnityWebRequestAudioModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UnityWebRequestModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UnityWebRequestTextureModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.UnityWebRequestWWWModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.VehiclesModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.VFXModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.VideoModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.VirtualTexturingModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.VRModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.WindModule.dll - - - ..\..\..\..\Downloads\Master\UnityEngine.XRModule.dll - - - ..\..\..\..\Downloads\Master\zxing.dll - - - - - - - - - - - - - - - - 8.9.6 - - - + + + + net48 + EndConditionsExtension + EndConditionsExtension + true + latest + true + x64 + disable + false + + + + true + embedded + DEBUG;TRACE + false + bin\x64\Debug\ + + + + false + pdbonly + TRACE + true + bin\x64\Release\ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EndConditionsExtension/Handler.cs b/EndConditionsExtension/Handler.cs index 3ee8078..89ceef2 100644 --- a/EndConditionsExtension/Handler.cs +++ b/EndConditionsExtension/Handler.cs @@ -1,76 +1,187 @@ -using EndConditionsExtension.Structures; -using Exiled.API.Enums; -using Exiled.API.Features; -using Exiled.Events.EventArgs.Server; -using PlayerRoles; -using System.Collections.Generic; -using System.Linq; -using UncomplicatedCustomRoles.API.Features; -using UncomplicatedCustomRoles.Interfaces; - -namespace EndConditionsExtension -{ - internal class Handler - { - protected LeadingTeam? Leading { get; set; } = LeadingTeam.Draw; - - public void OnEnding(EndingRoundEventArgs Ending) - { - bool CanEnd = true; - foreach (KeyValuePair Element in CustomRole.Alive) - CanEnd = CanEnd && EvaluateEndConditions(Player.Get(Element.Key), CustomRole.Get(Element.Value)); - - if (Leading is not null) - Ending.LeadingTeam = (LeadingTeam)Leading; - - Ending.IsAllowed = CanEnd; - } - public bool EvaluateEndConditions(Player Player, ICustomRole Role) - { - if (Plugin.Istance.Config.EndConditions.ContainsKey(Role.Id)) - { - IEndCondition EndCondition = Plugin.Istance.Config.EndConditions[Role.Id]; - if (EndCondition.MustRemainOnlyOneTeam) - { - if (Player.List.Count == Player.List.Where(player => player.Role.Team == Player.Role.Team && player.IsAlive).Count()) - { - Leading = EndCondition.WinningTeam; - return true; - } - } - else - { - List AliveTeams = new(); - foreach (Player PseudoPlayer in Player.List.Where(player => player.IsAlive)) - { - if (!AliveTeams.Contains(PseudoPlayer.Role.Team)) - { - AliveTeams.Add(PseudoPlayer.Role.Team); - } - } - if (AliveTeams == EndCondition.RemainingTeams.Keys.ToList()) - { - bool Can = true; - int Total = 0; - - // Let's count the values - foreach (Team Team in AliveTeams) - { - Total += Player.List.Where(player => player.IsAlive && player.Role.Team == Team).Count(); - Can = Can && Player.List.Where(player => player.IsAlive && player.Role.Team == Team).Count() <= EndCondition.RemainingTeams[Team]; - } - - Can = Can && Total <= EndCondition.MaxPlayersToEnd; - - Leading = EndCondition.WinningTeam; - return Can; - } - } - } - else - return true; - - return false; - } - } -} +using EndConditionsExtension.Elements; +using LabApi.Events.Arguments.ServerEvents; +using LabApi.Features.Wrappers; +using PlayerRoles; +using PlayerRoles.RoleAssign; +using System.Collections.Generic; +using System.Linq; +using UncomplicatedCustomRoles.API.Features; + +namespace EndConditionsExtension +{ + internal class Handler + { + // Tracks how many escapes have occurred per CustomTeamId in the current round + private Dictionary _escapeCounts = new(); + + public void OnWaitingForPlayers() + { + // Reset state for the new round + _escapeCounts.Clear(); + } + + public void OnCustomRoleEscaped(object sender, UcrEvents.CustomRoleEscapeEventData ev) + { + if (string.IsNullOrEmpty(ev.CustomTeamId)) + return; + + if (!_escapeCounts.ContainsKey(ev.CustomTeamId)) + _escapeCounts[ev.CustomTeamId] = 0; + + _escapeCounts[ev.CustomTeamId]++; + + // Check if this escape triggers an escape-based win condition + EvaluateEscapeWinConditions(); + } + + private void EvaluateEscapeWinConditions() + { + if (Plugin.Instance.Config.EscapeWinConditions == null) + return; + + foreach (var kvp in Plugin.Instance.Config.EscapeWinConditions) + { + var condition = kvp.Value; + if (!_escapeCounts.TryGetValue(condition.CustomTeamId, out int escapes) || escapes < condition.RequiredEscapes) + continue; // Not enough escapes + + if (condition.RequireScpAlive) + { + int scpCount = Player.GetPlayers().Count(p => p.Role.Team == Team.SCPs); + if (scpCount < condition.RequireScpEscortCount) + continue; // Not enough SCPs alive + } + + // Condition met! End the round. + LabApi.Features.Server.Logger.Info($"Escape win condition '{kvp.Key}' met for team '{condition.CustomTeamId}'. Ending round."); + + if (!string.IsNullOrEmpty(condition.Broadcast)) + { + foreach (var player in Player.GetPlayers()) + player.SendBroadcast(condition.Broadcast, 10); + } + + // Note: LabAPI doesn't have a direct "Force End Round with specific winner" method in this API version + // without reflection or using the RoundSummary object directly. + // The easiest way to force a round end is to use RoundSummary.singleton.ForceEnd. + // For now, we will trigger the end via RoundSummary. + RoundSummary.singleton.ForceEnd = new RoundSummary.ForceEndArgs(condition.WinningTeam, true); + return; + } + } + + public void OnRoundEnding(RoundEndingEventArgs ev) + { + if (EvaluatePerRoleEndConditions(ev) || EvaluateCustomTeamEndConditions(ev)) + { + // We modified the round ending args or handled it + return; + } + } + + private bool EvaluatePerRoleEndConditions(RoundEndingEventArgs ev) + { + if (Plugin.Instance.Config.EndConditions == null) + return false; + + var allPlayers = Player.GetPlayers(); + + foreach (var kvp in Plugin.Instance.Config.EndConditions) + { + int roleId = kvp.Key; + EndCondition condition = kvp.Value; + + // Check if any player has this custom role + var customPlayers = SummonedCustomRole.List.Values.Where(r => r.Role.Id == roleId).ToList(); + if (customPlayers.Count == 0) + continue; // No players with this role, skip + + var aliveTeams = GetAliveTeams(allPlayers); + + if (condition.MustRemainOnlyOneTeam) + { + // For "Only One Team", we check if the ONLY teams alive are the custom role's base team + // Note: In original code, it just ended the round if true. We'll refine it: + if (aliveTeams.Count == 1) // Only one team left + { + ev.IsAllowed = true; + ev.LeadingTeam = condition.WinningTeam; + return true; + } + } + else + { + // Check if the current alive teams match the condition's allowed teams + if (aliveTeams.All(t => condition.RemainingTeams.ContainsKey(t))) + { + int maxAllowed = condition.MaxPlayersToEnd; + int currentCount = allPlayers.Count(p => p.IsAlive && condition.RemainingTeams.ContainsKey(p.Role.Team)); + + if (currentCount <= maxAllowed) + { + ev.IsAllowed = true; + ev.LeadingTeam = condition.WinningTeam; + return true; + } + } + } + } + + return false; + } + + private bool EvaluateCustomTeamEndConditions(RoundEndingEventArgs ev) + { + if (Plugin.Instance.Config.CustomTeamEndConditions == null) + return false; + + var allPlayers = Player.GetPlayers(); + + foreach (var kvp in Plugin.Instance.Config.CustomTeamEndConditions) + { + string customTeamId = kvp.Key; + CustomTeamEndCondition condition = kvp.Value; + + // Find players in this custom team + var teamPlayers = SummonedCustomRole.List.Values + .Where(r => r.Role.CustomTeamId == customTeamId) + .Select(r => r.Player) + .ToList(); + + if (teamPlayers.Count == 0) + continue; // No players in this custom team + + // Count enemy players (alive players NOT in this custom team AND NOT in friendly teams) + int enemyCount = allPlayers.Count(p => + p.IsAlive && + !teamPlayers.Contains(p) && + !condition.FriendlyTeams.Contains(p.Role.Team) + ); + + if (condition.MustBeLastStanding && enemyCount == 0) + { + ev.IsAllowed = true; + ev.LeadingTeam = condition.WinningTeam; + return true; + } + else if (!condition.MustBeLastStanding && enemyCount <= condition.MaxEnemyPlayers) + { + ev.IsAllowed = true; + ev.LeadingTeam = condition.WinningTeam; + return true; + } + } + + return false; + } + + private List GetAliveTeams(List players) + { + return players + .Where(p => p.IsAlive) + .Select(p => p.Role.Team) + .Distinct() + .ToList(); + } + } +} diff --git a/EndConditionsExtension/Plugin.cs b/EndConditionsExtension/Plugin.cs index 728042d..59bb940 100644 --- a/EndConditionsExtension/Plugin.cs +++ b/EndConditionsExtension/Plugin.cs @@ -1,35 +1,48 @@ -using Exiled.API.Features; -using System; -using ServerHandler = Exiled.Events.Handlers.Server; - -namespace EndConditionsExtension -{ - internal class Plugin : Plugin - { - public override string Name => "EndConditionsExtension"; - public override string Author => "FoxWorn3365"; - public override string Prefix => "EndConditionsExtension"; - public override Version Version => new(0, 9, 0); - public override Version RequiredExiledVersion => new(8, 8, 0); - public static Plugin Istance; - internal Handler Handler; - public override void OnEnabled() - { - Istance = this; - Handler = new(); - - ServerHandler.EndingRound += Handler.OnEnding; - - base.OnEnabled(); - } - public override void OnDisabled() - { - ServerHandler.EndingRound -= Handler.OnEnding; - - Istance = null; - Handler = null; - - base.OnDisabled(); - } - } -} +using LabApi.Loader.Features.Plugins; +using LabApi.Loader.Features.Plugins.Enums; +using System; +using UncomplicatedCustomRoles.API.Features; +using ServerEvents = LabApi.Events.Handlers.ServerEvents; +using PlayerEvents = LabApi.Events.Handlers.PlayerEvents; + +namespace EndConditionsExtension +{ + public class Plugin : Plugin + { + public override string Name => "EndConditionsExtension"; + public override string Author => "FoxWorn3365, Refactored"; + public override string Description => "Extension for UncomplicatedCustomRoles to add custom end conditions."; + public override Version Version => new(1, 0, 0); + public override Version RequiredApiVersion => new(1, 1, 7); + public override LoadPriority Priority => LoadPriority.Medium; + + public static Plugin Instance; + internal Handler Handler; + + public override void Enable() + { + Instance = this; + Handler = new(); + + // UCR Event Registration + UcrEvents.CustomRoleEscaped += Handler.OnCustomRoleEscaped; + + // LabAPI Event Registration + ServerEvents.RoundEnding += Handler.OnRoundEnding; + ServerEvents.WaitingForPlayers += Handler.OnWaitingForPlayers; + } + + public override void Disable() + { + // LabAPI Event Unregistration + ServerEvents.RoundEnding -= Handler.OnRoundEnding; + ServerEvents.WaitingForPlayers -= Handler.OnWaitingForPlayers; + + // UCR Event Unregistration + UcrEvents.CustomRoleEscaped -= Handler.OnCustomRoleEscaped; + + Instance = null; + Handler = null; + } + } +} diff --git a/EndConditionsExtension/Structures/IEndCondition.cs b/EndConditionsExtension/Structures/IEndCondition.cs index e84db65..30107f0 100644 --- a/EndConditionsExtension/Structures/IEndCondition.cs +++ b/EndConditionsExtension/Structures/IEndCondition.cs @@ -1,14 +1,14 @@ -using Exiled.API.Enums; -using PlayerRoles; -using System.Collections.Generic; - -namespace EndConditionsExtension.Structures -{ - internal interface IEndCondition - { - public abstract bool MustRemainOnlyOneTeam { get; set; } - public abstract Dictionary RemainingTeams { get; set; } - public abstract int MaxPlayersToEnd { get; set; } - public abstract LeadingTeam WinningTeam { get; set; } - } -} +using PlayerRoles; +using PlayerRoles.RoleAssign; +using System.Collections.Generic; + +namespace EndConditionsExtension.Structures +{ + public interface IEndCondition + { + bool MustRemainOnlyOneTeam { get; set; } + Dictionary RemainingTeams { get; set; } + int MaxPlayersToEnd { get; set; } + LeadingTeam WinningTeam { get; set; } + } +}