]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Traitor activation fix for missing PDA (#30359)
authorErrant <35878406+Errant-4@users.noreply.github.com>
Fri, 18 Oct 2024 12:55:43 +0000 (14:55 +0200)
committerGitHub <noreply@github.com>
Fri, 18 Oct 2024 12:55:43 +0000 (14:55 +0200)
* Implant the uplink if no PDA is found

* comments

* tidy up loose ends

* Whoops usually I start with the namespace, how did I forget it, shame shame

* Consistent data type for starting TC balance, misc changes

* Implant briefing, guidebook

* Update AutoTraitor, add uplink, codeword and briefing parameters to TraitorRuleComponent,  no pda for reinforcements

* engine 5c0ce43

* pass pda to AddUplink

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

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

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

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

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

* removed redundant implant check

* minor cleanup

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs
Content.Server/GameTicking/Rules/TraitorRuleSystem.cs
Content.Server/Traitor/Components/AutoTraitorComponent.cs
Content.Server/Traitor/Systems/AutoTraitorSystem.cs
Content.Server/Traitor/Uplink/UplinkSystem.cs
Content.Shared/Implants/SharedSubdermalImplantSystem.cs
Resources/Locale/en-US/game-ticking/game-presets/preset-traitor.ftl
Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
Resources/Prototypes/Entities/Mobs/Player/human.yml
Resources/Prototypes/GameRules/roundstart.yml
Resources/ServerInfo/Guidebook/Antagonist/Traitors.xml

index 62f92963aa7b5da7d8f35b1b39aa6d2f7d9411cf..6f82aa042f01d307140a100540232826a20762ab 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Shared.Dataset;
+using Content.Shared.FixedPoint;
 using Content.Shared.NPC.Prototypes;
 using Content.Shared.Random;
 using Content.Shared.Roles;
@@ -31,6 +32,24 @@ public sealed partial class TraitorRuleComponent : Component
     [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];
 
@@ -68,5 +87,5 @@ public sealed partial class TraitorRuleComponent : Component
     /// The amount of TC traitors start with.
     /// </summary>
     [DataField]
-    public int StartingBalance = 20;
+    public FixedPoint2 StartingBalance = 20;
 }
index 44ad00ae170223be294c94848f62c896b8aa3a71..1987613763b5f2dec5a7d3789d1a940fef06e20a 100644 (file)
@@ -7,6 +7,7 @@ using Content.Server.PDA.Ringer;
 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;
@@ -75,38 +76,46 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
         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);
 
@@ -134,6 +143,32 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
         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)
     {
@@ -141,13 +176,17 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
     }
 
     // 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();
     }
index ab4bee2f267a745706c5ff678f0c42d936880bbf..a4710afd8ebf2b3f420aa7f03b5952957213f9a8 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Server.Traitor.Systems;
+using Robust.Shared.Prototypes;
 
 namespace Content.Server.Traitor.Components;
 
@@ -9,14 +10,8 @@ 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";
 }
index e9307effbc645ce673abe14297a284cb74f865e3..d5a4db591a7063b69692ed823f1f474c8a5a95aa 100644 (file)
@@ -12,9 +12,6 @@ public sealed class AutoTraitorSystem : EntitySystem
 {
     [Dependency] private readonly AntagSelectionSystem _antag = default!;
 
-    [ValidatePrototypeId<EntityPrototype>]
-    private const string DefaultTraitorRule = "Traitor";
-
     public override void Initialize()
     {
         base.Initialize();
@@ -24,6 +21,6 @@ public sealed class AutoTraitorSystem : EntitySystem
 
     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);
     }
 }
index ae809dc4d774c4459812a93da87b72af3252a924..4c0a990b148b85e45ba016e20fa7c8dcebe29b94 100644 (file)
 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;
     }
 }
index 830d2270aa45b3d54714fcd183eb314030950a07..94203de615583bd9e6f32fddebc1418f417c3dbc 100644 (file)
@@ -94,20 +94,36 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
     /// </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>
index fd3e6b82aa7900f174fb1d151c2d41448df9d2a9..cf2f2b11308c3f67fb00b1a4deff10ec7237de0e 100644 (file)
@@ -26,7 +26,7 @@ traitor-death-match-end-round-description-entry = {$originalName}'s PDA, with {$
 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]
@@ -36,9 +36,13 @@ traitor-role-codewords =
 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.
index 6ae711a39dd5f5f73dbf59db4380cb7022e4a052..e2dd9ac3f30566a1985435aeafabfb231450a706 100644 (file)
   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
index 4a7a48a0d5e962d448eac73b5534378210b91d48..7fc8bf7d6c99c0879622c81d3d6ce8d6bcf44553 100644 (file)
@@ -31,8 +31,7 @@
   components:
   # make the player a traitor once its taken
   - type: AutoTraitor
-    giveUplink: false
-    giveObjectives: false
+    profile: TraitorReinforcement
 
 - type: entity
   parent: MobHumanSyndicateAgent
index 46d4366f680b2fa824c6d92c620dde48cce7d00b..cec5c9ee093174b0e440b1d2fe70b4ecd34bfcb2 100644 (file)
       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
index 3e48200e88ed141d5c5b8264fe7ab3e965026356..1c7e74f444227906de37792ddadc0724c45102c0 100644 (file)
   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"/>