using Content.Shared.Dataset;
+using Content.Shared.FixedPoint;
using Content.Shared.NPC.Prototypes;
using Content.Shared.Random;
using Content.Shared.Roles;
[DataField]
public ProtoId<DatasetPrototype> ObjectiveIssuers = "TraitorCorporations";
+ /// <summary>
+ /// Give this traitor an Uplink on spawn.
+ /// </summary>
+ [DataField]
+ public bool GiveUplink = true;
+
+ /// <summary>
+ /// Give this traitor the codewords.
+ /// </summary>
+ [DataField]
+ public bool GiveCodewords = true;
+
+ /// <summary>
+ /// Give this traitor a briefing in chat.
+ /// </summary>
+ [DataField]
+ public bool GiveBriefing = true;
+
public int TotalTraitors => TraitorMinds.Count;
public string[] Codewords = new string[3];
/// The amount of TC traitors start with.
/// </summary>
[DataField]
- public int StartingBalance = 20;
+ public FixedPoint2 StartingBalance = 20;
}
using Content.Server.Roles;
using Content.Server.Traitor.Uplink;
using Content.Shared.Database;
+using Content.Shared.FixedPoint;
using Content.Shared.GameTicking.Components;
using Content.Shared.Mind;
using Content.Shared.NPC.Systems;
return codewords;
}
- public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true)
+ public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component)
{
//Grab the mind if it wasn't provided
if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind))
return false;
- var briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", component.Codewords)));
+ var briefing = "";
+
+ if (component.GiveCodewords)
+ briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", component.Codewords)));
+
var issuer = _random.Pick(_prototypeManager.Index(component.ObjectiveIssuers).Values);
+ // Uplink code will go here if applicable, but we still need the variable if there aren't any
Note[]? code = null;
- if (giveUplink)
+
+ if (component.GiveUplink)
{
// Calculate the amount of currency on the uplink.
var startingBalance = component.StartingBalance;
if (_jobs.MindTryGetJob(mindId, out var prototype))
- startingBalance = Math.Max(startingBalance - prototype.AntagAdvantage, 0);
-
- // creadth: we need to create uplink for the antag.
- // PDA should be in place already
- var pda = _uplink.FindUplinkTarget(traitor);
- if (pda == null || !_uplink.AddUplink(traitor, startingBalance, giveDiscounts: true))
- return false;
-
- // Give traitors their codewords and uplink code to keep in their character info menu
- code = EnsureComp<RingerUplinkComponent>(pda.Value).Code;
+ {
+ if (startingBalance < prototype.AntagAdvantage) // Can't use Math functions on FixedPoint2
+ startingBalance = 0;
+ else
+ startingBalance = startingBalance - prototype.AntagAdvantage;
+ }
- // 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", "#"))));
+ // Choose and generate an Uplink, and return the uplink code if applicable
+ var uplinkParams = RequestUplink(traitor, startingBalance, briefing);
+ code = uplinkParams.Item1;
+ briefing = uplinkParams.Item2;
}
- _antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code, issuer), null, component.GreetSoundNotification);
+ string[]? codewords = null;
+ if (component.GiveCodewords)
+ codewords = component.Codewords;
+
+ if (component.GiveBriefing)
+ _antag.SendBriefing(traitor, GenerateBriefing(codewords, code, issuer), null, component.GreetSoundNotification);
component.TraitorMinds.Add(mindId);
return true;
}
+ private (Note[]?, string) RequestUplink(EntityUid traitor, FixedPoint2 startingBalance, string briefing)
+ {
+ var pda = _uplink.FindUplinkTarget(traitor);
+ Note[]? code = null;
+
+ var uplinked = _uplink.AddUplink(traitor, startingBalance, pda, true);
+
+ if (pda is not null && uplinked)
+ {
+ // Codes are only generated if the uplink is a PDA
+ 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", "#"))));
+ return (code, briefing);
+ }
+ else if (pda is null && uplinked)
+ {
+ briefing += "\n" + Loc.GetString("traitor-role-uplink-implant-short");
+ }
+
+ return (null, briefing);
+ }
+
// TODO: AntagCodewordsComponent
private void OnObjectivesTextPrepend(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextPrependEvent args)
{
}
// 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 = null)
{
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", ("codewords", string.Join(", ", codewords))));
+ if (codewords != null)
+ sb.AppendLine(Loc.GetString("traitor-role-codewords", ("codewords", string.Join(", ", codewords))));
if (uplinkCode != null)
sb.AppendLine(Loc.GetString("traitor-role-uplink-code", ("code", string.Join("-", uplinkCode).Replace("sharp", "#"))));
+ else
+ sb.AppendLine(Loc.GetString("traitor-role-uplink-implant"));
+
return sb.ToString();
}
using Content.Server.Traitor.Systems;
+using Robust.Shared.Prototypes;
namespace Content.Server.Traitor.Components;
public sealed partial class AutoTraitorComponent : Component
{
/// <summary>
- /// Whether to give the traitor an uplink or not.
+ /// The traitor profile to use
/// </summary>
- [DataField("giveUplink"), ViewVariables(VVAccess.ReadWrite)]
- public bool GiveUplink = true;
-
- /// <summary>
- /// Whether to give the traitor objectives or not.
- /// </summary>
- [DataField("giveObjectives"), ViewVariables(VVAccess.ReadWrite)]
- public bool GiveObjectives = true;
+ [DataField]
+ public EntProtoId Profile = "Traitor";
}
{
[Dependency] private readonly AntagSelectionSystem _antag = default!;
- [ValidatePrototypeId<EntityPrototype>]
- private const string DefaultTraitorRule = "Traitor";
-
public override void Initialize()
{
base.Initialize();
private void OnMindAdded(EntityUid uid, AutoTraitorComponent comp, MindAddedMessage args)
{
- _antag.ForceMakeAntag<AutoTraitorComponent>(args.Mind.Comp.Session, DefaultTraitorRule);
+ _antag.ForceMakeAntag<AutoTraitorComponent>(args.Mind.Comp.Session, comp.Profile);
}
}
using System.Linq;
using Content.Server.Store.Systems;
using Content.Server.StoreDiscount.Systems;
+using Content.Shared.FixedPoint;
using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Implants;
using Content.Shared.Inventory;
using Content.Shared.PDA;
-using Content.Shared.FixedPoint;
using Content.Shared.Store;
using Content.Shared.Store.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Traitor.Uplink;
-namespace Content.Server.Traitor.Uplink
+public sealed class UplinkSystem : EntitySystem
{
- public sealed class UplinkSystem : EntitySystem
+ [Dependency] private readonly InventorySystem _inventorySystem = default!;
+ [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly StoreSystem _store = default!;
+ [Dependency] private readonly SharedSubdermalImplantSystem _subdermalImplant = default!;
+
+ [ValidatePrototypeId<CurrencyPrototype>]
+ public const string TelecrystalCurrencyPrototype = "Telecrystal";
+ private const string FallbackUplinkImplant = "UplinkImplant";
+ private const string FallbackUplinkCatalog = "UplinkUplinkImplanter";
+
+ /// <summary>
+ /// Adds an uplink to the target
+ /// </summary>
+ /// <param name="user">The person who is getting the uplink</param>
+ /// <param name="balance">The amount of currency on the uplink. If null, will just use the amount specified in the preset.</param>
+ /// <param name="uplinkEntity">The entity that will actually have the uplink functionality. Defaults to the PDA if null.</param>
+ /// <param name="giveDiscounts">Marker that enables discounts for uplink items.</param>
+ /// <returns>Whether or not the uplink was added successfully</returns>
+ public bool AddUplink(
+ EntityUid user,
+ FixedPoint2 balance,
+ EntityUid? uplinkEntity = null,
+ bool giveDiscounts = false)
{
- [Dependency] private readonly InventorySystem _inventorySystem = default!;
- [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
- [Dependency] private readonly StoreSystem _store = default!;
-
- [ValidatePrototypeId<CurrencyPrototype>]
- public const string TelecrystalCurrencyPrototype = "Telecrystal";
-
- /// <summary>
- /// Adds an uplink to the target
- /// </summary>
- /// <param name="user">The person who is getting the uplink</param>
- /// <param name="balance">The amount of currency on the uplink. If null, will just use the amount specified in the preset.</param>
- /// <param name="uplinkEntity">The entity that will actually have the uplink functionality. Defaults to the PDA if null.</param>
- /// <param name="giveDiscounts">Marker that enables discounts for uplink items.</param>
- /// <returns>Whether or not the uplink was added successfully</returns>
- public bool AddUplink(
- EntityUid user,
- FixedPoint2? balance,
- EntityUid? uplinkEntity = null,
- bool giveDiscounts = false
- )
- {
- // Try to find target item if none passed
- uplinkEntity ??= FindUplinkTarget(user);
- if (uplinkEntity == null)
- {
- return false;
- }
+ // Try to find target item if none passed
- EnsureComp<UplinkComponent>(uplinkEntity.Value);
- var store = EnsureComp<StoreComponent>(uplinkEntity.Value);
+ uplinkEntity ??= FindUplinkTarget(user);
- store.AccountOwner = user;
- store.Balance.Clear();
- if (balance != null)
- {
- store.Balance.Clear();
- _store.TryAddCurrency(new Dictionary<string, FixedPoint2> { { TelecrystalCurrencyPrototype, balance.Value } }, uplinkEntity.Value, store);
- }
+ if (uplinkEntity == null)
+ return ImplantUplink(user, balance, giveDiscounts);
- var uplinkInitializedEvent = new StoreInitializedEvent(
- TargetUser: user,
- Store: uplinkEntity.Value,
- UseDiscounts: giveDiscounts,
- Listings: _store.GetAvailableListings(user, uplinkEntity.Value, store)
- .ToArray()
- );
- RaiseLocalEvent(ref uplinkInitializedEvent);
- // TODO add BUI. Currently can't be done outside of yaml -_-
-
- return true;
- }
+ EnsureComp<UplinkComponent>(uplinkEntity.Value);
+
+ SetUplink(user, uplinkEntity.Value, balance, giveDiscounts);
+
+ // TODO add BUI. Currently can't be done outside of yaml -_-
+ // ^ What does this even mean?
+
+ return true;
+ }
+
+ /// <summary>
+ /// Configure TC for the uplink
+ /// </summary>
+ private void SetUplink(EntityUid user, EntityUid uplink, FixedPoint2 balance, bool giveDiscounts)
+ {
+ var store = EnsureComp<StoreComponent>(uplink);
+ store.AccountOwner = user;
+
+ store.Balance.Clear();
+ _store.TryAddCurrency(new Dictionary<string, FixedPoint2> { { TelecrystalCurrencyPrototype, balance } },
+ uplink,
+ store);
- /// <summary>
- /// Finds the entity that can hold an uplink for a user.
- /// Usually this is a pda in their pda slot, but can also be in their hands. (but not pockets or inside bag, etc.)
- /// </summary>
- public EntityUid? FindUplinkTarget(EntityUid user)
+ var uplinkInitializedEvent = new StoreInitializedEvent(
+ TargetUser: user,
+ Store: uplink,
+ UseDiscounts: giveDiscounts,
+ Listings: _store.GetAvailableListings(user, uplink, store)
+ .ToArray());
+ RaiseLocalEvent(ref uplinkInitializedEvent);
+ }
+
+ /// <summary>
+ /// Implant an uplink as a fallback measure if the traitor had no PDA
+ /// </summary>
+ private bool ImplantUplink(EntityUid user, FixedPoint2 balance, bool giveDiscounts)
+ {
+ var implantProto = new string(FallbackUplinkImplant);
+
+ if (!_proto.TryIndex<ListingPrototype>(FallbackUplinkCatalog, out var catalog))
+ return false;
+
+ if (!catalog.Cost.TryGetValue(TelecrystalCurrencyPrototype, out var cost))
+ return false;
+
+ if (balance < cost) // Can't use Math functions on FixedPoint2
+ balance = 0;
+ else
+ balance = balance - cost;
+
+ var implant = _subdermalImplant.AddImplant(user, implantProto);
+
+ if (!HasComp<StoreComponent>(implant))
+ return false;
+
+ SetUplink(user, implant.Value, balance, giveDiscounts);
+ return true;
+ }
+
+ /// <summary>
+ /// Finds the entity that can hold an uplink for a user.
+ /// Usually this is a pda in their pda slot, but can also be in their hands. (but not pockets or inside bag, etc.)
+ /// </summary>
+ public EntityUid? FindUplinkTarget(EntityUid user)
+ {
+ // Try to find PDA in inventory
+ if (_inventorySystem.TryGetContainerSlotEnumerator(user, out var containerSlotEnumerator))
{
- // Try to find PDA in inventory
- if (_inventorySystem.TryGetContainerSlotEnumerator(user, out var containerSlotEnumerator))
+ while (containerSlotEnumerator.MoveNext(out var pdaUid))
{
- while (containerSlotEnumerator.MoveNext(out var pdaUid))
- {
- if (!pdaUid.ContainedEntity.HasValue)
- continue;
-
- if (HasComp<PdaComponent>(pdaUid.ContainedEntity.Value) || HasComp<StoreComponent>(pdaUid.ContainedEntity.Value))
- return pdaUid.ContainedEntity.Value;
- }
- }
+ if (!pdaUid.ContainedEntity.HasValue)
+ continue;
- // Also check hands
- foreach (var item in _handsSystem.EnumerateHeld(user))
- {
- if (HasComp<PdaComponent>(item) || HasComp<StoreComponent>(item))
- return item;
+ if (HasComp<PdaComponent>(pdaUid.ContainedEntity.Value) || HasComp<StoreComponent>(pdaUid.ContainedEntity.Value))
+ return pdaUid.ContainedEntity.Value;
}
+ }
- return null;
+ // Also check hands
+ foreach (var item in _handsSystem.EnumerateHeld(user))
+ {
+ if (HasComp<PdaComponent>(item) || HasComp<StoreComponent>(item))
+ return item;
}
+
+ return null;
}
}
/// </summary>
public void AddImplants(EntityUid uid, IEnumerable<String> implants)
{
- var coords = Transform(uid).Coordinates;
foreach (var id in implants)
{
- var ent = Spawn(id, coords);
- if (TryComp<SubdermalImplantComponent>(ent, out var implant))
- {
- ForceImplant(uid, ent, implant);
- }
- else
- {
- Log.Warning($"Found invalid starting implant '{id}' on {uid} {ToPrettyString(uid):implanted}");
- Del(ent);
- }
+ AddImplant(uid, id);
+ }
+ }
+
+ /// <summary>
+ /// Adds a single implant to a person, and returns the implant.
+ /// Logs any implant ids that don't have <see cref="SubdermalImplantComponent"/>.
+ /// </summary>
+ /// <returns>
+ /// The implant, if it was successfully created. Otherwise, null.
+ /// </returns>>
+ public EntityUid? AddImplant(EntityUid uid, String implantId)
+ {
+ var coords = Transform(uid).Coordinates;
+ var ent = Spawn(implantId, coords);
+
+ if (TryComp<SubdermalImplantComponent>(ent, out var implant))
+ {
+ ForceImplant(uid, ent, implant);
+ }
+ else
+ {
+ Log.Warning($"Found invalid starting implant '{implantId}' on {uid} {ToPrettyString(uid):implanted}");
+ Del(ent);
+ return null;
}
+
+ return ent;
}
/// <summary>
traitor-role-greeting =
You are an agent sent by {$corporation} on behalf of [color = darkred]The Syndicate.[/color]
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.
+ Use your uplink to buy the tools you'll need for this mission.
Death to Nanotrasen!
traitor-role-codewords =
The codewords are: [color = lightgray]
traitor-role-uplink-code =
Set your ringtone to the notes [color = lightgray]{$code}[/color] to lock or unlock your uplink.
Remember to lock it after, or the stations crew will easily open it too!
+traitor-role-uplink-implant =
+ Your uplink implant has been activated, access it from your hotbar.
+ The uplink is secure unless someone removes it from your body.
# don't need all the flavour text for character menu
traitor-role-codewords-short =
The codewords are:
{$codewords}.
traitor-role-uplink-code-short = Your uplink code is {$code}. Set it as your PDA ringtone to access uplink.
+traitor-role-uplink-implant-short = Your uplink was implanted. Access it from your hotbar.
components:
# make the player a traitor once its taken
- type: AutoTraitor
- giveUplink: false
- giveObjectives: false
+ profile: TraitorReinforcement
- type: entity
id: MobMonkeySyndicateAgentNukeops # Reinforcement exclusive to nukeops uplink
components:
# make the player a traitor once its taken
- type: AutoTraitor
- giveUplink: false
- giveObjectives: false
+ profile: TraitorReinforcement
- type: entity
id: MobKoboldSyndicateAgentNukeops # Reinforcement exclusive to nukeops uplink
components:
# make the player a traitor once its taken
- type: AutoTraitor
- giveUplink: false
- giveObjectives: false
+ profile: TraitorReinforcement
- type: entity
parent: MobHumanSyndicateAgent
mindRoles:
- MindRoleTraitor
+- type: entity
+ id: TraitorReinforcement
+ parent: Traitor
+ components:
+ - type: TraitorRule
+ giveUplink: false
+ giveCodewords: false # It would actually give them a different set of codewords than the regular traitors, anyway
+ giveBriefing: false
+
- type: entity
id: Revolutionary
parent: BaseGameRule
By pressing [color=yellow][bold][keybind="OpenCharacterMenu"][/bold][/color], you'll see your personal uplink code. [bold]Setting your PDA's ringtone as this code will open the uplink.[/bold]
Pressing [color=yellow][bold][keybind="OpenCharacterMenu"][/bold][/color] also lets you view your objectives and the codewords.
+ If you do not have a PDA when you are activated, an [color=cyan]uplink implant[/color] is provided [bold]for the full [color=red]TC[/color] price of the implant.[/bold]
+ It can be accessed from your hotbar.
<Box>
<GuideEntityEmbed Entity="PassengerPDA" Caption="PDA"/>
<GuideEntityEmbed Entity="Telecrystal" Caption="Telecrystals"/>
+ <GuideEntityEmbed Entity="BaseUplinkRadio" Caption="Uplink Implant"/>
</Box>
- [bold]Make sure to close your uplink to prevent anyone else from seeing it.[/bold] You don't want [color=#cb0000]Security[/color] to get their hands on this premium selection of contraband!
+ [bold]Make sure to close your PDA uplink to prevent anyone else from seeing it.[/bold] You don't want [color=#cb0000]Security[/color] to get their hands on this premium selection of contraband!
+
+ Implanted uplinks are not normally accessible to other people, so they do not have any security measures. They can, however, be removed from you with an empty implanter.
<Box>
<GuideEntityEmbed Entity="WeaponPistolCobra" Caption="Weaponry"/>