2024-10-10 10:48:56 +02:00
using System.Diagnostics.CodeAnalysis ;
2025-01-11 22:17:26 +01:00
using System.Linq ;
Refactor antag rule code (#23445)
* Initial Pass, Rev, Thief
* Zombie initial pass
* Rebase, Traitor
* Nukeops, More overloads
* Revert RevolutionaryRuleComponent
* Use TryRoundStartAttempt, Rewrite nukie spawning
* Comments, Add task scheduler to GameRuleSystem
* Zombie initial testing done
* Sort methods, rework GameRuleTask
* Add CCVar, Initial testing continues
* Might as well get rid of the obsolete logging
* Oops, i dont know how to log apparently
* Suggested formatting fixes
* Suggested changes
* Fix merge issues
* Minor optimisation
* Allowed thief to choose other antags
* Review changes
* Spawn items on floor first, then inserting
* minor tweaks
* Shift as much as possible to ProtoId<>
* Remove unneeded
* Add exclusive antag attribute
* Fix merge issues
* Minor formatting fix
* Convert to struct
* Cleanup
* Review cleanup (need to test a lot)
* Some fixes, (mostly) tested
* oop
* Pass tests (for real)
---------
Co-authored-by: Rainfall <rainfey0+git@gmail.com>
Co-authored-by: AJCM <AJCM@tutanota.com>
2024-02-29 06:25:10 +00:00
using Content.Shared.Administration.Logs ;
2024-06-08 04:43:02 +12:00
using Content.Shared.CCVar ;
2023-08-30 21:46:11 -07:00
using Content.Shared.Database ;
2025-01-11 22:17:26 +01:00
using Content.Shared.GameTicking ;
2023-08-30 21:46:11 -07:00
using Content.Shared.Mind ;
2025-08-13 12:51:46 +02:00
using Content.Shared.Roles.Components ;
2025-08-08 16:58:46 +01:00
using Content.Shared.Whitelist ;
2023-11-14 12:52:40 +00:00
using Robust.Shared.Audio ;
2023-11-27 22:12:34 +11:00
using Robust.Shared.Audio.Systems ;
2024-06-08 04:43:02 +12:00
using Robust.Shared.Configuration ;
2025-04-19 00:23:01 +02:00
using Robust.Shared.Player ;
2023-08-30 21:46:11 -07:00
using Robust.Shared.Prototypes ;
2025-01-11 22:17:26 +01:00
using Robust.Shared.Serialization ;
2024-10-14 16:05:25 +13:00
using Robust.Shared.Utility ;
2023-08-30 21:46:11 -07:00
namespace Content.Shared.Roles ;
public abstract class SharedRoleSystem : EntitySystem
{
2025-08-08 16:58:46 +01:00
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default ! ;
[Dependency] private readonly SharedAudioSystem _audio = default ! ;
[Dependency] private readonly IConfigurationManager _cfg = default ! ;
2025-04-19 00:23:01 +02:00
[Dependency] protected readonly ISharedPlayerManager Player = default ! ;
2025-08-08 16:58:46 +01:00
[Dependency] private readonly IEntityManager _entityManager = default ! ;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default ! ;
[Dependency] private readonly SharedMindSystem _minds = default ! ;
[Dependency] private readonly IPrototypeManager _prototypes = default ! ;
2023-08-30 21:46:11 -07:00
2024-06-08 04:43:02 +12:00
private JobRequirementOverridePrototype ? _requirementOverride ;
2023-08-30 21:46:11 -07:00
public override void Initialize ( )
{
2024-06-08 04:43:02 +12:00
Subs . CVar ( _cfg , CCVars . GameRoleTimerOverride , SetRequirementOverride , true ) ;
2024-11-27 00:56:35 +01:00
2025-01-11 22:17:26 +01:00
SubscribeLocalEvent < StartingMindRoleComponent , PlayerSpawnCompleteEvent > ( OnSpawn ) ;
}
private void OnSpawn ( EntityUid uid , StartingMindRoleComponent component , PlayerSpawnCompleteEvent args )
{
if ( ! _minds . TryGetMind ( uid , out var mindId , out var mindComp ) )
return ;
MindAddRole ( mindId , component . MindRole , mind : mindComp , silent : component . Silent ) ;
2024-06-08 04:43:02 +12:00
}
private void SetRequirementOverride ( string value )
{
if ( string . IsNullOrEmpty ( value ) )
{
_requirementOverride = null ;
return ;
}
2025-08-21 03:10:07 +02:00
if ( ! _prototypes . TryIndex ( value , out _requirementOverride ) )
2024-06-08 04:43:02 +12:00
Log . Error ( $"Unknown JobRequirementOverridePrototype: {value}" ) ;
2023-08-30 21:46:11 -07:00
}
2024-10-10 10:48:56 +02:00
/// <summary>
/// Adds multiple mind roles to a mind
/// </summary>
/// <param name="mindId">The mind entity to add the role to</param>
/// <param name="roles">The list of mind roles to add</param>
/// <param name="mind">If the mind component is provided, it will be checked if it belongs to the mind entity</param>
/// <param name="silent">If true, no briefing will be generated upon receiving the mind role</param>
public void MindAddRoles ( EntityUid mindId ,
2025-01-11 22:17:26 +01:00
List < EntProtoId > ? roles ,
2024-10-10 10:48:56 +02:00
MindComponent ? mind = null ,
bool silent = false )
2023-08-30 21:46:11 -07:00
{
2024-10-10 10:48:56 +02:00
if ( roles is null | | roles . Count = = 0 )
return ;
foreach ( var proto in roles )
2023-08-30 21:46:11 -07:00
{
2024-10-10 10:48:56 +02:00
MindAddRole ( mindId , proto , mind , silent ) ;
2023-08-30 21:46:11 -07:00
}
2024-10-10 10:48:56 +02:00
}
2023-08-30 21:46:11 -07:00
2024-10-10 10:48:56 +02:00
/// <summary>
/// Adds a mind role to a mind
/// </summary>
/// <param name="mindId">The mind entity to add the role to</param>
/// <param name="protoId">The mind role to add</param>
/// <param name="mind">If the mind component is provided, it will be checked if it belongs to the mind entity</param>
/// <param name="silent">If true, no briefing will be generated upon receiving the mind role</param>
public void MindAddRole ( EntityUid mindId ,
2025-01-11 22:17:26 +01:00
EntProtoId protoId ,
2024-10-10 10:48:56 +02:00
MindComponent ? mind = null ,
bool silent = false )
{
if ( protoId = = "MindRoleJob" )
MindAddJobRole ( mindId , mind , silent , "" ) ;
else
MindAddRoleDo ( mindId , protoId , mind , silent ) ;
2023-08-30 21:46:11 -07:00
}
2024-10-10 10:48:56 +02:00
/// <summary>
/// Adds a Job mind role with the specified job prototype
/// </summary>
/// /// <param name="mindId">The mind entity to add the job role to</param>
/// <param name="mind">If the mind component is provided, it will be checked if it belongs to the mind entity</param>
/// <param name="silent">If true, no briefing will be generated upon receiving the mind role</param>
/// <param name="jobPrototype">The Job prototype for the new role</param>
public void MindAddJobRole ( EntityUid mindId ,
MindComponent ? mind = null ,
bool silent = false ,
string? jobPrototype = null )
2023-08-30 21:46:11 -07:00
{
2024-10-14 16:05:25 +13:00
if ( ! Resolve ( mindId , ref mind ) )
return ;
2024-10-10 10:48:56 +02:00
// Can't have someone get paid for two jobs now, can we
2024-10-14 16:05:25 +13:00
if ( MindHasRole < JobRoleComponent > ( ( mindId , mind ) , out var jobRole )
& & jobRole . Value . Comp1 . JobPrototype ! = jobPrototype )
2023-08-30 21:46:11 -07:00
{
2024-10-14 16:05:25 +13:00
_adminLogger . Add ( LogType . Mind ,
LogImpact . Low ,
$"Job Role of {ToPrettyString(mind.OwnedEntity)} changed from '{jobRole.Value.Comp1.JobPrototype}' to '{jobPrototype}'" ) ;
2023-08-30 21:46:11 -07:00
2024-10-14 16:05:25 +13:00
jobRole . Value . Comp1 . JobPrototype = jobPrototype ;
2024-10-10 10:48:56 +02:00
}
else
MindAddRoleDo ( mindId , "MindRoleJob" , mind , silent , jobPrototype ) ;
2023-08-30 21:46:11 -07:00
}
2024-10-10 10:48:56 +02:00
/// <summary>
/// Creates a Mind Role
/// </summary>
private void MindAddRoleDo ( EntityUid mindId ,
2025-01-11 22:17:26 +01:00
EntProtoId protoId ,
2024-10-10 10:48:56 +02:00
MindComponent ? mind = null ,
bool silent = false ,
string? jobPrototype = null )
2024-04-25 20:25:57 -04:00
{
if ( ! Resolve ( mindId , ref mind ) )
2024-10-10 10:48:56 +02:00
{
2025-01-11 22:17:26 +01:00
Log . Error ( $"Failed to add role {protoId} to {ToPrettyString(mindId)} : Mind does not match provided mind component" ) ;
2024-04-25 20:25:57 -04:00
return ;
2024-10-10 10:48:56 +02:00
}
2024-04-25 20:25:57 -04:00
2025-09-09 18:17:56 +02:00
if ( ! _prototypes . Resolve ( protoId , out var protoEnt ) )
2024-04-25 20:25:57 -04:00
{
2025-01-11 22:17:26 +01:00
Log . Error ( $"Failed to add role {protoId} to {ToPrettyString(mindId)} : Role prototype does not exist" ) ;
2024-10-10 10:48:56 +02:00
return ;
}
2024-04-25 20:25:57 -04:00
2024-10-10 10:48:56 +02:00
//TODO don't let a prototype being added a second time
//If that was somehow to occur, a second mindrole for that comp would be created
//Meaning any mind role checks could return wrong results, since they just return the first match they find
2025-08-21 03:10:07 +02:00
if ( ! PredictedTrySpawnInContainer ( protoId , mindId , MindComponent . MindRoleContainerId , out var mindRoleId ) )
{
Log . Error ( $"Failed to add role {protoId} to {ToPrettyString(mindId)} : Could not spawn the role entity inside the container" ) ;
return ;
}
var mindRoleComp = EnsureComp < MindRoleComponent > ( mindRoleId . Value ) ;
2024-10-10 10:48:56 +02:00
if ( jobPrototype is not null )
{
mindRoleComp . JobPrototype = jobPrototype ;
2025-08-21 03:10:07 +02:00
EnsureComp < JobRoleComponent > ( mindRoleId . Value ) ;
2024-10-14 16:05:25 +13:00
DebugTools . AssertNull ( mindRoleComp . AntagPrototype ) ;
DebugTools . Assert ( ! mindRoleComp . Antag ) ;
DebugTools . Assert ( ! mindRoleComp . ExclusiveAntag ) ;
2024-04-25 20:25:57 -04:00
}
2025-01-11 22:17:26 +01:00
var update = MindRolesUpdate ( ( mindId , mind ) ) ;
2024-04-25 20:25:57 -04:00
2025-01-11 22:17:26 +01:00
// RoleType refresh, Role time tracking, Update Admin playerlist
2025-02-23 01:52:48 +01:00
var message = new RoleAddedEvent ( mindId , mind , update , silent ) ;
RaiseLocalEvent ( mindId , message , true ) ;
2024-04-25 20:25:57 -04:00
2024-10-10 10:48:56 +02:00
var name = Loc . GetString ( protoEnt . Name ) ;
if ( mind . OwnedEntity is not null )
{
_adminLogger . Add ( LogType . Mind ,
LogImpact . Low ,
$"{name} added to mind of {ToPrettyString(mind.OwnedEntity)}" ) ;
}
else
{
//TODO: This is not tied to the player on the Admin Log filters.
//Probably only happens when Job Role is added on initial spawn, before the mind entity is put in a mob
2025-01-11 22:17:26 +01:00
Log . Error ( $"{ToPrettyString(mindId)} does not have an OwnedEntity!" ) ;
2024-10-10 10:48:56 +02:00
_adminLogger . Add ( LogType . Mind ,
LogImpact . Low ,
$"{name} added to {ToPrettyString(mindId)}" ) ;
}
2024-04-25 20:25:57 -04:00
}
2025-01-11 22:17:26 +01:00
/// <summary>
/// Select the mind's currently "active" mind role entity, and update the mind's role type, if necessary
/// </summary>
/// <returns>
/// True if this changed the mind's role type
/// </returns>>
private bool MindRolesUpdate ( Entity < MindComponent ? > ent )
{
2025-08-21 03:10:07 +02:00
if ( ! Resolve ( ent . Owner , ref ent . Comp ) )
2025-01-11 22:17:26 +01:00
return false ;
//get the most important/latest mind role
2025-04-16 19:04:48 +02:00
var ( roleType , subtype ) = GetRoleTypeByTime ( ent . Comp ) ;
2025-01-11 22:17:26 +01:00
2025-08-21 03:10:07 +02:00
if ( ent . Comp . RoleType = = roleType & & ent . Comp . Subtype = = subtype )
2025-01-11 22:17:26 +01:00
return false ;
2025-04-16 19:04:48 +02:00
SetRoleType ( ent . Owner , roleType , subtype ) ;
2025-01-11 22:17:26 +01:00
return true ;
}
2025-03-25 17:03:59 +01:00
/// <summary>
2025-04-16 19:04:48 +02:00
/// Return the most recently specified role type and subtype, or Neutral
2025-03-25 17:03:59 +01:00
/// </summary>
2025-04-16 19:04:48 +02:00
private ( ProtoId < RoleTypePrototype > , LocId ? ) GetRoleTypeByTime ( MindComponent mind )
2025-01-11 22:17:26 +01:00
{
2025-03-25 17:03:59 +01:00
var role = GetRoleCompByTime ( mind ) ;
2025-04-16 19:04:48 +02:00
return ( role ? . Comp ? . RoleType ? ? "Neutral" , role ? . Comp ? . Subtype ) ;
2025-03-25 17:03:59 +01:00
}
2025-01-11 22:17:26 +01:00
2025-03-25 17:03:59 +01:00
/// <summary>
/// Return the most recently specified role type's mind role entity, or null
/// </summary>
public Entity < MindRoleComponent > ? GetRoleCompByTime ( MindComponent mind )
{
var roles = new List < Entity < MindRoleComponent > > ( ) ;
2025-01-11 22:17:26 +01:00
2025-08-21 03:10:07 +02:00
foreach ( var role in mind . MindRoleContainer . ContainedEntities )
2025-01-11 22:17:26 +01:00
{
var comp = Comp < MindRoleComponent > ( role ) ;
if ( comp . RoleType is not null )
2025-03-25 17:03:59 +01:00
roles . Add ( ( role , comp ) ) ;
2025-01-11 22:17:26 +01:00
}
2025-03-25 17:03:59 +01:00
Entity < MindRoleComponent > ? result = roles . Count > 0 ? roles . LastOrDefault ( ) : null ;
2025-01-11 22:17:26 +01:00
return ( result ) ;
}
2025-04-16 19:04:48 +02:00
private void SetRoleType ( EntityUid mind , ProtoId < RoleTypePrototype > roleTypeId , LocId ? subtype )
2025-01-11 22:17:26 +01:00
{
if ( ! TryComp < MindComponent > ( mind , out var comp ) )
{
2025-04-16 19:04:48 +02:00
Log . Error ( $"Failed to update Role Type of mind entity {ToPrettyString(mind)} to {roleTypeId}, {subtype}. MindComponent not found." ) ;
2025-01-11 22:17:26 +01:00
return ;
}
if ( ! _prototypes . HasIndex ( roleTypeId ) )
{
2025-04-16 19:04:48 +02:00
Log . Error ( $"Failed to change Role Type of {_minds.MindOwnerLoggingString(comp)} to {roleTypeId}, {subtype}. Invalid role" ) ;
2025-01-11 22:17:26 +01:00
return ;
}
comp . RoleType = roleTypeId ;
2025-04-16 19:04:48 +02:00
comp . Subtype = subtype ;
2025-01-11 22:17:26 +01:00
Dirty ( mind , comp ) ;
// Update player character window
2025-04-19 00:23:01 +02:00
if ( Player . TryGetSessionById ( comp . UserId , out var session ) )
2025-01-11 22:17:26 +01:00
RaiseNetworkEvent ( new MindRoleTypeChangedEvent ( ) , session . Channel ) ;
else
{
var error = $"The Character Window of {_minds.MindOwnerLoggingString(comp)} potentially did not update immediately : session error" ;
2025-03-20 20:56:51 +01:00
_adminLogger . Add ( LogType . Mind , LogImpact . Medium , $"{error}" ) ;
2025-01-11 22:17:26 +01:00
}
if ( comp . OwnedEntity is null )
{
Log . Error ( $"{ToPrettyString(mind)} does not have an OwnedEntity!" ) ;
_adminLogger . Add ( LogType . Mind ,
2025-03-20 20:56:51 +01:00
LogImpact . Medium ,
2025-04-16 19:04:48 +02:00
$"Role Type of {ToPrettyString(mind)} changed to {roleTypeId}, {subtype}" ) ;
2025-01-11 22:17:26 +01:00
return ;
}
_adminLogger . Add ( LogType . Mind ,
LogImpact . High ,
2025-04-16 19:04:48 +02:00
$"Role Type of {ToPrettyString(comp.OwnedEntity)} changed to {roleTypeId}, {subtype}" ) ;
2025-01-11 22:17:26 +01:00
}
2024-10-10 10:48:56 +02:00
/// <summary>
2025-05-17 08:24:32 +02:00
/// Finds and removes all mind roles of a specific type
2024-10-10 10:48:56 +02:00
/// </summary>
2024-10-14 16:05:25 +13:00
/// <param name="mind">The mind to remove the role from.</param>
2024-10-10 10:48:56 +02:00
/// <typeparam name="T">The type of the role to remove.</typeparam>
2025-05-17 08:24:32 +02:00
/// <returns>True if the role existed and was removed</returns>>
2024-10-14 16:05:25 +13:00
public bool MindRemoveRole < T > ( Entity < MindComponent ? > mind ) where T : IComponent
2024-04-24 21:31:45 -04:00
{
2024-10-14 16:05:25 +13:00
if ( typeof ( T ) = = typeof ( MindRoleComponent ) )
throw new InvalidOperationException ( ) ;
if ( ! Resolve ( mind . Owner , ref mind . Comp ) )
return false ;
2024-04-24 21:31:45 -04:00
2024-10-10 10:48:56 +02:00
var delete = new List < EntityUid > ( ) ;
2025-05-17 08:24:32 +02:00
// If there were no matches and thus no mind role entity names, we'll need the component's name, to report what role failed to be removed
var original = "'" + typeof ( T ) . Name + "'" ;
var deleteName = original ;
2025-08-21 03:10:07 +02:00
foreach ( var role in mind . Comp . MindRoleContainer . ContainedEntities )
2024-04-24 21:31:45 -04:00
{
2025-05-17 08:24:32 +02:00
if ( ! HasComp < MindRoleComponent > ( role ) )
{
Log . Error ( $"Encountered mind role entity {ToPrettyString(role)} without a {nameof(MindRoleComponent)}" ) ;
continue ;
}
2024-10-10 10:48:56 +02:00
if ( ! HasComp < T > ( role ) )
continue ;
2025-05-17 08:24:32 +02:00
delete . Add ( role ) ;
deleteName = RemoveRoleLogNameGeneration ( deleteName , MetaData ( role ) . EntityName , original ) ;
}
return MindRemoveRoleDo ( mind , delete , deleteName ) ;
}
private string RemoveRoleLogNameGeneration ( string name , string newName , string original )
{
// If there were matches for deletion, this will run, and we get a new name to replace the original input
if ( name = = original )
name = "'" + newName + "'" ;
// It is theoretically possible to get multiple matches
// If they have different names, then we want all of them listed
else if ( ! name . Contains ( newName ) )
// and we can't just drop the multiple names within a single ' ' section later, because that would
// make it look like it's one name that is just formatted to look like a list
name = name + ", " + "'" + newName + "'" ;
return name ;
}
/// <summary>
/// Finds and removes all mind roles of a specific type
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <typeparam name="T">The type of the role to remove.</typeparam>
/// <returns>True if the role existed and was removed</returns>
public bool MindRemoveRole < T > ( EntityUid mindId ) where T : IComponent
{
if ( ! TryComp < MindComponent > ( mindId , out var mind ) )
{
Log . Error ( $"The specified mind entity '{ToPrettyString(mindId)}' does not have a {nameof(MindComponent)}" ) ;
return false ;
}
return MindRemoveRole < T > ( ( mindId , mind ) ) ;
}
2025-08-21 03:10:07 +02:00
/// <summary>
2025-05-17 08:24:32 +02:00
/// Finds and removes all mind roles of a specific type
/// </summary>
/// <param name="mind">The mind entity and component</param>
/// /// <param name="protoId">The prototype ID of the mind role to be removed</param>
/// <returns>True if the role existed and was removed</returns>
public bool MindRemoveRole ( Entity < MindComponent ? > mind , EntProtoId < MindRoleComponent > protoId )
{
2025-08-21 03:10:07 +02:00
if ( ! Resolve ( mind . Owner , ref mind . Comp ) )
2025-05-17 08:24:32 +02:00
return false ;
// If there were no matches and thus no mind role entity names, we'll need the protoId, to report what role failed to be removed
var original = "'" + protoId + "'" ;
var deleteName = original ;
var delete = new List < EntityUid > ( ) ;
2025-08-21 03:10:07 +02:00
foreach ( var role in mind . Comp . MindRoleContainer . ContainedEntities )
2025-05-17 08:24:32 +02:00
{
2025-01-11 22:17:26 +01:00
if ( ! HasComp < MindRoleComponent > ( role ) )
2024-10-14 16:05:25 +13:00
{
Log . Error ( $"Encountered mind role entity {ToPrettyString(role)} without a {nameof(MindRoleComponent)}" ) ;
continue ;
}
2024-10-10 10:48:56 +02:00
2025-05-17 08:24:32 +02:00
var id = MetaData ( role ) . EntityPrototype ? . ID ;
if ( id is null | | id ! = protoId )
continue ;
2024-10-10 10:48:56 +02:00
delete . Add ( role ) ;
2025-05-17 08:24:32 +02:00
deleteName = RemoveRoleLogNameGeneration ( deleteName , MetaData ( role ) . EntityName , original ) ;
2024-04-24 21:31:45 -04:00
}
2025-05-17 08:24:32 +02:00
return MindRemoveRoleDo ( mind , delete , deleteName ) ;
}
/// <summary>
/// Performs the actual role entity deletion.
/// </summary>
private bool MindRemoveRoleDo ( Entity < MindComponent ? > mind , List < EntityUid > delete , string? logName = "" )
{
2025-08-21 03:10:07 +02:00
if ( ! Resolve ( mind . Owner , ref mind . Comp ) )
2024-10-14 16:05:25 +13:00
return false ;
2025-05-17 08:24:32 +02:00
if ( delete . Count < = 0 )
{
Log . Warning ( $"Failed to remove mind role {logName} from {ToPrettyString(mind.Owner)} : mind does not have this role " ) ;
return false ;
}
2024-10-10 10:48:56 +02:00
foreach ( var role in delete )
{
2024-11-27 00:56:35 +01:00
_entityManager . DeleteEntity ( role ) ;
2024-10-10 10:48:56 +02:00
}
2024-04-24 21:31:45 -04:00
2025-01-11 22:17:26 +01:00
var update = MindRolesUpdate ( mind ) ;
2025-02-23 01:52:48 +01:00
var message = new RoleRemovedEvent ( mind . Owner , mind . Comp , update ) ;
RaiseLocalEvent ( mind , message , true ) ;
2024-10-10 10:48:56 +02:00
_adminLogger . Add ( LogType . Mind ,
LogImpact . Low ,
2025-05-17 08:24:32 +02:00
$"All roles of type {logName} removed from mind of {ToPrettyString(mind.Comp.OwnedEntity)}" ) ;
2024-10-14 16:05:25 +13:00
2024-10-10 10:48:56 +02:00
return true ;
2024-04-24 21:31:45 -04:00
}
2024-10-10 10:48:56 +02:00
/// <summary>
/// Finds the first mind role of a specific T type on a mind entity.
/// Outputs entity components for the mind role's MindRoleComponent and for T
/// </summary>
2025-01-11 22:17:26 +01:00
/// <param name="mind">The mind entity</param>
2024-10-10 10:48:56 +02:00
/// <typeparam name="T">The type of the role to find.</typeparam>
/// <param name="role">The Mind Role entity component</param>
/// <returns>True if the role is found</returns>
2024-10-14 16:05:25 +13:00
public bool MindHasRole < T > ( Entity < MindComponent ? > mind ,
[NotNullWhen(true)] out Entity < MindRoleComponent , T > ? role ) where T : IComponent
2024-10-10 10:48:56 +02:00
{
role = null ;
2024-10-14 16:05:25 +13:00
if ( ! Resolve ( mind . Owner , ref mind . Comp ) )
2024-10-10 10:48:56 +02:00
return false ;
2025-08-21 03:10:07 +02:00
foreach ( var roleEnt in mind . Comp . MindRoleContainer . ContainedEntities )
2023-08-30 21:46:11 -07:00
{
2024-10-14 16:05:25 +13:00
if ( ! TryComp ( roleEnt , out T ? tcomp ) )
2024-10-10 10:48:56 +02:00
continue ;
2024-10-14 16:05:25 +13:00
if ( ! TryComp ( roleEnt , out MindRoleComponent ? roleComp ) )
{
Log . Error ( $"Encountered mind role entity {ToPrettyString(roleEnt)} without a {nameof(MindRoleComponent)}" ) ;
continue ;
}
role = ( roleEnt , roleComp , tcomp ) ;
return true ;
2023-08-30 21:46:11 -07:00
}
2024-10-14 16:05:25 +13:00
return false ;
2023-08-30 21:46:11 -07:00
}
/// <summary>
2024-10-10 10:48:56 +02:00
/// Finds the first mind role of a specific type on a mind entity.
/// Outputs an entity component for the mind role's MindRoleComponent
2023-08-30 21:46:11 -07:00
/// </summary>
2024-10-10 10:48:56 +02:00
/// <param name="mindId">The mind entity</param>
/// <param name="type">The Type to look for</param>
/// <param name="role">The output role</param>
/// <returns>True if the role is found</returns>
public bool MindHasRole ( EntityUid mindId ,
Type type ,
[NotNullWhen(true)] out Entity < MindRoleComponent > ? role )
2023-08-30 21:46:11 -07:00
{
2024-10-10 10:48:56 +02:00
role = null ;
// All MindRoles have this component, it would just return the first one.
// Order might not be what is expected.
// Better to report null
if ( type = = Type . GetType ( "MindRoleComponent" ) )
2023-08-30 21:46:11 -07:00
{
2024-10-10 10:48:56 +02:00
Log . Error ( $"Something attempted to query mind role 'MindRoleComponent' on mind {mindId}. This component is present on every single mind role." ) ;
return false ;
2023-08-30 21:46:11 -07:00
}
2024-10-10 10:48:56 +02:00
if ( ! TryComp < MindComponent > ( mindId , out var mind ) )
return false ;
2023-08-30 21:46:11 -07:00
2024-10-10 10:48:56 +02:00
var found = false ;
2025-08-21 03:10:07 +02:00
foreach ( var roleEnt in mind . MindRoleContainer . ContainedEntities )
2023-08-30 21:46:11 -07:00
{
2024-10-10 10:48:56 +02:00
if ( ! HasComp ( roleEnt , type ) )
continue ;
2024-10-14 16:05:25 +13:00
if ( ! TryComp ( roleEnt , out MindRoleComponent ? roleComp ) )
{
Log . Error ( $"Encountered mind role entity {ToPrettyString(roleEnt)} without a {nameof(MindRoleComponent)}" ) ;
continue ;
}
role = ( roleEnt , roleComp ) ;
2024-10-10 10:48:56 +02:00
found = true ;
break ;
2023-08-30 21:46:11 -07:00
}
2024-10-10 10:48:56 +02:00
return found ;
2023-08-30 21:46:11 -07:00
}
2025-08-08 16:58:46 +01:00
/// <summary>
/// Returns true if a mind has a role that matches a whitelist.
/// </summary>
public bool MindHasRole ( Entity < MindComponent > mind , EntityWhitelist whitelist )
{
2025-08-21 03:10:07 +02:00
foreach ( var roleEnt in mind . Comp . MindRoleContainer . ContainedEntities )
2025-08-08 16:58:46 +01:00
{
if ( _whitelist . IsWhitelistPass ( whitelist , roleEnt ) )
return true ;
}
return false ;
}
2024-10-10 10:48:56 +02:00
/// <summary>
/// Finds the first mind role of a specific type on a mind entity.
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <typeparam name="T">The type of the role to find.</typeparam>
/// <returns>True if the role is found</returns>
2023-10-17 19:42:47 -07:00
public bool MindHasRole < T > ( EntityUid mindId ) where T : IComponent
2023-08-30 21:46:11 -07:00
{
2024-10-14 16:05:25 +13:00
return MindHasRole < T > ( mindId , out _ ) ;
2023-08-30 21:46:11 -07:00
}
2024-10-10 10:48:56 +02:00
//TODO: Delete this later
/// <summary>
/// Returns the first mind role of a specific type
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <returns>Entity Component of the mind role</returns>
[Obsolete("Use MindHasRole's output value")]
public Entity < MindRoleComponent > ? MindGetRole < T > ( EntityUid mindId ) where T : IComponent
2023-08-30 21:46:11 -07:00
{
2024-10-10 10:48:56 +02:00
Entity < MindRoleComponent > ? result = null ;
var mind = Comp < MindComponent > ( mindId ) ;
2025-08-21 03:10:07 +02:00
foreach ( var uid in mind . MindRoleContainer . ContainedEntities )
2024-10-10 10:48:56 +02:00
{
if ( HasComp < T > ( uid ) & & TryComp < MindRoleComponent > ( uid , out var comp ) )
2025-08-21 03:10:07 +02:00
result = ( uid , comp ) ;
2024-10-10 10:48:56 +02:00
}
return result ;
2023-08-30 21:46:11 -07:00
}
2024-10-10 10:48:56 +02:00
/// <summary>
/// Reads all Roles of a mind Entity and returns their data as RoleInfo
/// </summary>
2024-10-14 16:05:25 +13:00
/// <param name="mind">The mind entity</param>
2024-10-10 10:48:56 +02:00
/// <returns>RoleInfo list</returns>
2024-10-14 16:05:25 +13:00
public List < RoleInfo > MindGetAllRoleInfo ( Entity < MindComponent ? > mind )
2024-10-10 10:48:56 +02:00
{
var roleInfo = new List < RoleInfo > ( ) ;
2024-10-14 16:05:25 +13:00
if ( ! Resolve ( mind . Owner , ref mind . Comp ) )
2024-10-10 10:48:56 +02:00
return roleInfo ;
2025-08-21 03:10:07 +02:00
foreach ( var role in mind . Comp . MindRoleContainer . ContainedEntities )
2024-10-10 10:48:56 +02:00
{
var valid = false ;
var name = "game-ticker-unknown-role" ;
var prototype = "" ;
2024-10-14 16:05:25 +13:00
string? playTimeTracker = null ;
2024-10-10 10:48:56 +02:00
2024-10-14 16:05:25 +13:00
if ( ! TryComp ( role , out MindRoleComponent ? comp ) )
2024-10-10 10:48:56 +02:00
{
2024-10-14 16:05:25 +13:00
Log . Error ( $"Encountered mind role entity {ToPrettyString(role)} without a {nameof(MindRoleComponent)}" ) ;
continue ;
2024-10-10 10:48:56 +02:00
}
2024-10-14 16:05:25 +13:00
if ( comp . AntagPrototype is not null )
prototype = comp . AntagPrototype ;
2024-10-10 10:48:56 +02:00
if ( comp . JobPrototype is not null & & comp . AntagPrototype is null )
{
prototype = comp . JobPrototype ;
if ( _prototypes . TryIndex ( comp . JobPrototype , out var job ) )
{
playTimeTracker = job . PlayTimeTracker ;
name = job . Name ;
valid = true ;
}
else
{
Log . Error ( $" Mind Role Prototype '{role.Id}' contains invalid Job prototype: '{comp.JobPrototype}'" ) ;
}
}
else if ( comp . AntagPrototype is not null & & comp . JobPrototype is null )
{
prototype = comp . AntagPrototype ;
if ( _prototypes . TryIndex ( comp . AntagPrototype , out var antag ) )
{
name = antag . Name ;
valid = true ;
}
else
{
Log . Error ( $" Mind Role Prototype '{role.Id}' contains invalid Antagonist prototype: '{comp.AntagPrototype}'" ) ;
}
}
else if ( comp . JobPrototype is not null & & comp . AntagPrototype is not null )
{
Log . Error ( $" Mind Role Prototype '{role.Id}' contains both Job and Antagonist prototypes" ) ;
}
if ( valid )
2024-10-14 16:05:25 +13:00
roleInfo . Add ( new RoleInfo ( name , comp . Antag , playTimeTracker , prototype ) ) ;
2024-10-10 10:48:56 +02:00
}
return roleInfo ;
}
/// <summary>
/// Does this mind possess an antagonist role
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <returns>True if the mind possesses any antag roles</returns>
2023-08-30 21:46:11 -07:00
public bool MindIsAntagonist ( EntityUid ? mindId )
{
2024-10-10 10:48:56 +02:00
if ( mindId is null )
2023-08-30 21:46:11 -07:00
return false ;
2024-10-14 16:05:25 +13:00
return CheckAntagonistStatus ( mindId . Value ) . Antag ;
2023-08-30 21:46:11 -07:00
}
Refactor antag rule code (#23445)
* Initial Pass, Rev, Thief
* Zombie initial pass
* Rebase, Traitor
* Nukeops, More overloads
* Revert RevolutionaryRuleComponent
* Use TryRoundStartAttempt, Rewrite nukie spawning
* Comments, Add task scheduler to GameRuleSystem
* Zombie initial testing done
* Sort methods, rework GameRuleTask
* Add CCVar, Initial testing continues
* Might as well get rid of the obsolete logging
* Oops, i dont know how to log apparently
* Suggested formatting fixes
* Suggested changes
* Fix merge issues
* Minor optimisation
* Allowed thief to choose other antags
* Review changes
* Spawn items on floor first, then inserting
* minor tweaks
* Shift as much as possible to ProtoId<>
* Remove unneeded
* Add exclusive antag attribute
* Fix merge issues
* Minor formatting fix
* Convert to struct
* Cleanup
* Review cleanup (need to test a lot)
* Some fixes, (mostly) tested
* oop
* Pass tests (for real)
---------
Co-authored-by: Rainfall <rainfey0+git@gmail.com>
Co-authored-by: AJCM <AJCM@tutanota.com>
2024-02-29 06:25:10 +00:00
/// <summary>
/// Does this mind possess an exclusive antagonist role
/// </summary>
/// <param name="mindId">The mind entity</param>
2024-10-10 10:48:56 +02:00
/// <returns>True if the mind possesses any exclusive antag roles</returns>
Refactor antag rule code (#23445)
* Initial Pass, Rev, Thief
* Zombie initial pass
* Rebase, Traitor
* Nukeops, More overloads
* Revert RevolutionaryRuleComponent
* Use TryRoundStartAttempt, Rewrite nukie spawning
* Comments, Add task scheduler to GameRuleSystem
* Zombie initial testing done
* Sort methods, rework GameRuleTask
* Add CCVar, Initial testing continues
* Might as well get rid of the obsolete logging
* Oops, i dont know how to log apparently
* Suggested formatting fixes
* Suggested changes
* Fix merge issues
* Minor optimisation
* Allowed thief to choose other antags
* Review changes
* Spawn items on floor first, then inserting
* minor tweaks
* Shift as much as possible to ProtoId<>
* Remove unneeded
* Add exclusive antag attribute
* Fix merge issues
* Minor formatting fix
* Convert to struct
* Cleanup
* Review cleanup (need to test a lot)
* Some fixes, (mostly) tested
* oop
* Pass tests (for real)
---------
Co-authored-by: Rainfall <rainfey0+git@gmail.com>
Co-authored-by: AJCM <AJCM@tutanota.com>
2024-02-29 06:25:10 +00:00
public bool MindIsExclusiveAntagonist ( EntityUid ? mindId )
{
2024-10-10 10:48:56 +02:00
if ( mindId is null )
Refactor antag rule code (#23445)
* Initial Pass, Rev, Thief
* Zombie initial pass
* Rebase, Traitor
* Nukeops, More overloads
* Revert RevolutionaryRuleComponent
* Use TryRoundStartAttempt, Rewrite nukie spawning
* Comments, Add task scheduler to GameRuleSystem
* Zombie initial testing done
* Sort methods, rework GameRuleTask
* Add CCVar, Initial testing continues
* Might as well get rid of the obsolete logging
* Oops, i dont know how to log apparently
* Suggested formatting fixes
* Suggested changes
* Fix merge issues
* Minor optimisation
* Allowed thief to choose other antags
* Review changes
* Spawn items on floor first, then inserting
* minor tweaks
* Shift as much as possible to ProtoId<>
* Remove unneeded
* Add exclusive antag attribute
* Fix merge issues
* Minor formatting fix
* Convert to struct
* Cleanup
* Review cleanup (need to test a lot)
* Some fixes, (mostly) tested
* oop
* Pass tests (for real)
---------
Co-authored-by: Rainfall <rainfey0+git@gmail.com>
Co-authored-by: AJCM <AJCM@tutanota.com>
2024-02-29 06:25:10 +00:00
return false ;
2024-10-14 16:05:25 +13:00
return CheckAntagonistStatus ( mindId . Value ) . ExclusiveAntag ;
Refactor antag rule code (#23445)
* Initial Pass, Rev, Thief
* Zombie initial pass
* Rebase, Traitor
* Nukeops, More overloads
* Revert RevolutionaryRuleComponent
* Use TryRoundStartAttempt, Rewrite nukie spawning
* Comments, Add task scheduler to GameRuleSystem
* Zombie initial testing done
* Sort methods, rework GameRuleTask
* Add CCVar, Initial testing continues
* Might as well get rid of the obsolete logging
* Oops, i dont know how to log apparently
* Suggested formatting fixes
* Suggested changes
* Fix merge issues
* Minor optimisation
* Allowed thief to choose other antags
* Review changes
* Spawn items on floor first, then inserting
* minor tweaks
* Shift as much as possible to ProtoId<>
* Remove unneeded
* Add exclusive antag attribute
* Fix merge issues
* Minor formatting fix
* Convert to struct
* Cleanup
* Review cleanup (need to test a lot)
* Some fixes, (mostly) tested
* oop
* Pass tests (for real)
---------
Co-authored-by: Rainfall <rainfey0+git@gmail.com>
Co-authored-by: AJCM <AJCM@tutanota.com>
2024-02-29 06:25:10 +00:00
}
2025-08-21 03:10:07 +02:00
private ( bool Antag , bool ExclusiveAntag ) CheckAntagonistStatus ( Entity < MindComponent ? > mind )
{
if ( ! Resolve ( mind . Owner , ref mind . Comp ) )
return ( false , false ) ;
2023-11-14 12:52:40 +00:00
2024-10-10 10:48:56 +02:00
var antagonist = false ;
var exclusiveAntag = false ;
2025-08-21 03:10:07 +02:00
foreach ( var role in mind . Comp . MindRoleContainer . ContainedEntities )
2024-10-10 10:48:56 +02:00
{
2024-10-11 21:17:01 +02:00
if ( ! TryComp < MindRoleComponent > ( role , out var roleComp ) )
{
2024-10-14 16:05:25 +13:00
Log . Error ( $"Mind Role Entity {ToPrettyString(role)} does not have a MindRoleComponent, despite being listed as a role belonging to {ToPrettyString(mind)}|" ) ;
2024-10-11 21:17:01 +02:00
continue ;
}
2024-10-14 16:05:25 +13:00
antagonist | = roleComp . Antag ;
exclusiveAntag | = roleComp . ExclusiveAntag ;
2024-10-10 10:48:56 +02:00
}
return ( antagonist , exclusiveAntag ) ;
2024-04-24 21:31:45 -04:00
}
2023-11-14 12:52:40 +00:00
/// <summary>
/// Play a sound for the mind, if it has a session attached.
/// Use this for role greeting sounds.
/// </summary>
public void MindPlaySound ( EntityUid mindId , SoundSpecifier ? sound , MindComponent ? mind = null )
{
2025-04-19 00:23:01 +02:00
if ( ! Resolve ( mindId , ref mind ) )
return ;
if ( Player . TryGetSessionById ( mind . UserId , out var session ) )
_audio . PlayGlobal ( sound , session ) ;
2023-11-14 12:52:40 +00:00
}
2024-06-08 04:43:02 +12:00
2024-10-14 16:05:25 +13:00
// TODO ROLES Change to readonly.
// Passing around a reference to a prototype's hashset makes me uncomfortable because it might be accidentally
// mutated.
2024-06-08 04:43:02 +12:00
public HashSet < JobRequirement > ? GetJobRequirement ( JobPrototype job )
{
if ( _requirementOverride ! = null & & _requirementOverride . Jobs . TryGetValue ( job . ID , out var req ) )
return req ;
return job . Requirements ;
}
2024-10-14 16:05:25 +13:00
// TODO ROLES Change to readonly.
2024-06-08 04:43:02 +12:00
public HashSet < JobRequirement > ? GetJobRequirement ( ProtoId < JobPrototype > job )
{
if ( _requirementOverride ! = null & & _requirementOverride . Jobs . TryGetValue ( job , out var req ) )
return req ;
return _prototypes . Index ( job ) . Requirements ;
}
2024-10-14 16:05:25 +13:00
// TODO ROLES Change to readonly.
2024-06-08 04:43:02 +12:00
public HashSet < JobRequirement > ? GetAntagRequirement ( ProtoId < AntagPrototype > antag )
{
if ( _requirementOverride ! = null & & _requirementOverride . Antags . TryGetValue ( antag , out var req ) )
return req ;
return _prototypes . Index ( antag ) . Requirements ;
}
2024-10-14 16:05:25 +13:00
// TODO ROLES Change to readonly.
2024-06-08 04:43:02 +12:00
public HashSet < JobRequirement > ? GetAntagRequirement ( AntagPrototype antag )
{
if ( _requirementOverride ! = null & & _requirementOverride . Antags . TryGetValue ( antag . ID , out var req ) )
return req ;
return antag . Requirements ;
}
2025-04-16 19:04:48 +02:00
/// <summary>
/// Returns the localized name of a role type's subtype. If the provided subtype parameter turns out to be empty, it returns the localized name of the role type instead.
/// </summary>
public string GetRoleSubtypeLabel ( LocId roleType , LocId ? subtype )
{
return string . IsNullOrEmpty ( subtype ) ? Loc . GetString ( roleType ) : Loc . GetString ( subtype ) ;
}
2023-08-30 21:46:11 -07:00
}
2025-01-11 22:17:26 +01:00
/// <summary>
/// Raised on the client to update Role Type on the character window, in case it happened to be open.
/// </summary>
[Serializable, NetSerializable]
public sealed class MindRoleTypeChangedEvent : EntityEventArgs
{
}