Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion UncomplicatedCustomRoles/API/Features/CustomRole.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* This file is a part of the UncomplicatedCustomRoles project.
*
* Copyright (c) 2023-present FoxWorn3365 (Federico Cosma) <me@fcosma.it>
Expand Down Expand Up @@ -231,6 +231,13 @@ public class CustomRole : ICustomRole
/// </summary>
public virtual bool IgnoreSpawnSystem { get; set; } = false;

/// <summary>
/// Gets or sets an optional custom team/faction identifier (e.g. "SerpentHand", "UIU").<br></br>
/// Used by EndConditionsExtension for custom win conditions and escape-based scoring.
/// When null or empty, the role uses the standard <see cref="Team"/> for round-end evaluation.
/// </summary>
public virtual string CustomTeamId { get; set; } = null;

/// <summary>
/// Invoked when the custom role is spawned
/// </summary>
Expand Down
9 changes: 8 additions & 1 deletion UncomplicatedCustomRoles/API/Features/EventCustomRole.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* This file is a part of the UncomplicatedCustomRoles project.
*
* Copyright (c) 2023-present FoxWorn3365 (Federico Cosma) <me@fcosma.it>
Expand Down Expand Up @@ -217,6 +217,13 @@ public class EventCustomRole : ICustomRole
/// </summary>
public virtual bool IgnoreSpawnSystem { get; set; } = false;

/// <summary>
/// Gets or sets an optional custom team/faction identifier (e.g. "SerpentHand", "UIU").<br></br>
/// Used by EndConditionsExtension for custom win conditions and escape-based scoring.
/// When null or empty, the role uses the standard <see cref="Team"/> for round-end evaluation.
/// </summary>
public virtual string CustomTeamId { get; set; } = null;

public override string ToString() => $"{Regex.Replace(Name, "<color=.*?>(.*?)</color>", "$1")} ({Id})";

/// <summary>
Expand Down
9 changes: 8 additions & 1 deletion UncomplicatedCustomRoles/API/Features/SummonedCustomRole.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* This file is a part of the UncomplicatedCustomRoles project.
*
* Copyright (c) 2023-present FoxWorn3365 (Federico Cosma) <me@fcosma.it>
Expand Down Expand Up @@ -202,6 +202,9 @@ internal SummonedCustomRole(Player player, ICustomRole role, Triplet<string, str
CustomInfo.Role = Role.RoleAppearance.GetFullName();
});
}

// Raise the public event for external plugins/extensions
UcrEvents.RaiseCustomRoleAssigned(Player, Role, this);
}

