Skip to content

Commit cc3ec70

Browse files
authored
Merge pull request #3
Added more UCR functions
2 parents fa4def4 + 83bde6f commit cc3ec70

8 files changed

Lines changed: 173 additions & 68 deletions

File tree

UncomplicatedCustomEscapeZones/API/Features/CustomEscapeZone.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class CustomEscapeZone : ICustomEscapeZone
4747
new()
4848
{
4949
{
50-
"ClassD", [
50+
"InternalTeam ClassD", [
5151
new Dictionary<string, string>
5252
{
5353
{ "default", "InternalRole ChaosRepressor" },
@@ -56,7 +56,7 @@ public class CustomEscapeZone : ICustomEscapeZone
5656
]
5757
},
5858
{
59-
"Scientist", [
59+
"InternalTeam Scientist", [
6060
new Dictionary<string, string>
6161
{
6262
{ "default", "InternalRole NtfSpecialist" },
@@ -71,7 +71,7 @@ public class CustomEscapeZone : ICustomEscapeZone
7171
/// </summary>
7272
/// <param name="id"></param>
7373
/// <param name="customEscapeZone"></param>
74-
/// <returns><see cref="true" /> if the operation was successfull.</returns>
74+
/// <returns><see langword="true" /> if the operation was successful.</returns>
7575
public static bool TryGet(int id, out ICustomEscapeZone customEscapeZone)
7676
{
7777
if (CustomEscapeZones.TryGetValue(id, out ICustomEscapeZone escapeZone))
@@ -88,7 +88,7 @@ public static bool TryGet(int id, out ICustomEscapeZone customEscapeZone)
8888
/// Get a registered <see cref="ICustomEscapeZone" /> by it's Id
8989
/// </summary>
9090
/// <param name="id"></param>
91-
/// <returns>The <see cref="ICustomEscapeZone" /> with the given Id or <see cref="null" /> if not found.</returns>
91+
/// <returns>The <see cref="ICustomEscapeZone" /> with the given Id or <see langword="null" /> if not found.</returns>
9292
public static ICustomEscapeZone Get(int id)
9393
{
9494
return TryGet(id, out ICustomEscapeZone customEscapeZone) ? customEscapeZone : null;

UncomplicatedCustomEscapeZones/Events/EventHandler.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ public override void OnPlayerEscaping(PlayerEscapingEventArgs ev)
2020

2121
if (ev.EscapeZone.TryGetEscapeZone(out SummonedEscapeZone escapeZone))
2222
{
23-
LogManager.Debug($"Player {ev.Player.Nickname} is escaping at custom escape zone: {escapeZone}");
23+
LogManager.Debug($"Player {ev.Player.Nickname} is escaping at custom escape zone: {escapeZone.Bounds}");
2424
if (escapeZone.Zone.RoleAfterEscape.Count < 1)
2525
{
26-
LogManager.Debug($"Player {ev.Player.Nickname} evaluated for a natural respawn!");
26+
LogManager.Debug($"Player {ev.Player.Nickname} evaluated for a natural respawn Reason: No RoleAfterEscape configured! {ev.EscapeScenario}");
2727
ev.IsAllowed = true;
2828
return;
2929
}
@@ -43,6 +43,7 @@ public override void OnPlayerEscaping(PlayerEscapingEventArgs ev)
4343
if (newRoleValue.Value is null)
4444
{
4545
ev.IsAllowed = true;
46+
LogManager.Debug($"Player {ev.Player.Nickname} evaluated for a natural respawn! {ev.EscapeScenario}");
4647
return;
4748
}
4849

@@ -54,6 +55,9 @@ public override void OnPlayerEscaping(PlayerEscapingEventArgs ev)
5455
{
5556
ev.NewRole = role;
5657
ev.IsAllowed = true;
58+
if (ev.EscapeScenario == Escape.EscapeScenarioType.None)
59+
ev.EscapeScenario = Escape.EscapeScenarioType.Custom;
60+
LogManager.Debug($"Player {ev.Player.Nickname} will respawn as {role})!");
5761
}
5862
}
5963
else
@@ -69,6 +73,8 @@ public override void OnPlayerEscaping(PlayerEscapingEventArgs ev)
6973
"Successfully activated the call to method SpawnManager::SummonCustomSubclass(<...>) as the player is not inside the Escape::Bucket bucket! - Adding it...");
7074
API.Features.Escape.Bucket.Add(ev.Player.PlayerId);
7175
UCR.GiveCustomRole(id, ev.Player);
76+
LogManager.Debug(
77+
$"Successfully called method SpawnManager::SummonCustomSubclass(<...>) for player {ev.Player.Nickname}!");
7278
}
7379
else
7480
{

UncomplicatedCustomEscapeZones/Intergrations/UCR.cs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,41 @@ public static bool TryGetSummonedCustomRole(Player player, out object summonedCu
9696
{
9797
object scrPlayer = scr.GetType().GetProperty("Player")?.GetValue(scr);
9898
PropertyInfo playerIdProp = scrPlayer?.GetType().GetProperty("PlayerId");
99-
if (playerIdProp != null && playerIdProp.GetValue(scrPlayer)?.Equals(player.PlayerId) == true)
100-
{
101-
summonedCustomRole = scr;
102-
return true;
103-
}
99+
if (playerIdProp == null || playerIdProp.GetValue(scrPlayer)?.Equals(player.PlayerId) != true) continue;
100+
summonedCustomRole = scr;
101+
return true;
104102
}
105103

106104
return false;
107105
}
106+
107+
internal static int? GetSummonedCustomRoleId(object summoned)
108+
{
109+
try
110+
{
111+
if (summoned is null)
112+
return null;
113+
LogManager.Debug($"Trying to obtain SummonedCustomRole.Id via reflection from {summoned}");
114+
Type t = summoned.GetType();
115+
PropertyInfo roleProp = t.GetProperty("Role", BindingFlags.Public | BindingFlags.Instance);
116+
LogManager.Debug($"Found Role property: {roleProp}");
117+
object roleObject = roleProp?.GetValue(summoned);
118+
if (roleObject is null)
119+
return null;
120+
LogManager.Debug($"Found Role object: {roleObject}");
121+
122+
PropertyInfo idProp = roleObject.GetType().GetProperty("Id", BindingFlags.Public | BindingFlags.Instance);
123+
LogManager.Debug($"Found Id property: {idProp}");
124+
object idValue = idProp?.GetValue(roleObject);
125+
LogManager.Debug($"Found Id value: {idValue}");
126+
if (idValue is int id)
127+
return id;
128+
LogManager.Debug("Id value is not an integer.");
129+
}
130+
catch (Exception e)
131+
{
132+
LogManager.Debug($"Reflection failed to obtain SummonedCustomRole.Id: {e.Message}");
133+
}
134+
return null;
135+
}
108136
}

UncomplicatedCustomEscapeZones/Managers/EscapeManager.cs

Lines changed: 118 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,69 +17,102 @@ public class EscapeManager
1717
{
1818
// Determine which role-specific configuration applies to this player
1919
string playerRoleKey = player.Role.ToString();
20-
LogManager.Debug(playerRoleKey);
21-
if (string.IsNullOrWhiteSpace(playerRoleKey))
20+
string playerTeamKey = player.Team.ToString();
21+
22+
LogManager.Debug($"Player Role: {playerRoleKey}");
23+
LogManager.Debug($"Player Team: {playerTeamKey}");
24+
25+
if (string.IsNullOrWhiteSpace(playerRoleKey) || string.IsNullOrWhiteSpace(playerTeamKey))
2226
{
2327
LogManager.Warn(
24-
$"Unable to determine player's role for escape evaluation (PlayerId={player.PlayerId}). Allowing natural escape.");
28+
$"Unable to determine player's role or team for escape evaluation (PlayerId={player.PlayerId}). Allowing natural escape.");
2529
return new KeyValuePair<bool, object?>(false, null);
2630
}
2731

28-
// Try exact match first, then case-insensitive match as fallback
29-
if (!roleAfterEscape.TryGetValue(playerRoleKey, out List<Dictionary<string, string>>? entries))
30-
foreach (string? key in roleAfterEscape.Keys.Where(key =>
31-
string.Equals(key, playerRoleKey, StringComparison.OrdinalIgnoreCase)))
32+
List<Dictionary<string, string>>? entries = ResolveEntries(
33+
roleAfterEscape,
34+
$"InternalTeam {playerTeamKey}",
35+
$"IT {playerRoleKey}",
36+
playerRoleKey);
37+
38+
if (UCR.TryGetSummonedCustomRole(player, out object summonedPlayer))
39+
{
40+
int? customRoleId = UCR.GetSummonedCustomRoleId(summonedPlayer);
41+
if (customRoleId is not null)
3242
{
33-
entries = roleAfterEscape[key];
34-
break;
43+
LogManager.Debug($"Player {player.PlayerId} has custom role {customRoleId}, checking for specific escape config...");
44+
List<Dictionary<string, string>>? customEntries = ResolveEntries(
45+
roleAfterEscape,
46+
$"CustomRole {customRoleId}",
47+
$"CR {customRoleId}");
48+
if (customEntries is not null)
49+
{
50+
LogManager.Debug($"Found {customEntries.Count} RoleAfterEscape entries for custom role '{customRoleId}'.");
51+
entries = customEntries;
52+
}
3553
}
54+
}
3655

3756
if (entries is null)
3857
{
3958
LogManager.Debug($"No RoleAfterEscape entries found for role '{playerRoleKey}'. Allowing natural escape.");
4059
return new KeyValuePair<bool, object?>(false, null);
4160
}
61+
62+
LogManager.Debug($"Found {entries.Count} RoleAfterEscape entries for role '{playerRoleKey}'.");
4263

4364
Dictionary<Team, KeyValuePair<bool, object?>?> asCuffedByInternalTeam = new();
65+
Dictionary<RoleTypeId, KeyValuePair<bool, object?>?> asCuffedByInternalRole = new();
4466
// Dictionary<uint, KeyValuePair<bool, object?>?> asCuffedByCustomTeam = new(); we will add the support to UCT and UIU-RS
4567
Dictionary<int, KeyValuePair<bool, object?>?> asCuffedByCustomRole = new();
4668

4769
KeyValuePair<bool, object?>? defaultValue = new KeyValuePair<bool, object?>(false, null);
4870

4971
// Flatten and parse all condition/value pairs for this role
5072
foreach (Dictionary<string, string> dict in entries)
51-
foreach (KeyValuePair<string, string> kvp in dict)
5273
{
53-
KeyValuePair<bool, object?>? data = ParseEscapeString(kvp.Value);
54-
if (kvp.Key is "default")
74+
LogManager.Debug($"Parsing RoleAfterEscape entry with {dict.Count} conditions.");
75+
foreach (KeyValuePair<string, string> kvp in dict)
5576
{
56-
defaultValue = data;
57-
}
58-
else
59-
{
60-
List<string> elements = kvp.Key.Split(' ').ToList();
61-
62-
if (elements.Count != 4 || elements[0] is not "cuffed" || elements[1] is not "by")
77+
KeyValuePair<bool, object?>? data = ParseEscapeString(kvp.Value);
78+
if (kvp.Key is "default")
6379
{
64-
LogManager.Warn(
65-
$"Failed to parse an EscapeRole[key]: syntax should be cuffed by <source> <id>, found {elements.Count} args!\nSource: {kvp.Key}");
66-
return new KeyValuePair<bool, object?>(false, RoleTypeId.Spectator);
80+
defaultValue = data;
81+
LogManager.Debug(
82+
$"Set default escape outcome for role '{playerRoleKey}' to: {(data is null ? "Deny" : data.Value.Key ? $"CustomRole {data.Value.Value}" : $"InternalRole {data.Value.Value}")}");
6783
}
68-
69-
switch (elements[2])
84+
else
7085
{
71-
case "InternalTeam" when Enum.TryParse(elements[3], out Team team):
72-
asCuffedByInternalTeam.TryAdd(team, data);
73-
break;
74-
case "CustomRole" when int.TryParse(elements[3], out int id) && UCR.TryGetCustomRole(id, out _):
75-
asCuffedByCustomRole.TryAdd(id, data);
76-
break;
77-
default:
86+
List<string> elements = kvp.Key.Split(' ').ToList();
87+
88+
if (elements.Count != 4 || elements[0] is not "cuffed" || elements[1] is not "by")
7889
{
79-
bool okInt = int.TryParse(elements[3], out _);
8090
LogManager.Warn(
81-
$"Function SpawnManager::ParseEscapeRole[2](<...>) failed!\nPossible causes can be:\n- The source is not valid. Allowed: InternalTeam / IT / CustomRole / CR. Found: {elements[2]}\n- The target is not a CustomRole / InternalRole. Found: {elements[3]} (int32 parsable: {okInt})");
82-
break;
91+
$"Failed to parse an EscapeRole[key]: syntax should be cuffed by <source> <id>, found {elements.Count} args!\nSource: {kvp.Key}");
92+
return new KeyValuePair<bool, object?>(false, RoleTypeId.Spectator);
93+
}
94+
95+
LogManager.Debug($"Parsing escape condition: {kvp.Key} -> {kvp.Value}");
96+
97+
switch (elements[2])
98+
{
99+
case "InternalTeam" or "IT" when Enum.TryParse(elements[3], out Team team):
100+
asCuffedByInternalTeam.TryAdd(team, data);
101+
break;
102+
case "InternalRole" or "IR" when Enum.TryParse(elements[3], out RoleTypeId id):
103+
asCuffedByInternalRole.TryAdd(id, data);
104+
break;
105+
case "CustomRole" or "CR"
106+
when int.TryParse(elements[3], out int id) && UCR.TryGetCustomRole(id, out _):
107+
asCuffedByCustomRole.TryAdd(id, data);
108+
break;
109+
default:
110+
{
111+
bool okInt = int.TryParse(elements[3], out _);
112+
LogManager.Warn(
113+
$"Function SpawnManager::ParseEscapeRole[2](<...>) failed!\nPossible causes can be:\n- The source is not valid. Allowed: InternalTeam / IT / CustomRole / CR. Found: {elements[2]}\n- The target is not a CustomRole / InternalRole. Found: {elements[3]} (int32 parsable: {okInt})");
114+
break;
115+
}
83116
}
84117
}
85118
}
@@ -88,19 +121,54 @@ public class EscapeManager
88121
// Now let's assign
89122
if (!player.IsDisarmed)
90123
return defaultValue;
91-
if (player.IsDisarmed && player.DisarmedBy is not null)
92-
//if (player.DisarmedBy.TryGetSummonedInstance(out SummonedCustomRole role) && asCuffedByCustomRole.ContainsKey(role.Role.Id))
93-
// return asCuffedByCustomRole[role.Role.Id];
94-
//else
95-
if (asCuffedByInternalTeam.ContainsKey(player.DisarmedBy.Team))
96-
return asCuffedByInternalTeam[player.DisarmedBy.Team];
124+
LogManager.Debug($"Player {player.PlayerId} is disarmed by {player.DisarmedBy?.Team} - {player.DisarmedBy?.Role}");
125+
if (player is { IsDisarmed: true, DisarmedBy: not null })
126+
{
127+
// Try custom role via reflection first
128+
if (UCR.TryGetSummonedCustomRole(player.DisarmedBy, out object summoned))
129+
{
130+
int? customRoleId = UCR.GetSummonedCustomRoleId(summoned);
131+
if (customRoleId is not null && asCuffedByCustomRole.TryGetValue(customRoleId.Value, out KeyValuePair<bool, object?>? escapeRole) && escapeRole is not null)
132+
{
133+
LogManager.Debug($"Player {player.PlayerId} disarmed by custom role {customRoleId}, applying mapped escape outcome.");
134+
return escapeRole;
135+
}
136+
}
137+
138+
// Then try internal role
139+
if (asCuffedByInternalRole.TryGetValue(player.DisarmedBy.Role, out KeyValuePair<bool, object?>? roleValue) && roleValue is not null)
140+
return roleValue;
141+
142+
if (asCuffedByInternalTeam.TryGetValue(player.DisarmedBy.Team, out KeyValuePair<bool, object?>? teamValue) && teamValue is not null)
143+
return teamValue;
144+
}
97145

98146
LogManager.Debug(
99147
$"Returing default type for escaping evaluation of player {player.PlayerId} who's cuffed by {player.DisarmedBy?.Team}");
100148
return defaultValue;
149+
150+
// Local function to resolve entries with case-insensitive keys
151+
List<Dictionary<string, string>>? ResolveEntries(
152+
Dictionary<string, List<Dictionary<string, string>>> source,
153+
params string[] keys)
154+
{
155+
foreach (string key in keys)
156+
{
157+
LogManager.Debug($"Attempting to resolve entries for key: {key}");
158+
if (source.TryGetValue(key, out List<Dictionary<string, string>>? value))
159+
return value;
160+
161+
string? ciMatch = source.Keys.FirstOrDefault(k =>
162+
string.Equals(k, key, StringComparison.OrdinalIgnoreCase));
163+
164+
if (ciMatch is not null)
165+
return source[ciMatch];
166+
}
167+
return null;
168+
}
101169
}
102170

103-
public static KeyValuePair<bool, object?>? ParseEscapeString(string escape)
171+
private static KeyValuePair<bool, object?>? ParseEscapeString(string escape)
104172
{
105173
if (escape is "Deny" or "deny" or "DENY")
106174
return null;
@@ -113,10 +181,15 @@ public class EscapeManager
113181
return new KeyValuePair<bool, object?>(false, RoleTypeId.Spectator);
114182
}
115183

116-
if (elements[0] is "CustomRole" || elements[0] is "CR")
117-
return new KeyValuePair<bool, object?>(true, int.Parse(elements[1]));
118-
if ((elements[0] is "InternalRole" || elements[0] is "IR") && Enum.TryParse(elements[1], out RoleTypeId role))
119-
return new KeyValuePair<bool, object?>(false, role);
184+
switch (elements[0])
185+
{
186+
case "CustomRole":
187+
case "CR":
188+
return new KeyValuePair<bool, object?>(true, int.Parse(elements[1]));
189+
case "InternalRole" or "IR" when Enum.TryParse(elements[1], out RoleTypeId role):
190+
return new KeyValuePair<bool, object?>(false, role);
191+
}
192+
120193
bool okInt = int.TryParse(elements[1], out _);
121194
LogManager.Warn(
122195
$"Function SpawnManager::ParseEscapeString(string escape) failed!\nPossible causes can be:\n- The source is not valid. Allowed: InternalRole / IR / CustomRole / CR. Found: {elements[0]}\n- The target is not a CustomRole / InternalRole. Found: {elements[1]} (int32 parsable: {okInt})");

UncomplicatedCustomEscapeZones/Managers/LogManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public static void Debug(string message)
2626
if (!DebugEnabled)
2727
return;
2828

29-
Logger.Raw($"[DEBUG] [{Plugin.Instance.Name}] {message}", ConsoleColor.Cyan);
29+
Logger.Raw($"[DEBUG] [{Plugin.Instance.Name}] {message}", ConsoleColor.Green);
3030
}
3131

3232
public static void Info(string message, ConsoleColor color = ConsoleColor.Cyan)

UncomplicatedCustomEscapeZones/Plugin.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ internal class Plugin : Plugin<Config>
2323
public override string Name => "UncomplicatedCustomEscapeZones";
2424
public override string Description => "Customize your SCP:SL server with Custom Escape Zones!";
2525
public override string Author => "MedveMarci & FoxWorn3365";
26-
public override Version Version => new(1, 0, 0, 0);
26+
public override Version Version => new(1, 1, 0, 0);
2727
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
2828
public override LoadPriority Priority => LoadPriority.Highest;
2929

UncomplicatedCustomEscapeZones/UncomplicatedCustomEscapeZones.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@
8181
<Compile Include="API\Features\SummonedEscapeZone.cs"/>
8282
<Compile Include="API\Struct\Triplet.cs"/>
8383
<Compile Include="Commands\CommandParent.cs"/>
84-
<Compile Include="Commands\GetData.cs" />
85-
<Compile Include="Commands\Outline.cs" />
84+
<Compile Include="Commands\GetData.cs"/>
85+
<Compile Include="Commands\Outline.cs"/>
8686
<Compile Include="Commands\Reload.cs"/>
8787
<Compile Include="Config.cs"/>
8888
<Compile Include="Events\EventHandler.cs"/>

0 commit comments

Comments
 (0)