Role Types (#33420)

* mindcomponent namespace

* wip MindRole stuff

* admin player tab

* mindroletype comment

* mindRolePrototype redesign

* broken param

* wip RoleType implementation

* basic role type switching for antags

* traitor fix

* fix AdminPanel update

* the renameningTM

* cleanup

* feature uncreeping

* roletypes on mind roles

* update MindComponent.RoleType when MindRoles change

* ghostrole configuration

* ghostrole config improvements

* live update of roleType on the character window

* logging stuff and notes

* remove thing no one asked for

* weh

* Mind Role Entities wip

* headrev count fix

* silicon stuff, cleanup

* exclusive antag config, cleanup

* jobroleadd overwerite

* logging stuff

* MindHasRole cleanup, admin log stuff

* last second cleanup

* ocd

* move roletypeprototype to its own file, minor note stuff

* remove Roletype.Created

* log stuff

* roletype setup for ghostroles and autotraitor reinforcements

* ghostrole type configs

* adjustable admin overlay

* cleanup

* fix this in its own PR

* silicon antagonist

* borg stuff

* mmi roletype handling

* spawnable borg roletype handling

* weh

* ghost role cleanup

* weh

* RoleEvent update

* polish

* log stuff

* admin overlay config

* ghostrolecomponent cleanup

* weh

* admin overlay code cleanup

* minor cleanup

* Obsolete MindRoleAddedEvent

* comment

* minor code cleanup

* MindOnDoGreeting fix

* Role update message

* fix duplicate job greeting for cyborgs

* fix emag job message dupe

* nicer-looking role type update

* crew aligned

* syndicate assault borg role fix

* fix test fail

* fix a merge mistake

* fix LoneOp role type

* Update Content.Client/Administration/AdminNameOverlay.cs

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* Update Content.Shared/Roles/SharedRoleSystem.cs

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* comment formatting

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* change logging category

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* fix a space

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* use MindAddRoles

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* get MindComponent from TryGetMind

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* move var declaration outside loop

* remove TryComp

* take RoleEnum behind the barn

* don't use ensurecomp unnecessarily

* cvar comments

* toggleableghostrolecomponent documentation

* skrek

* use EntProtoId

* mindrole config

* merge baserolecomponent into basemindrolecomponent

* ai and borg silicon role tweaks

* formatting

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* I will end you (the color)

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* use LocId type for a locale id

* update RoleEvent documentation

* update RoleEvent documentation

* remove obsolete MindRoleAddedEvent

* refine MindRolesUpdate()

* use dependency

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* inject dependency

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

* roleType.Name no longer required

* reformatted draw code logic

* GhostRoleMarkerRoleComponent comment

* minor SharedRoleSystem cleanup

* StartingMindRoleComponent, unhardcode roundstart silicon

* Update Content.Shared/Roles/SharedRoleSystem.cs

* remove a whitespace

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
This commit is contained in:
Errant
2025-01-11 22:17:26 +01:00
committed by GitHub
parent 3da354304d
commit 46d58bf22a
65 changed files with 744 additions and 106 deletions

View File

@@ -17,7 +17,6 @@ using Content.Shared.IdentityManagement;
using Content.Shared.Inventory;
using Content.Shared.Mind;
using Content.Shared.PDA;
using Content.Shared.Players;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Popups;
using Content.Shared.Roles;
@@ -32,6 +31,7 @@ using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server.Administration.Systems;
@@ -48,6 +48,7 @@ public sealed class AdminSystem : EntitySystem
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly PhysicsSystem _physics = default!;
[Dependency] private readonly PlayTimeTrackingManager _playTime = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly SharedRoleSystem _role = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
@@ -165,7 +166,8 @@ public sealed class AdminSystem : EntitySystem
private void OnRoleEvent(RoleEvent ev)
{
var session = _minds.GetSession(ev.Mind);
if (!ev.Antagonist || session == null)
if (!ev.RoleTypeUpdate || session == null)
return;
UpdatePlayerList(session);
@@ -239,9 +241,16 @@ public sealed class AdminSystem : EntitySystem
}
var antag = false;
RoleTypePrototype roleType = new();
var startingRole = string.Empty;
if (_minds.TryGetMind(session, out var mindId, out _))
if (_minds.TryGetMind(session, out var mindId, out var mindComp))
{
if (_proto.TryIndex(mindComp.RoleType, out var role))
roleType = role;
else
Log.Error($"{ToPrettyString(mindId)} has invalid Role Type '{mindComp.RoleType}'. Displaying '{Loc.GetString(roleType.Name)}' instead");
antag = _role.MindIsAntagonist(mindId);
startingRole = _jobs.MindTryGetJobName(mindId);
}
@@ -255,7 +264,7 @@ public sealed class AdminSystem : EntitySystem
overallPlaytime = playTime;
}
return new PlayerInfo(name, entityName, identityName, startingRole, antag, GetNetEntity(session?.AttachedEntity), data.UserId,
return new PlayerInfo(name, entityName, identityName, startingRole, antag, roleType, GetNetEntity(session?.AttachedEntity), data.UserId,
connected, _roundActivePlayers.Contains(data.UserId), overallPlaytime);
}

