Skip to content

Commit 452560c

Browse files
committed
Last minute changes.
1 parent 734eea9 commit 452560c

File tree

7 files changed

+255
-151
lines changed

7 files changed

+255
-151
lines changed

UncomplicatedCustomTeams/API/Features/SummonedTeam.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ public static SummonedTeam Summon(Team team, IEnumerable<Player> players)
201201
if (!string.IsNullOrEmpty(team.CassieTranslation))
202202
{
203203
if (team.IsCassieAnnouncementEnabled)
204-
Cassie.MessageTranslated(team.CassieMessage, team.CassieTranslation, isNoisy: team.IsNoisy, isSubtitles: true);
204+
Exiled.API.Features.Cassie.MessageTranslated(team.CassieMessage, team.CassieTranslation, isNoisy: team.IsNoisy, isSubtitles: true);
205205
}
206206
bool hasCustomSound = team.SoundPaths != null && team.SoundPaths.Any(s => !string.IsNullOrEmpty(s.Path) && s.Path != "/path/to/your/ogg/file");
207207
if (hasCustomSound)

UncomplicatedCustomTeams/API/Features/Team.cs

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using PlayerRoles;
1+
using Exiled.API.Enums;
2+
using PlayerRoles;
23
using System;
34
using System.Collections.Generic;
45
using System.ComponentModel;
@@ -66,6 +67,13 @@ public static void Register(Team team)
6667
/// </summary>
6768
public uint SpawnChance { get; set; } = 100;
6869

70+
/// <summary>
71+
/// If set to true, this team's successful spawn roll will not prevent other teams
72+
/// with the same SpawnWave from being evaluated.
73+
/// </summary>
74+
[Description("Set to true to allow this team to spawn alongside other teams during the same Spawn Wave. If false (default), it will be the only one.")]
75+
public bool AllowConcurrentSpawns { get; set; } = false;
76+
6977
/// <summary>
7078
/// Defines the spawn conditions for a custom team.
7179
/// </summary>
@@ -91,39 +99,25 @@ public static void Register(Team team)
9199
/// </summary>
92100
public bool IsNoisy { get; set; } = true;
93101

102+
public RoundEndRule WinCondition { get; set; } = new RoundEndRule();
103+
94104
/// <summary>
95105
/// A list of sounds to be played sequentially when the team spawns.
96-
/// Requires AudioPlayerAPI. Download it here: https://github.com/Killers0992/AudioPlayerApi
106+
/// Requires AudioPlayerAPI.
97107
/// </summary>
98-
[Description("A list of sounds to be played sequentially. Requires AudioPlayerAPI.")]
108+
[Description("A list of sounds to be played sequentially. Requires AudioPlayerAPI. Download it here: https://github.com/Killers0992/AudioPlayerApi")]
99109
public List<SoundPathEntry> SoundPaths { get; set; } = [new()];
100110

101111
/// <summary>
102-
/// Volume of the sound, should be between 1 and 100.
112+
/// Volume of the sound, should be between 1 and 5.
103113
/// </summary>
104114
public float SoundVolume { get; set; } = 1f;
105115

106-
/// <summary>
107-
/// A list of PlayerRoles.Team whose presence on the map guarantees victory with custom team.
108-
/// </summary>
109-
[Description("Here, you can define which teams will win against your custom team.")]
110-
public List<PlayerRoles.Team> TeamAliveToWin { get; set; } = [];
111-
112-
/// <summary>
113-
/// Retrieves a list of actual PlayerRoles.Team enums based on the teams in TeamAliveToWin.
114-
/// </summary>
115-
public static List<PlayerRoles.Team> GetWinningTeams()
116-
{
117-
return [.. Team.List
118-
.SelectMany(team => team.TeamAliveToWin)
119-
.Distinct()];
120-
}
121-
122116
/// <summary>
123117
/// The list of every role that will be a part of this wave
124118
/// </summary>
125119
[YamlIgnore]
126-
public List<IUCTCustomRole> TeamRoles => Roles.OfType<IUCTCustomRole>().Concat(EcrRoles).ToList();
120+
public List<IUCTCustomRole> TeamRoles => [.. Roles.OfType<IUCTCustomRole>(), .. EcrRoles];
127121

128122
/// <summary>
129123
/// The list of every UCR role that will be a part of this wave
@@ -166,44 +160,55 @@ public static void Register(Team team)
166160
/// <summary>
167161
/// The list of every ECR role that will be a part of this wave
168162
/// </summary>
169-
public List<ExiledCustomRole> EcrRoles { get; set; } = new()
170-
{
163+
public List<ExiledCustomRole> EcrRoles { get; set; } =
164+
[
171165
new()
172166
{
173167
Id = 1,
174168
Priority = RolePriority.None,
175169
MaxPlayers = 1,
176170
DropInventoryOnDeath = true
177171
}
178-
};
172+
];
179173

