Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Traitor flavor (take 2) #28983

Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ public sealed partial class TraitorRuleComponent : Component
public ProtoId<DatasetPrototype> CodewordVerbs = "verbs";

[DataField]
public ProtoId<DatasetPrototype> ObjectiveIssuers = "TraitorCorporations";
public ProtoId<DatasetPrototype> ObjectiveIssuers = "TraitorFlavor";

public int TotalTraitors => TraitorMinds.Count;
public string[] Codewords = new string[3];
public string ObjectiveIssuer = string.Empty;

public enum SelectionState
{
Expand Down
54 changes: 44 additions & 10 deletions Content.Server/GameTicking/Rules/TraitorRuleSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public override void Initialize()

SubscribeLocalEvent<TraitorRuleComponent, AfterAntagEntitySelectedEvent>(AfterEntitySelected);

SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextPrependEvent>(OnObjectivesTextPrepend);
}

Expand Down Expand Up @@ -72,6 +73,7 @@ public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool

var briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", component.Codewords)));
var issuer = _random.Pick(_prototypeManager.Index(component.ObjectiveIssuers).Values);
component.ObjectiveIssuer = issuer;

Note[]? code = null;
if (giveUplink)
Expand All @@ -89,20 +91,16 @@ public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool

// Give traitors their codewords and uplink code to keep in their character info menu
code = EnsureComp<RingerUplinkComponent>(pda.Value).Code;

// If giveUplink is false the uplink code part is omitted
briefing = string.Format("{0}\n{1}", briefing,
Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("-", code).Replace("sharp", "#"))));
}

_antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code, issuer), null, component.GreetSoundNotification);
_antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code, issuer), Color.Crimson, component.GreetSoundNotification);

component.TraitorMinds.Add(mindId);

// Assign briefing
_roleSystem.MindAddRole(mindId, new RoleBriefingComponent
{
Briefing = briefing
Briefing = GenerateBriefingCharacter(component.Codewords, code, issuer)
}, mind, true);

// Change the faction
Expand All @@ -112,20 +110,56 @@ public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool
return true;
}

private void OnObjectivesTextGetInfo(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextGetInfoEvent args)
{
args.Minds = comp.TraitorMinds.Select(mindId => (mindId, Comp<MindComponent>(mindId).CharacterName ?? "?")).ToList();
args.AgentName = Loc.GetString($"traitor-{comp.ObjectiveIssuer}-roundend");
args.Faction = Loc.GetString($"traitor-round-end-agent-name");
}

// TODO: AntagCodewordsComponent
private void OnObjectivesTextPrepend(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextPrependEvent args)
{
args.Text += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", comp.Codewords)));
}

// TODO: figure out how to handle this? add priority to briefing event?
private string GenerateBriefing(string[] codewords, Note[]? uplinkCode, string? objectiveIssuer = null)
private string GenerateBriefing(string[] codewords, Note[]? uplinkCode, string objectiveIssuer)
{
var sb = new StringBuilder();
sb.AppendLine("\n" + Loc.GetString($"traitor-{objectiveIssuer}-intro"));

if (uplinkCode != null)
{
sb.AppendLine("\n" + Loc.GetString($"traitor-{objectiveIssuer}-uplink"));
sb.AppendLine(Loc.GetString($"traitor-role-uplink-code", ("code", string.Join("-", uplinkCode).Replace("sharp", "#"))));
}
else sb.AppendLine("\n" + Loc.GetString($"traitor-role-nouplink"));

sb.AppendLine("\n" + Loc.GetString($"traitor-role-codewords", ("codewords", string.Join(", ", codewords))));

sb.AppendLine("\n" + Loc.GetString($"traitor-role-moreinfo"));

return sb.ToString();
}
private string GenerateBriefingCharacter(string[] codewords, Note[]? uplinkCode, string objectiveIssuer)
{
var sb = new StringBuilder();
sb.AppendLine(Loc.GetString("traitor-role-greeting", ("corporation", objectiveIssuer ?? Loc.GetString("objective-issuer-unknown"))));
sb.AppendLine(Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", codewords))));
sb.AppendLine("\n" + Loc.GetString($"traitor-{objectiveIssuer}-intro"));

if (uplinkCode != null)
sb.AppendLine(Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("-", uplinkCode).Replace("sharp", "#"))));
sb.AppendLine(Loc.GetString($"traitor-role-uplink-code-short", ("code", string.Join("-", uplinkCode).Replace("sharp", "#"))));
else sb.AppendLine("\n" + Loc.GetString($"traitor-role-nouplink"));

sb.AppendLine(Loc.GetString($"traitor-role-codewords-short", ("codewords", string.Join(", ", codewords))));