View File

@@ -153,7 +153,7 @@ public partial struct AntagSelectionDefinition()
/// List of Mind Role Prototypes to be added to the player's mind.
/// </summary>
[DataField]
public List<ProtoId<EntityPrototype>>? MindRoles;
public List<EntProtoId>? MindRoles;
/// <summary>
/// A set of starting gear that's equipped to the player.

View File

@@ -222,8 +222,6 @@ namespace Content.Server.GameTicking
_mind.SetUserId(newMind, data.UserId);
var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId);
_roles.MindAddJobRole(newMind, silent: silent, jobPrototype:jobId);
var jobName = _jobs.MindTryGetJobName(newMind);
_playTimeTrackings.PlayerRolesChanged(player);
@@ -233,6 +231,9 @@ namespace Content.Server.GameTicking
_mind.TransferTo(newMind, mob);
_roles.MindAddJobRole(newMind, silent: silent, jobPrototype:jobId);
var jobName = _jobs.MindTryGetJobName(newMind);
if (lateJoin && !silent)
{
if (jobPrototype.JoinNotifyCrew)

View File

@@ -1,10 +1,12 @@
namespace Content.Server.Ghost;
using Content.Shared.Roles;
namespace Content.Server.Ghost;
/// <summary>
/// This is used to mark Observers properly, as they get Minds
/// </summary>
[RegisterComponent]
public sealed partial class ObserverRoleComponent : Component
public sealed partial class ObserverRoleComponent : BaseMindRoleComponent
{
public string Name => Loc.GetString("observer-role-name");
}

View File

@@ -72,12 +72,16 @@ public sealed partial class GhostRoleComponent : Component
}
}
[DataField("allowSpeech")]
[ViewVariables(VVAccess.ReadWrite)]
/// <summary>
/// The mind roles that will be added to the mob's mind entity
/// </summary>
[DataField, Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // Don't make eye contact
public List<EntProtoId> MindRoles = new() { "MindRoleGhostRoleNeutral" };
[DataField]
public bool AllowSpeech { get; set; } = true;
[DataField("allowMovement")]
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public bool AllowMovement { get; set; }
[ViewVariables(VVAccess.ReadOnly)]
@@ -107,3 +111,4 @@ public sealed partial class GhostRoleComponent : Component
[Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // also FIXME Friends
public ProtoId<JobPrototype>? JobProto = null;
}

View File

@@ -9,39 +9,81 @@ namespace Content.Server.Ghost.Roles.Components;
[RegisterComponent, Access(typeof(ToggleableGhostRoleSystem))]
public sealed partial class ToggleableGhostRoleComponent : Component
{
[DataField("examineTextMindPresent")]
/// <summary>
/// The text shown on the entity's Examine when it is controlled by a player
/// </summary>
[DataField]
public string ExamineTextMindPresent = string.Empty;
[DataField("examineTextMindSearching")]
/// <summary>
/// The text shown on the entity's Examine when it is waiting for a controlling player
/// </summary>
[DataField]
public string ExamineTextMindSearching = string.Empty;
[DataField("examineTextNoMind")]
/// <summary>
/// The text shown on the entity's Examine when it has no controlling player
/// </summary>
[DataField]
public string ExamineTextNoMind = string.Empty;
[DataField("beginSearchingText")]
/// <summary>
/// The popup text when the entity (PAI/positronic brain) it is activated to seek a controlling player
/// </summary>
[DataField]
public string BeginSearchingText = string.Empty;
[DataField("roleName")]
/// <summary>
/// The name shown on the Ghost Role list
/// </summary>
[DataField]
public string RoleName = string.Empty;
[DataField("roleDescription")]
/// <summary>
/// The description shown on the Ghost Role list
/// </summary>
[DataField]
public string RoleDescription = string.Empty;
[DataField("roleRules")]
/// <summary>
/// The introductory message shown when trying to take the ghost role/join the raffle
/// </summary>
[DataField]
public string RoleRules = string.Empty;
[DataField("wipeVerbText")]
/// <summary>
/// A list of mind roles that will be added to the entity's mind
/// </summary>
[DataField]
public List<EntProtoId> MindRoles;
/// <summary>
/// The displayed name of the verb to wipe the controlling player
/// </summary>
[DataField]
public string WipeVerbText = string.Empty;
[DataField("wipeVerbPopup")]
/// /// <summary>
/// The popup message when wiping the controlling player
/// </summary>
[DataField]
public string WipeVerbPopup = string.Empty;
[DataField("stopSearchVerbText")]
/// <summary>
/// The displayed name of the verb to stop searching for a controlling player
/// </summary>
[DataField]
public string StopSearchVerbText = string.Empty;
[DataField("stopSearchVerbPopup")]
/// /// <summary>
/// The popup message when stopping to search for a controlling player
/// </summary>
[DataField]
public string StopSearchVerbPopup = string.Empty;
/// /// <summary>
/// The prototype ID of the job that will be given to the controlling mind
/// </summary>
[DataField("job")]
public ProtoId<JobPrototype>? JobProto = null;
public ProtoId<JobPrototype>? JobProto;
}

View File

@@ -3,11 +3,13 @@
namespace Content.Server.Ghost.Roles;
/// <summary>
/// This is used for round end display of ghost roles.
/// It may also be used to ensure some ghost roles count as antagonists in future.
/// Added to mind role entities to tag that they are a ghostrole.
/// It also holds the name for the round end display
/// </summary>
[RegisterComponent]
public sealed partial class GhostRoleMarkerRoleComponent : BaseMindRoleComponent
{
[DataField("name")] public string? Name;
//TODO does anything still use this? It gets populated by GhostRolesystem but I don't see anything ever reading it
[DataField] public string? Name;
}

View File

@@ -33,7 +33,6 @@ using Content.Server.Popups;
using Content.Shared.Verbs;
using Robust.Shared.Collections;
using Content.Shared.Ghost.Roles.Components;
using Content.Shared.Roles.Jobs;
namespace Content.Server.Ghost.Roles;
@@ -514,13 +513,13 @@ public sealed class GhostRoleSystem : EntitySystem
var newMind = _mindSystem.CreateMind(player.UserId,
EntityManager.GetComponent<MetaDataComponent>(mob).EntityName);
_roleSystem.MindAddRole(newMind, "MindRoleGhostMarker");
if(_roleSystem.MindHasRole<GhostRoleMarkerRoleComponent>(newMind!, out var markerRole))
markerRole.Value.Comp2.Name = role.RoleName;
_mindSystem.SetUserId(newMind, player.UserId);
_mindSystem.TransferTo(newMind, mob);
_roleSystem.MindAddRoles(newMind.Owner, role.MindRoles, newMind.Comp);
if (_roleSystem.MindHasRole<GhostRoleMarkerRoleComponent>(newMind!, out var markerRole))
markerRole.Value.Comp2.Name = role.RoleName;
}
/// <summary>