180-
public static Team EvaluateSpawn(WaveType wave)
174+
public static List<Team> EvaluateSpawn(WaveType wave)
181175
{
176+
List<Team> winningTeams = [];
177+
182178
var eligibleTeams = List.Where(t => t.SpawnConditions.SpawnWave == wave).ToList();
183179

184180
if (!eligibleTeams.Any())
185181
{
186-
return null;
182+
return winningTeams;
187183
}
188184

189-
LogManager.Debug($"Found {eligibleTeams.Count} eligible Custom Team(s) for WaveType '{wave}'. Evaluating chances independently.");
185+
LogManager.Debug($"Found {eligibleTeams.Count} eligible Custom Team(s) for WaveType '{wave}'. Evaluating chances.");
190186

191187
foreach (var team in eligibleTeams)
192188
{
193189
int roll = _random.Next(0, 100);
194190
if (roll < team.SpawnChance)
195191
{
196-
LogManager.Debug($"Team '{team.Name}' succeeded its spawn roll! (Rolled: {roll}, Needed < {team.SpawnChance}). Selecting this team.");
197-
return team;
192+
LogManager.Debug($"Team '{team.Name}' succeeded its spawn roll! (Rolled: {roll}, Needed < {team.SpawnChance}). Adding to spawn list.");
193+
194+
winningTeams.Add(team);
195+
196+
if (!team.AllowConcurrentSpawns)
197+
{
198+
LogManager.Debug($"Team '{team.Name}' has AllowConcurrentSpawns set to false. Stopping further evaluations for this wave.");
199+
break;
200+
}
198201
}
199202
else
200203
{
201-
LogManager.Debug($"Team '{team.Name}' failed its spawn roll. (Rolled: {roll}, Needed < {team.SpawnChance}).");
204+
LogManager.Debug($"Team '{team.Name}' failed its spawn roll. (Rolled: {roll}, Needed >= {team.SpawnChance}).");
202205
}
203206
}
204207

205-
LogManager.Debug("No custom team succeeded their spawn roll. returning null...");
206-
return null;
208+
if (!winningTeams.Any())
209+
LogManager.Debug("No custom team succeeded their spawn roll for this wave.");
210+
211+
return winningTeams;
207212
}
208213

209214
public class SpawnData
@@ -212,10 +217,19 @@ public class SpawnData
212217
public Vector3 SpawnPosition { get; set; } = Vector3.zero;
213218
public Vector3 SpawnRotation { get; set; } = Vector3.zero; // yaml has skill issue with Quaternion
214219

220+
[Description("Spawn this team AFTER the custom team with the specified ID has spawned. SpawnWave must be set to 'TeamDependent'.")]
221+
public uint AfterTeamSpawn { get; set; } = 0;
222+
223+
[Description("Spawn this team AFTER the custom team with the specified ID has been completely eliminated. SpawnWave must be set to 'TeamDependent'.")]
224+
public uint AfterTeamDeath { get; set; } = 0;
225+
226+
[Description("How many generators must be engaged for this team to spawn. Only works if SpawnWave is set to 'AfterGeneratorActivated'. Value should be between 1 and 3.")]
227+
public int RequiredEngagedGenerators { get; set; } = 1;
228+
215229
private ItemType _usedItem = ItemType.None;
216230
private int? _customItemId = null;
217231

218-
[Description("Specify the item or custom item ID that triggers this team spawn. Only works if SpawnWave is set to 'UsedItem'.")]
232+
[Description("Specify the Game Base Utem or EXILED Custom Item ID that triggers this team spawn. Only works if SpawnWave is set to 'UsedItem'.")]
219233
public string UsedItem
220234
{
221235
get
@@ -266,7 +280,7 @@ public string UsedItem
266280
/// <returns><c>true</c> if the spawn position is required; otherwise, <c>false</c>.</returns>
267281
public bool RequiresSpawnPosition()
268282
{
269-
return SpawnWave == WaveType.AfterDecontamination || SpawnWave == WaveType.AfterWarhead || SpawnWave == WaveType.RoundStarted || SpawnWave == WaveType.ScpDeath || SpawnWave == WaveType.UsedItem;
283+
return SpawnWave == WaveType.AfterDecontamination || SpawnWave == WaveType.AfterWarhead || SpawnWave == WaveType.RoundStarted || SpawnWave == WaveType.ScpDeath || SpawnWave == WaveType.UsedItem || SpawnWave == WaveType.TeamDependent || SpawnWave == WaveType.AfterGeneratorActivated;
270284
}
271285
}
272286
/// <summary>
@@ -286,5 +300,16 @@ public class SoundPathEntry
286300
[Description("Delay in seconds before this sound starts playing.")]
287301
public float Delay { get; set; } = 0f;
288302
}
303+
304+
public class RoundEndRule
305+
{
306+
[Description("If true, the round will not end while this team is alive, effectively blocking standard round-end conditions. NOTE: This does NOT prevent the round from ending if this team wins by eliminating all enemies. If they win, the round ends immediately.")]
307+
public bool PreventRoundEndIfAlive { get; set; } = true;
308+
[Description("A list of vanilla teams that are considered allies. The Custom Team does NOT need to eliminate players from these teams to trigger the win condition.")]
309+
public List<PlayerRoles.Team> AlliedTeams { get; set; } = [];
310+
311+
[Description("The specific faction to declare as the winner when the win condition is met.")]
312+
public LeadingTeam WinningTeam { get; set; } = LeadingTeam.Draw;
313+
}
289314
}
290315
}