sb.AppendLine("\n" + Loc.GetString($"traitor-role-allegiances"));
sb.AppendLine(Loc.GetString($"traitor-{objectiveIssuer}-allies"));

sb.AppendLine("\n" + Loc.GetString($"traitor-role-notes"));
sb.AppendLine(Loc.GetString($"traitor-{objectiveIssuer}-goal"));

sb.AppendLine("\n" + Loc.GetString($"traitor-role-clarity"));

return sb.ToString();
}
Expand Down
70 changes: 39 additions & 31 deletions Content.Server/Objectives/ObjectivesSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Linq;
using System.Text;
using Robust.Server.Player;
using Robust.Shared.Containers;

namespace Content.Server.Objectives;

Expand All @@ -36,7 +37,7 @@ public override void Initialize()
private void OnRoundEndText(RoundEndTextAppendEvent ev)
{
// go through each gamerule getting data for the roundend summary.
var summaries = new Dictionary<string, Dictionary<string, List<(EntityUid, string)>>>();
var summaries = new Dictionary<string, Dictionary<string, Dictionary<string, List<(EntityUid, string)>>>>();
var query = EntityQueryEnumerator<GameRuleComponent>();
while (query.MoveNext(out var uid, out var gameRule))
{
Expand All @@ -48,17 +49,21 @@ private void OnRoundEndText(RoundEndTextAppendEvent ev)
if (info.Minds.Count == 0)
continue;

// first group the gamerules by their agents, for example 2 different dragons
var agent = info.AgentName;
// first group the gamerules by their factions, for example 2 different dragons
var agent = info.Faction ?? info.AgentName;
if (!summaries.ContainsKey(agent))
summaries[agent] = new Dictionary<string, List<(EntityUid, string)>>();
summaries[agent] = new Dictionary<string, Dictionary<string, List<(EntityUid, string)>>>();

// next group them by agent names, for example different traitors, blood brother teams, etc.
if (!summaries[agent].ContainsKey(info.AgentName))
summaries[agent][info.AgentName] = new Dictionary<string, List<(EntityUid, string)>>();

var prepend = new ObjectivesTextPrependEvent("");
RaiseLocalEvent(uid, ref prepend);

// next group them by their prepended texts
// for example with traitor rule, group them by the codewords they share
var summary = summaries[agent];
var summary = summaries[agent][info.AgentName];
if (summary.ContainsKey(prepend.Text))
{
// same prepended text (usually empty) so combine them
Expand All @@ -71,36 +76,39 @@ private void OnRoundEndText(RoundEndTextAppendEvent ev)
}

// convert the data into summary text
foreach (var (agent, summary) in summaries)
foreach (var (faction, summariesFaction) in summaries)
{
// first get the total number of players that were in these game rules combined
var total = 0;
var totalInCustody = 0;
foreach (var (_, minds) in summary)
foreach (var (agent, summary) in summariesFaction)
{
total += minds.Count;
totalInCustody += minds.Where(pair => IsInCustody(pair.Item1)).Count();
}
// first get the total number of players that were in these game rules combined
var total = 0;
var totalInCustody = 0;
foreach (var (_, minds) in summary)
{
total += minds.Count;
totalInCustody += minds.Where(m => IsInCustody(m.Item1)).Count();
}

var result = new StringBuilder();
result.AppendLine(Loc.GetString("objectives-round-end-result", ("count", total), ("agent", agent)));
if (agent == Loc.GetString("traitor-round-end-agent-name"))
{
result.AppendLine(Loc.GetString("objectives-round-end-result-in-custody", ("count", total), ("custody", totalInCustody), ("agent", agent)));
}
// next add all the players with its own prepended text
foreach (var (prepend, minds) in summary)
{
if (prepend != string.Empty)
result.Append(prepend);
var result = new StringBuilder();
result.AppendLine(Loc.GetString("objectives-round-end-result", ("count", total), ("agent", faction)));
if (agent == Loc.GetString("traitor-round-end-agent-name"))
{
result.AppendLine(Loc.GetString("objectives-round-end-result-in-custody", ("count", total), ("custody", totalInCustody), ("agent", faction)));
}
// next add all the players with its own prepended text
foreach (var (prepend, minds) in summary)
{
if (prepend != string.Empty)
result.Append(prepend);

// add space between the start text and player list
result.AppendLine();
// add space between the start text and player list
result.AppendLine();

AddSummary(result, agent, minds);
}
AddSummary(result, agent, minds);
}

ev.AddLine(result.AppendLine().ToString());
ev.AddLine(result.AppendLine().ToString());
}
}
}

