Skip to content

Commit ae5ba25

Browse files
authored
Merge pull request #2 from UncomplicatedCustomServer/Pre-UCT
Pre-UCT -> Main
2 parents 1e8e764 + 03efd3a commit ae5ba25

27 files changed

+2037
-277
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace UncomplicatedCustomTeams.API.Enums
8+
{
9+
public enum RolePriority
10+
{
11+
First,
12+
Second,
13+
Third,
14+
Fourth,
15+
Fifth
16+
}
17+
}

UncomplicatedCustomTeams/API/Features/CustomRole.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.ComponentModel;
2+
using UncomplicatedCustomTeams.API.Enums;
23

34
namespace UncomplicatedCustomTeams.API.Features
45
{
@@ -9,5 +10,12 @@ public class CustomRole : UncomplicatedCustomRoles.API.Features.CustomRole
910
/// </summary>
1011
[Description("The maximum number of players that can have this role in this wave")]
1112
public int MaxPlayers { get; set; }
13+
14+
/// <summary>
15+
/// The priority of assigning this role in the wave (First -> Fourth).
16+
/// The lower the value, the higher the priority.
17+
/// </summary>
18+
[Description("Priority of assigning custom role in Team (First -> Fourth). The lower the value, the higher the priority.")]
19+
public RolePriority Priority { get; set; } = RolePriority.First;
1220
}
1321
}

UncomplicatedCustomTeams/API/Features/SummonedCustomRole.cs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,45 @@ public void Destroy()
4040
}
4141

4242
#pragma warning disable CS0618 // A class member was marked with the Obsolete attribute -> the [Obsolete()] attribute is only there to avoid users to use this in a wrong way!
43-
public void AddRole(RoleTypeId proposed)
43+
public void AddRole(RoleTypeId? proposed = null)
4444
{
45-
LogManager.Debug($"Changing role to player {Player.Nickname} ({Player.Id}) to {CustomRole.Name} ({CustomRole.Id}) from team {Team.Team.Name}");
45+
RoleTypeId finalRole = proposed ?? RoleTypeId.ChaosConscript;
46+
LogManager.Debug($"Changing role to {finalRole} for player {Player.Nickname} ({Player.Id})");
4647

4748
Player.Role.Set(CustomRole.Role, Exiled.API.Enums.SpawnReason.Respawn, RoleSpawnFlags.None);
4849

49-
if (Team.Team.SpawnPosition == Vector3.zero || Team.Team.SpawnPosition == Vector3.one)
50-
Player.Position = proposed.GetRandomSpawnLocation().Position;
50+
if (Player.Role != CustomRole.Role)
51+
{
52+
LogManager.Debug($"Role assignment failed! Falling back to {finalRole}.");
53+
Player.Role.Set(finalRole, Exiled.API.Enums.SpawnReason.ForceClass, RoleSpawnFlags.AssignInventory);
54+
}
55+
Vector3 spawnPos;
56+
if (Team.Team.SpawnConditions.SpawnPosition != Vector3.zero)
57+
{
58+
spawnPos = Team.Team.SpawnConditions.SpawnPosition;
59+
LogManager.Debug($"Using custom Vector3 spawn position: {spawnPos}");
60+
}
5161
else
52-
Player.Position = Team.Team.SpawnPosition;
62+
{
63+
switch (Team.Team.SpawnConditions.SpawnWave)
64+
{
65+
case "NtfWave":
66+
spawnPos = RoleTypeId.NtfCaptain.GetRandomSpawnLocation().Position;
67+
LogManager.Debug($"Using NTF spawn position for role: {CustomRole.Role}");
68+
break;
5369

70+
case "ChaosWave":
71+
spawnPos = RoleTypeId.ChaosConscript.GetRandomSpawnLocation().Position;
72+
LogManager.Debug($"Using Chaos spawn position for role: {CustomRole.Role}");
73+
break;
74+
75+
default:
76+
spawnPos = finalRole.GetRandomSpawnLocation().Position;
77+
LogManager.Debug($"Using fallback spawn for role: {CustomRole.Role}");
78+
break;
79+
}
80+
}
81+
Player.Position = spawnPos;
5482
Player.SetCustomRoleAttributes(CustomRole);
5583
IsRoleSet = true;
5684
}

UncomplicatedCustomTeams/API/Features/SummonedTeam.cs

Lines changed: 140 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
using Exiled.API.Features;
1+
using Exiled.API.Enums;
2+
using Exiled.API.Features;
23
using PlayerRoles;
34
using System;
45
using System.Collections.Generic;
56
using System.Linq;
7+
using UncomplicatedCustomTeams.Utilities;
8+
using Utils.NonAllocLINQ;
69

710
namespace 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

Comments
 (0)