UncomplicatedCustomTeams/Commands/ForceNextWave.cs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ internal class ForceNextWave : IUCTCommand
1414
{
1515
public string Name { get; } = "fnw";
1616

17-
public string Description { get; } = "Forces the next wave to be a custom team.";
17+
public string Description { get; } = "Forces the next wave to be a custom team. Usage: 'uct fnw' (random) or 'uct fnw <ID> <ForceSpawn>' (specific).";
1818

1919
public string RequiredPermission { get; } = "uct.fnw";
2020

@@ -26,9 +26,19 @@ public bool Executor(List<string> arguments, ICommandSender sender, out string r
2626
return false;
2727
}
2828

29-
if (arguments.Count != 1)
29+
if (arguments.Count == 0)
3030
{
31-
response = "Usage: uct fnw <TeamId>";
31+
DefaultSpawnWaves.ForceAnyCustomTeam = true;
32+
DefaultSpawnWaves.ForcedNextWave = false;
33+
Plugin.NextTeam = null;
34+
35+
response = "Next wave will perform a guaranteed spawn of a RANDOM Custom Team (matching the Spawn Wave type).";
36+
return true;
37+
}
38+
39+
if (arguments.Count < 2)
40+
{
41+
response = "Usage: uct fnw <TeamId> <ForceSpawn (true/false)>\nExample: uct fnw 1 true";
3242
return false;
3343
}
3444

@@ -37,6 +47,13 @@ public bool Executor(List<string> arguments, ICommandSender sender, out string r
3747
response = "Invalid team ID!";
3848
return false;
3949
}
50+
51+
if (!bool.TryParse(arguments[1], out bool ignoreChance))
52+
{
53+
response = "Invalid boolean for ForceSpawn! Use 'true' (force 100%) or 'false' (respect spawn chance).";
54+
return false;
55+
}
56+
4057
var team = Team.List.FirstOrDefault(t => t.Id == id);
4158

4259
if (team is null)
@@ -53,8 +70,11 @@ public bool Executor(List<string> arguments, ICommandSender sender, out string r
5370

5471
Plugin.NextTeam = new SummonedTeam(team);
5572
DefaultSpawnWaves.ForcedNextWave = true;
73+
DefaultSpawnWaves.IgnoreSpawnChance = ignoreChance;
74+
DefaultSpawnWaves.ForceAnyCustomTeam = false;
5675

57-
response = $"Next wave will spawn team '{team.Name}' successfully.";
76+
string chanceMsg = ignoreChance ? "ignoring spawn chance (100% spawn)" : $"respecting spawn chance ({team.SpawnChance}%)";
77+
response = $"Next wave explicitly set to team '{team.Name}'. {chanceMsg}.";
5878
return true;
5979
}
6080
}

UncomplicatedCustomTeams/EventHandlers/SpawnWaves/DefaultSpawnWaves.cs

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ namespace UncomplicatedCustomTeams.EventHandlers.SpawnWaves
1212
internal class DefaultSpawnWaves
1313
{
1414
public static bool ForcedNextWave = false;
15+
public static bool IgnoreSpawnChance = true;
16+
public static bool ForceAnyCustomTeam = false;
1517
public static bool CustomTeamSpawnedThisWave = false;
1618

1719
public void OnRespawningTeam(RespawningTeamEventArgs ev)
@@ -29,28 +31,69 @@ public void OnRespawningTeam(RespawningTeamEventArgs ev)
2931
return;
3032
}
3133

