1- using Exiled . API . Features ;
1+ using Exiled . API . Enums ;
2+ using Exiled . API . Features ;
23using PlayerRoles ;
34using System ;
45using System . Collections . Generic ;
56using System . Linq ;
7+ using UncomplicatedCustomTeams . Utilities ;
8+ using Utils . NonAllocLINQ ;
69
710namespace UncomplicatedCustomTeams . API . Features
811{
@@ -21,6 +24,9 @@ public class SummonedTeam
2124
2225 public long Time { get ; }
2326
27+ /// <summary>
28+ /// Creates a new summoned team and adds it to the list.
29+ /// </summary>
2430 public SummonedTeam ( Team team )
2531 {
2632 Team = team ;
@@ -30,6 +36,9 @@ public SummonedTeam(Team team)
3036 List . Add ( this ) ;
3137 }
3238
39+ /// <summary>
40+ /// Spawns all players assigned to this team.
41+ /// </summary>
3342 public void SpawnAll ( )
3443 {
3544 foreach ( SummonedCustomRole Role in Players )
@@ -42,77 +51,171 @@ public void SpawnAll()
4251
4352 RoleTypeId SpawnType = RoleTypeId . ChaosConscript ;
4453
45- if ( Team . SpawnWave == Exiled . API . Enums . SpawnableFaction . NtfWave )
54+ if ( Team . SpawnConditions ? . SpawnWave == " NtfWave" )
4655 SpawnType = RoleTypeId . NtfPrivate ;
4756
4857 Role . AddRole ( SpawnType ) ;
4958 }
5059 }
5160
61+ /// <summary>
62+ /// Checks if any players assigned to this team are still alive.
63+ /// </summary>
5264 public void CheckPlayers ( )
5365 {
5466 foreach ( SummonedCustomRole Role in Players )
5567 if ( Role . Player . IsAlive )
5668 Players . Remove ( Role ) ;
5769 }
5870
71+ /// <summary>
72+ /// Checks if the given team is a custom team.
73+ /// </summary>
74+ public static bool IsCustomTeam ( PlayerRoles . Team team )
75+ {
76+ return Team . List . Any ( t => t . Name == team . ToString ( ) ) ;
77+ }
78+
79+ /// <summary>
80+ /// Checks if the round should end based on the alive teams and winning conditions.
81+ /// </summary>
82+ public static void CheckRoundEndCondition ( )
83+ {
84+ var aliveTeams = Player . List . Where ( p => p . IsAlive )
85+ . Select ( p => p . Role . Team )
86+ . Where ( t => ! IsCustomTeam ( t ) )
87+ . Distinct ( )
88+ . ToList ( ) ;
89+
90+ var winningTeams = Team . GetWinningTeams ( ) ;
91+ bool hasWinningTeamAlive = aliveTeams . Any ( team => winningTeams . Contains ( team ) ) ;
92+ bool onlyWinningTeamsRemain = aliveTeams . All ( team => winningTeams . Contains ( team ) ) ;
93+ bool hasAliveCustomTeam = SummonedTeam . List . Any ( team => team . HasAlivePlayers ( ) ) ;
94+
95+ if ( hasWinningTeamAlive && onlyWinningTeamsRemain && hasAliveCustomTeam )
96+ {
97+ if ( ! Round . IsLocked )
98+ {
99+ Round . EndRound ( ) ;
100+ }
101+ }
102+ }
103+
104+ /// <summary>
105+ /// Checks if this summoned team has any alive players.
106+ /// </summary>
107+ public bool HasAlivePlayers ( )
108+ {
109+ return Players . Any ( role => role . Player . IsAlive ) ;
110+ }
111+
112+ /// <summary>
113+ /// Destroys this summoned team and removes it from the list.
114+ /// </summary>
59115 public void Destroy ( )
60116 {
61117 foreach ( SummonedCustomRole role in Players ) { role . Destroy ( ) ; }
62118 Players . Clear ( ) ;
63119 List . Remove ( this ) ;
64120 }
65121
122+ /// <summary>
123+ /// Clamps a value between the given minimum and maximum.
124+ /// </summary>
66125 public static float Clamp ( float value , float min , float max )
67126 {
68127 return ( value < min ) ? min : ( value > max ) ? max : value ;
69128 }
70129
130+ public void ForceSpawnPlayer ( Player player , RoleTypeId fallbackRole = RoleTypeId . ClassD )
131+ {
132+ LogManager . Debug ( $ "Force spawning player { player . Nickname } for team { Team . Name } ...") ;
133+
134+ SummonedCustomRole summonedRole = SummonedPlayersGet ( player ) ;
135+
136+ if ( summonedRole != null )
137+ {
138+ LogManager . Debug ( $ "Found custom role for { player . Nickname } : { summonedRole . CustomRole . Name } ") ;
139+ summonedRole . AddRole ( ) ;
140+ }
141+ else
142+ {
143+ LogManager . Debug ( $ "No assigned custom role found for { player . Nickname } , using fallback role: { fallbackRole } ") ;
144+ player . Role . Set ( fallbackRole , SpawnReason . ForceClass , RoleSpawnFlags . AssignInventory ) ;
145+ }
146+ }
147+
148+ /// <summary>
149+ /// Summons a new team and assigns players to available roles.
150+ /// </summary>
71151 public static SummonedTeam Summon ( Team team , IEnumerable < Player > players )
72152 {
153+ if ( team == null )
154+ {
155+ return null ;
156+ }
73157 SummonedTeam SummonedTeam = new ( team ) ;
74158
75159 foreach ( Player Player in players )
76160 {
77- foreach ( CustomRole Role in team . Roles )
161+ foreach ( CustomRole role in team . Roles . OrderBy ( r => r . Priority ) )
78162 {
79- if ( SummonedTeam . SummonedPlayersCount ( Role ) < Role . MaxPlayers )
163+ if ( SummonedTeam . SummonedPlayersCount ( role ) < role . MaxPlayers )
80164 {
81- SummonedTeam . Players . Add ( new ( SummonedTeam , Player , Role ) ) ;
165+ SummonedTeam . Players . Add ( new ( SummonedTeam , Player , role ) ) ;
166+ LogManager . Debug ( $ "{ Player . Nickname } -> { role . Name } (Priority: { role . Priority } )") ;
82167 break ;
83168 }
84169 }
85170 }
86171 if ( ! string . IsNullOrEmpty ( team . CassieTranslation ) )
87172 {
88- Cassie . Message ( team . CassieTranslation , isSubtitles : true , isNoisy : team . IsNoisy , isHeld : false ) ;
173+ Cassie . MessageTranslated ( team . CassieMessage , team . CassieTranslation , isNoisy : team . IsNoisy , isSubtitles : true ) ;
89174 }
90- else if ( ! string . IsNullOrEmpty ( team . CassieMessage ) )
91- {
92- Cassie . Message ( team . CassieMessage , isSubtitles : true , isNoisy : team . IsNoisy , isHeld : false ) ;
93- }
94-
95-
96175 if ( ! string . IsNullOrEmpty ( team . SoundPath ) )
97176 {
98177 AudioPlayer audioPlayer = AudioPlayer . CreateOrGet ( $ "Global_Audio_{ team . Id } ", onIntialCreation : ( p ) =>
99178 {
100179 Speaker speaker = p . AddSpeaker ( "Main" , isSpatial : false , maxDistance : 5000f ) ;
101180 } ) ;
102-
103181 float volume = Clamp ( team . SoundVolume , 1f , 100f ) ;
104-
105182 audioPlayer . AddClip ( $ "sound_{ team . Id } ", volume ) ;
106183 }
107-
108184 return SummonedTeam ;
109185 }
110186
187+ /// <summary>
188+ /// Checks if a team can spawn based on the number of spectators.
189+ /// </summary>
190+ public static List < Player > CanSpawnTeam ( Team team )
191+ {
192+ if ( team == null )
193+ return new List < Player > ( ) ;
194+
195+ List < Player > allPlayers = Player . List . ToList ( ) ;
196+ int totalPlayers = allPlayers . Count ;
197+ LogManager . Debug ( $ "Total players: { totalPlayers } , MinPlayers required: { team . MinPlayers } ") ;
198+ if ( totalPlayers < team . MinPlayers )
199+ {
200+ LogManager . Debug ( $ "Not enough players on the server to spawn team { team . Name } .") ;
201+ return new List < Player > ( ) ;
202+ }
203+ List < Player > spectators = allPlayers
204+ . Where ( p => ! p . IsAlive && p . Role . Type == RoleTypeId . Spectator && ! p . IsOverwatchEnabled )
205+ . ToList ( ) ;
206+ LogManager . Debug ( $ "Spectators available: { spectators . Count } ") ;
207+ LogManager . Debug ( $ "Spawning all { spectators . Count } spectators for team { team . Name } .") ;
208+ return spectators ;
209+ }
210+
211+ /// <summary>
212+ /// Refreshes the players list, ensuring that the maximum allowed players per role is respected.
213+ /// </summary>
111214 public void RefreshPlayers ( IEnumerable < Player > players )
112215 {
113216 foreach ( Player Player in players )
114217 {
115- foreach ( CustomRole Role in Team . Roles )
218+ foreach ( CustomRole Role in Team . Roles . OrderBy ( r => r . Priority ) )
116219 {
117220 if ( SummonedPlayersCount ( Role ) < Role . MaxPlayers )
118221 {
@@ -123,25 +226,46 @@ public void RefreshPlayers(IEnumerable<Player> players)
123226 }
124227 }
125228
229+ /// <summary>
230+ /// Counts the number of players assigned to a specific custom role.
231+ /// </summary>
126232 public int SummonedPlayersCount ( CustomRole role )
127233 {
128234 return Players . Where ( cr => cr . CustomRole == role ) . Count ( ) ;
129235 }
130236
237+ /// <summary>
238+ /// Gets the list of summoned players for a specific custom role.
239+ /// </summary>
131240 public IEnumerable < SummonedCustomRole > SummonedPlayersGet ( CustomRole role ) => Players . Where ( cr => cr . CustomRole == role ) ;
132241
242+ /// <summary>
243+ /// Gets the summoned player role for a specific player.
244+ /// </summary>
133245 public SummonedCustomRole SummonedPlayersGet ( Player player ) => Players . Where ( cr => cr . Player . Id == player . Id ) . FirstOrDefault ( ) ;
134246
247+ /// <summary>
248+ /// Tries to get a summoned player role for a specific player.
249+ /// </summary>
135250 public bool SummonedPlayersTryGet ( Player player , out SummonedCustomRole role )
136251 {
137252 role = SummonedPlayersGet ( player ) ;
138253 return role != null ;
139254 }
140255
256+ /// <summary>
257+ /// Attempts to spawn a player with the given role.
258+ /// </summary>
141259 public void TrySpawnPlayer ( Player player , RoleTypeId role ) => SummonedPlayersGet ( player ) ? . AddRole ( role ) ;
142260
261+ /// <summary>
262+ /// Finds a summoned team by its ID.
263+ /// </summary>
143264 public static SummonedTeam Get ( string Id ) => List . Where ( st => st . Id == Id ) . FirstOrDefault ( ) ;
144265
266+ /// <summary>
267+ /// Tries to find a summoned team by its ID.
268+ /// </summary>
145269 public static bool TryGet ( string Id , out SummonedTeam team )
146270 {
147271 team = Get ( Id ) ;
0 commit comments