/// <summary>
Expand Down Expand Up @@ -294,6 +297,10 @@ private bool EvaluateCustomActions()
public void Destroy()
{
LogManager.Silent($"Destroying instance {Id} of CR {Role.Id} of PL {Player}");

// Raise the public event before cleanup so extensions can still read state
UcrEvents.RaiseCustomRoleRemoved(Player, Role, this);

Remove();
List.TryRemove(Id, out _);
_cachedListByPlayerId.TryRemove(Player.PlayerId, out _);
Expand Down
111 changes: 111 additions & 0 deletions UncomplicatedCustomRoles/API/Features/UcrEvents.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* This file is a part of the UncomplicatedCustomRoles project.
*
* Copyright (c) 2023-present FoxWorn3365 (Federico Cosma) <me@fcosma.it>
*
* This file is licensed under the GNU Affero General Public License v3.0.
* You should have received a copy of the AGPL license along with this file.
* If not, see <https://www.gnu.org/licenses/>.
*/

using LabApi.Features.Wrappers;
using System;
using UncomplicatedCustomRoles.API.Interfaces;

namespace UncomplicatedCustomRoles.API.Features
{
/// <summary>
/// Public static event bus for external plugins/extensions to subscribe to UCR lifecycle events.
/// This is the primary integration point for addons like EndConditionsExtension.
/// </summary>
public static class UcrEvents
{
/// <summary>
/// Data passed along with every UCR event.
/// </summary>
public class CustomRoleEventData : EventArgs
{
/// <summary>
/// Gets the <see cref="LabApi.Features.Wrappers.Player"/> involved in the event.
/// </summary>
public Player Player { get; }

/// <summary>
/// Gets the <see cref="ICustomRole"/> definition of the role.
/// </summary>
public ICustomRole Role { get; }

/// <summary>
/// Gets the <see cref="SummonedCustomRole"/> instance, if available.
/// May be null in removal events if the instance was already destroyed.
/// </summary>
public SummonedCustomRole SummonedRole { get; }

public CustomRoleEventData(Player player, ICustomRole role, SummonedCustomRole summonedRole)
{
Player = player;
Role = role;
SummonedRole = summonedRole;
}
}

/// <summary>
/// Data passed when a custom role player escapes.
/// </summary>
public class CustomRoleEscapeEventData : CustomRoleEventData
{
/// <summary>
/// Gets whether the player was cuffed/disarmed when escaping.
/// </summary>
public bool IsCuffed { get; }

/// <summary>
/// Gets the new role the player is being assigned to after escaping.
/// May be null if the player receives a natural role change.
/// </summary>
public ICustomRole NewRole { get; }

/// <summary>
/// Gets the <see cref="ICustomRole.CustomTeamId"/> of the escaping role, for convenience.
/// </summary>
public string CustomTeamId => Role?.CustomTeamId;

public CustomRoleEscapeEventData(Player player, ICustomRole role, SummonedCustomRole summonedRole, bool isCuffed, ICustomRole newRole = null)
: base(player, role, summonedRole)
{
IsCuffed = isCuffed;
NewRole = newRole;
}
}

/// <summary>
/// Fired after a player has been assigned a custom role and fully initialized.
/// </summary>
public static event EventHandler<CustomRoleEventData> CustomRoleAssigned;

/// <summary>
/// Fired just before a player's custom role is destroyed/removed.
/// </summary>
public static event EventHandler<CustomRoleEventData> CustomRoleRemoved;

/// <summary>
/// Fired when a player with a custom role successfully escapes.
/// </summary>
public static event EventHandler<CustomRoleEscapeEventData> CustomRoleEscaped;

internal static void RaiseCustomRoleAssigned(Player player, ICustomRole role, SummonedCustomRole summonedRole)
{
CustomRoleAssigned?.Invoke(null, new CustomRoleEventData(player, role, summonedRole));
}

internal static void RaiseCustomRoleRemoved(Player player, ICustomRole role, SummonedCustomRole summonedRole)
{
CustomRoleRemoved?.Invoke(null, new CustomRoleEventData(player, role, summonedRole));
}

internal static void RaiseCustomRoleEscaped(Player player, ICustomRole role, SummonedCustomRole summonedRole, bool isCuffed, ICustomRole newRole = null)
{
CustomRoleEscaped?.Invoke(null, new CustomRoleEscapeEventData(player, role, summonedRole, isCuffed, newRole));
}
}
}
9 changes: 8 additions & 1 deletion UncomplicatedCustomRoles/API/Interfaces/ICustomRole.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* This file is a part of the UncomplicatedCustomRoles project.
*
* Copyright (c) 2023-present FoxWorn3365 (Federico Cosma) <me@fcosma.it>
Expand Down Expand Up @@ -82,5 +82,12 @@ public interface ICustomRole
public abstract List<object>? CustomFlags { get; set; }

public abstract bool IgnoreSpawnSystem { get; set; }

/// <summary>
/// Gets or sets an optional custom team/faction identifier (e.g. "SerpentHand", "UIU").
/// Used by EndConditionsExtension for custom win conditions and escape-based scoring.
/// When null or empty, the role uses the standard <see cref="Team"/> for round-end evaluation.
/// </summary>
public abstract string CustomTeamId { get; set; }
}
}
16 changes: 15 additions & 1 deletion UncomplicatedCustomRoles/Events/PlayerEventHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* This file is a part of the UncomplicatedCustomRoles project.
*
* Copyright (c) 2023-present FoxWorn3365 (Federico Cosma) <me@fcosma.it>
Expand Down Expand Up @@ -325,10 +325,15 @@ public void OnEscaping(PlayerEscapingEventArgs Escaping)
return;
}

bool isCuffed = Escaping.Player.IsDisarmed;

if (summoned.Role.CanEscape && (summoned.Role.RoleAfterEscape is null || summoned.Role.RoleAfterEscape.Count < 1))
{
LogManager.Debug($"Player with the role {summoned.Role.Id} ({summoned.Role.Name}) evaluated for a natural respawn!");
Escaping.IsAllowed = true;

// Raise escape event for extensions (natural escape, no new custom role)
UcrEvents.RaiseCustomRoleEscaped(Escaping.Player, summoned.Role, summoned, isCuffed);
return;
}

Expand All @@ -346,6 +351,9 @@ public void OnEscaping(PlayerEscapingEventArgs Escaping)
if (NewRole.Value is null)
{
Escaping.IsAllowed = true;

// Raise escape event for extensions (natural escape)
UcrEvents.RaiseCustomRoleEscaped(Escaping.Player, summoned.Role, summoned, isCuffed);
return;
}

Expand All @@ -358,6 +366,9 @@ public void OnEscaping(PlayerEscapingEventArgs Escaping)
{
Escaping.NewRole = role;
Escaping.IsAllowed = true;

// Raise escape event for extensions (escape to internal role)
UcrEvents.RaiseCustomRoleEscaped(Escaping.Player, summoned.Role, summoned, isCuffed);
}
}
}
Expand All @@ -378,6 +389,9 @@ public void OnEscaping(PlayerEscapingEventArgs Escaping)
LogManager.Silent($"Successfully activated the call to method SpawnManager::SummonCustomSubclass(<...>) as the player is not inside the Escape::Bucket bucket! - Adding it...");
API.Features.Escape.Bucket.Add(Escaping.Player.PlayerId);
SpawnManager.SummonCustomSubclass(Escaping.Player, role.Id);

// Raise escape event for extensions (escape to custom role)
UcrEvents.RaiseCustomRoleEscaped(Escaping.Player, summoned.Role, summoned, isCuffed, role);
}
else
LogManager.Silent($"Canceled call to method SpawnManager::SummonCustomSubclass(<...>) due to the presence of the player inside the Escape::Bucket! - Event already fired!");
Expand Down