View File

@@ -51,10 +51,13 @@ public sealed class ToggleableGhostRoleSystem : EntitySystem
var ghostRole = EnsureComp<GhostRoleComponent>(uid);
EnsureComp<GhostTakeoverAvailableComponent>(uid);
//GhostRoleComponent inherits custom settings from the ToggleableGhostRoleComponent
ghostRole.RoleName = Loc.GetString(component.RoleName);
ghostRole.RoleDescription = Loc.GetString(component.RoleDescription);
ghostRole.RoleRules = Loc.GetString(component.RoleRules);
ghostRole.JobProto = component.JobProto;
ghostRole.MindRoles = component.MindRoles;
}
private void OnExamined(EntityUid uid, ToggleableGhostRoleComponent component, ExaminedEvent args)

View File

@@ -49,8 +49,8 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundEnd);
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<RoleAddedEvent>(OnRoleAdd);
SubscribeLocalEvent<RoleRemovedEvent>(OnRoleRemove);
SubscribeLocalEvent<RoleAddedEvent>(OnRoleEvent);
SubscribeLocalEvent<RoleRemovedEvent>(OnRoleEvent);
SubscribeLocalEvent<AFKEvent>(OnAFK);
SubscribeLocalEvent<UnAFKEvent>(OnUnAFK);
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
@@ -121,13 +121,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
return GetTimedRoles(contentData.Mind.Value);
}
private void OnRoleRemove(RoleRemovedEvent ev)
{
if (_minds.TryGetSession(ev.Mind, out var session))
_tracking.QueueRefreshTrackers(session);
}
private void OnRoleAdd(RoleAddedEvent ev)
private void OnRoleEvent(RoleEvent ev)
{
if (_minds.TryGetSession(ev.Mind, out var session))
_tracking.QueueRefreshTrackers(session);

View File

@@ -19,10 +19,25 @@ public sealed class JobSystem : SharedJobSystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MindComponent, MindRoleAddedEvent>(MindOnDoGreeting);
SubscribeLocalEvent<RoleAddedEvent>(OnRoleAddedEvent);
SubscribeLocalEvent<RoleRemovedEvent>(OnRoleRemovedEvent);
}
private void MindOnDoGreeting(EntityUid mindId, MindComponent component, ref MindRoleAddedEvent args)
private void OnRoleAddedEvent(RoleAddedEvent args)
{
MindOnDoGreeting(args.MindId, args.Mind, args);
if (args.RoleTypeUpdate)
_roles.RoleUpdateMessage(args.Mind);
}
private void OnRoleRemovedEvent(RoleRemovedEvent args)
{
if (args.RoleTypeUpdate)
_roles.RoleUpdateMessage(args.Mind);
}
private void MindOnDoGreeting(EntityUid mindId, MindComponent component, RoleAddedEvent args)
{
if (args.Silent)
return;

View File

@@ -1,11 +1,13 @@
namespace Content.Server.Roles;
using Content.Shared.Roles;
namespace Content.Server.Roles;
/// <summary>
/// Adds a briefing to the character info menu, does nothing else.
/// </summary>
[RegisterComponent]
public sealed partial class RoleBriefingComponent : Component
public sealed partial class RoleBriefingComponent : BaseMindRoleComponent
{
[DataField("briefing"), ViewVariables(VVAccess.ReadWrite)]
[DataField]
public string Briefing;
}

View File

@@ -1,10 +1,16 @@
using Content.Server.Chat.Managers;
using Content.Shared.Chat;
using Content.Shared.Mind;
using Content.Shared.Roles;
using Robust.Shared.Prototypes;
namespace Content.Server.Roles;
public sealed class RoleSystem : SharedRoleSystem
{
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public string? MindGetBriefing(EntityUid? mindId)
{
if (mindId == null)
@@ -37,6 +43,32 @@ public sealed class RoleSystem : SharedRoleSystem
return ev.Briefing;
}
public void RoleUpdateMessage(MindComponent mind)
{
if (mind.Session is null)
return;
if (!_proto.TryIndex(mind.RoleType, out var proto))
return;
var roleText = Loc.GetString(proto.Name);
var color = proto.Color;
var session = mind.Session;
//TODO add audio? Would need to be optional so it does not play on role changes that already come with their own audio
// _audio.PlayGlobal(Sound, session);
var message = Loc.GetString("role-type-update-message", ("color", color), ("role", roleText));
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
_chat.ChatMessageToOne(ChatChannel.Server,
message,
wrappedMessage,
default,
false,
session.Channel);
}
}
/// <summary>

View File

@@ -1,5 +1,7 @@
using Content.Shared.Containers.ItemSlots;
using Content.Server.Roles;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Mind.Components;
using Content.Shared.Roles;
using Content.Shared.Silicons.Borgs.Components;
using Robust.Shared.Containers;
@@ -8,6 +10,9 @@ namespace Content.Server.Silicons.Borgs;
/// <inheritdoc/>
public sealed partial class BorgSystem
{
[Dependency] private readonly SharedRoleSystem _roles = default!;
public void InitializeMMI()
{
SubscribeLocalEvent<MMIComponent, ComponentInit>(OnMMIInit);
@@ -41,8 +46,13 @@ public sealed partial class BorgSystem
Dirty(uid, component);
if (_mind.TryGetMind(ent, out var mindId, out var mind))
{
_mind.TransferTo(mindId, uid, true, mind: mind);
if (!_roles.MindHasRole<SiliconBrainRoleComponent>(mindId))
_roles.MindAddRole(mindId, "MindRoleSiliconBrain", silent: true);
}
_appearance.SetData(uid, MMIVisuals.BrainPresent, true);
}
@@ -75,7 +85,12 @@ public sealed partial class BorgSystem
RemComp(uid, component);
if (_mind.TryGetMind(linked, out var mindId, out var mind))
{
if (_roles.MindHasRole<SiliconBrainRoleComponent>(mindId))
_roles.MindRemoveRole<SiliconBrainRoleComponent>(mindId);
_mind.TransferTo(mindId, uid, true, mind: mind);
}
_appearance.SetData(linked, MMIVisuals.BrainPresent, false);
}

View File

@@ -5,7 +5,6 @@ using Content.Server.DeviceNetwork.Systems;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Hands.Systems;
using Content.Server.PowerCell;
using Content.Shared.Access.Systems;
using Content.Shared.Alert;
using Content.Shared.Database;
using Content.Shared.IdentityManagement;

View File

@@ -193,7 +193,7 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
private void EnsureSubvertedSiliconRole(EntityUid mindId)
{
if (!_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId))
_roles.MindAddRole(mindId, "MindRoleSubvertedSilicon");
_roles.MindAddRole(mindId, "MindRoleSubvertedSilicon", silent: true);
}
private void RemoveSubvertedSiliconRole(EntityUid mindId)