Expand Down Expand Up @@ -237,7 +245,7 @@ private bool IsInCustody(EntityUid mindId, MindComponent? mind = null)
/// Get the title for a player's mind used in round end.
/// Pass in the original entity name which is shown alongside username.
/// </summary>
public string GetTitle(Entity<MindComponent?> mind, string name)
public string GetTitle(Entity<MindComponent?> mind, string name = "")
{
if (Resolve(mind, ref mind.Comp) &&
mind.Comp.OriginalOwnerUserId != null &&
Expand All @@ -260,7 +268,7 @@ public string GetTitle(Entity<MindComponent?> mind, string name)
/// The objectives system already checks if the game rule is added so you don't need to check that in this event's handler.
/// </remarks>
[ByRefEvent]
public record struct ObjectivesTextGetInfoEvent(List<(EntityUid, string)> Minds, string AgentName);
public record struct ObjectivesTextGetInfoEvent(List<(EntityUid, string)> Minds, string AgentName, string? Faction = null);

/// <summary>
/// Raised on the game rule before text for each agent's objectives is added, letting you prepend something.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,31 @@ traitor-death-match-end-round-description-entry = {$originalName}'s PDA, with {$
## TraitorRole

# TraitorRole
traitor-role-greeting =
You are an agent sent by {$corporation} on behalf of The Syndicate.
Your objectives and codewords are listed in the character menu.
Use the uplink loaded into your PDA to buy the tools you'll need for this mission.
Death to Nanotrasen!
traitor-role-codewords =
The codewords are:
{$codewords}.
Codewords can be used in regular conversation to identify yourself discretely to other syndicate agents.
Listen for them, and keep them secret.

traitor-role-uplink-code =
Set your ringtone to the notes {$code} to lock or unlock your uplink.
Remember to lock it after, or the stations crew will easily open it too!

traitor-role-moreinfo =
Find more information about your role in the character menu.

traitor-role-nouplink =
You do not have a syndicate uplink. Make it count.

traitor-role-allegiances =
Your allegiances:

traitor-role-notes =
Notes from your employer:

traitor-role-clarity =
Allegiances and additional notes are recommendations. You do not have to strictly follow them.

# don't need all the flavour text for character menu
traitor-role-codewords-short =
The codewords are:
Expand Down
6 changes: 3 additions & 3 deletions Resources/Locale/en-US/objectives/round-end.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ objectives-round-end-result-in-custody = {$custody} out of {$count} {MAKEPLURAL(
objectives-player-user-named = [color=White]{$name}[/color] ([color=gray]{$user}[/color])
objectives-player-named = [color=White]{$name}[/color]

objectives-no-objectives = {$custody}{$title} was a {$agent}.
objectives-with-objectives = {$custody}{$title} was a {$agent} who had the following objectives:
objectives-no-objectives = {$custody}{$title} was {$agent}.
objectives-with-objectives = {$custody}{$title} was {$agent} who had the following objectives:

objectives-objective-success = {$objective} | [color={$markupColor}]Success![/color]
objectives-objective-fail = {$objective} | [color={$markupColor}]Failure![/color] ({$progress}%)

objectives-in-custody = [bold][color=red]| IN CUSTODY | [/color][/bold]
objectives-in-custody = [bold][color=red]| IN CUSTODY |[/color][/bold]
8 changes: 4 additions & 4 deletions Resources/Locale/en-US/prototypes/roles/antags.ftl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
roles-antag-syndicate-agent-name = Syndicate agent
roles-antag-syndicate-agent-objective = Complete your objectives without being caught.
roles-antag-traitor-name = Traitor
roles-antag-traitor-objective = Complete your objectives without being caught.

roles-antag-initial-infected-name = Initial Infected
roles-antag-initial-infected-objective = Once you turn, infect as many other crew members as possible.
Expand All @@ -14,13 +14,13 @@ roles-antag-suspicion-suspect-name = Suspect
roles-antag-suspicion-suspect-objective = Kill the innocents.

roles-antag-nuclear-operative-commander-name = Nuclear operative commander
roles-antag-nuclear-operative-commander-objective = Lead your team to the destruction of the station.
roles-antag-nuclear-operative-commander-objective = Lead your team to the destruction of the station!

roles-antag-nuclear-operative-agent-name = Nuclear operative agent
roles-antag-nuclear-operative-agent-objective = Like default operative, the team's treatment will have priority.

roles-antag-nuclear-operative-name = Nuclear operative
roles-antag-nuclear-operative-objective = Find the nuke disk and blow up the station.
roles-antag-nuclear-operative-objective = Find the nuke disk and blow up the station!

roles-antag-subverted-silicon-name = Subverted silicon
roles-antag-subverted-silicon-objective = Follow your new laws and do bad unto the station.
Expand Down
Loading
Loading