32-
Plugin.NextTeam?.RefreshPlayers(allPlayers);
33-
34-
if (ForcedNextWave && Plugin.NextTeam is not null)
35-
{
36-
ForcedNextWave = false;
37-
Plugin.NextTeam.RefreshPlayers(allPlayers);
38-
CustomTeamSpawnedThisWave = true;
39-
LimitPlayersToCustomTeam(ev);
40-
LogManager.Debug($"Forced wave executed for {Plugin.NextTeam.Team.Name} with ID {Plugin.NextTeam.Team.Id}");
41-
return;
42-
}
43-
44-
LogManager.Debug($"Respawning team event, let's propose our team\nTeams: {API.Features.Team.List.Count}");
45-
LogManager.Debug($"Next team for respawn is {ev.NextKnownTeam}");
46-
4734
WaveType faction = ev.NextKnownTeam switch
4835
{
4936
PlayerRoles.Faction.FoundationStaff => WaveType.NtfWave,
5037
PlayerRoles.Faction.FoundationEnemy => WaveType.ChaosWave,
5138
_ => WaveType.None
5239
};
5340

41+
if (ForceAnyCustomTeam && faction != WaveType.None)
42+
{
43+
ForceAnyCustomTeam = false;
44+
var availableTeams = Team.List.Where(t => t.SpawnConditions.SpawnWave == faction).ToList();
45+
46+
if (availableTeams.Count > 0)
47+
{
48+
var randomTeam = availableTeams[UnityEngine.Random.Range(0, availableTeams.Count)];
49+
LogManager.Info($"Randomly selected custom team '{randomTeam.Name}' for forced spawn.");
50+
51+
Plugin.NextTeam = new SummonedTeam(randomTeam);
52+
ForcedNextWave = true;
53+
IgnoreSpawnChance = true;
54+
}
55+
else
56+
{
57+
LogManager.Warn($"No Custom Teams found for wave {faction}. Reverting to vanilla.");
58+
}
59+
}
60+
61+
Plugin.NextTeam?.RefreshPlayers(allPlayers);
62+
63+
if (ForcedNextWave && Plugin.NextTeam is not null)
64+
{
65+
bool shouldSpawn = true;
66+
67+
if (Plugin.NextTeam.Team.SpawnConditions.SpawnWave != faction)
68+
{
69+
LogManager.Warn($"Forced team '{Plugin.NextTeam.Team.Name}' wave mismatch ({Plugin.NextTeam.Team.SpawnConditions.SpawnWave} vs {faction}). Aborting force.");
70+
shouldSpawn = false;
71+
}
72+
else if (!IgnoreSpawnChance)
73+
{
74+
int roll = UnityEngine.Random.Range(0, 100);
75+
if (roll >= Plugin.NextTeam.Team.SpawnChance)
76+
{
77+
LogManager.Info($"Forced wave for '{Plugin.NextTeam.Team.Name}' failed RNG roll. Reverting to standard check.");
78+
shouldSpawn = false;
79+
}
80+
}
81+
82+
if (shouldSpawn)
83+
{
84+
ForcedNextWave = false;
85+
CustomTeamSpawnedThisWave = true;
86+
LimitPlayersToCustomTeam(ev);
87+
LogManager.Debug($"Forced wave executed for {Plugin.NextTeam.Team.Name}");
88+
return;
89+
}
90+
else
91+
{
92+
ForcedNextWave = false;
93+
Plugin.NextTeam = null;
94+
}
95+
}
96+
5497
if (faction is WaveType.None)
5598
{
5699
Plugin.NextTeam = null;
@@ -61,32 +104,27 @@ public void OnRespawningTeam(RespawningTeamEventArgs ev)
61104

62105
if (!teamsToSpawn.Any())
63106
{
64-
LogManager.Debug("No valid team found in EvaluateSpawns, aborting team selection.");
65107
Plugin.NextTeam = null;
66108
return;
67109
}
68110

69111
var team = teamsToSpawn.First();
70112

71-
if (team.SpawnConditions?.SpawnWave == faction)
113+
if (team != null)
72114
{
73115
Plugin.CachedSpawnList = SummonedTeam.CanSpawnTeam(team);
74116
Plugin.NextTeam = SummonedTeam.Summon(team, Plugin.CachedSpawnList);
75117

76118
if (Plugin.NextTeam is not null)
77119
{
78-
var allowedIds = Plugin.CachedSpawnList.Select(p => p.Id).ToList();
79-
ev.Players.RemoveAll(p => !allowedIds.Contains(p.Id));
80120
CustomTeamSpawnedThisWave = true;
121+
LimitPlayersToCustomTeam(ev);
81122
}
82123
}
83124
else
84125
{
85126
Plugin.NextTeam = null;
86-
LogManager.Debug("No valid custom team found for this wave.");
87127
}
88-
89-
LogManager.Debug($"Next team selected: {Plugin.NextTeam?.Team?.Name}");
90128
}
91129

92130
private void LimitPlayersToCustomTeam(RespawningTeamEventArgs ev)

0 commit comments

Comments
 (0)