Traitor activation fix for missing PDA (#30359)
* 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>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 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", "#"))));
|
||||
{
|
||||
if (startingBalance < prototype.AntagAdvantage) // Can't use Math functions on FixedPoint2
|
||||
startingBalance = 0;
|
||||
else
|
||||
startingBalance = startingBalance - prototype.AntagAdvantage;
|
||||
}
|
||||
|
||||
_antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code, issuer), null, component.GreetSoundNotification);
|
||||
// Choose and generate an Uplink, and return the uplink code if applicable
|
||||
var uplinkParams = RequestUplink(traitor, startingBalance, briefing);
|
||||
code = uplinkParams.Item1;
|
||||
briefing = uplinkParams.Item2;
|
||||
}
|
||||
|
||||
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"))));
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
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
|
||||
{
|
||||
[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
|
||||
@@ -29,39 +35,73 @@ namespace Content.Server.Traitor.Uplink
|
||||
/// <returns>Whether or not the uplink was added successfully</returns>
|
||||
public bool AddUplink(
|
||||
EntityUid user,
|
||||
FixedPoint2? balance,
|
||||
FixedPoint2 balance,
|
||||
EntityUid? uplinkEntity = null,
|
||||
bool giveDiscounts = false
|
||||
)
|
||||
bool giveDiscounts = false)
|
||||
{
|
||||
// Try to find target item if none passed
|
||||
|
||||
uplinkEntity ??= FindUplinkTarget(user);
|
||||
|
||||
if (uplinkEntity == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return ImplantUplink(user, balance, giveDiscounts);
|
||||
|
||||
EnsureComp<UplinkComponent>(uplinkEntity.Value);
|
||||
var store = EnsureComp<StoreComponent>(uplinkEntity.Value);
|
||||
|
||||
store.AccountOwner = user;
|
||||
store.Balance.Clear();
|
||||
if (balance != null)
|
||||
{
|
||||
store.Balance.Clear();
|
||||
_store.TryAddCurrency(new Dictionary<string, FixedPoint2> { { TelecrystalCurrencyPrototype, balance.Value } }, uplinkEntity.Value, store);
|
||||
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);
|
||||
|
||||
var uplinkInitializedEvent = new StoreInitializedEvent(
|
||||
TargetUser: user,
|
||||
Store: uplinkEntity.Value,
|
||||
Store: uplink,
|
||||
UseDiscounts: giveDiscounts,
|
||||
Listings: _store.GetAvailableListings(user, uplinkEntity.Value, store)
|
||||
.ToArray()
|
||||
);
|
||||
Listings: _store.GetAvailableListings(user, uplink, store)
|
||||
.ToArray());
|
||||
RaiseLocalEvent(ref uplinkInitializedEvent);
|
||||
// TODO add BUI. Currently can't be done outside of yaml -_-
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
@@ -94,4 +134,3 @@ namespace Content.Server.Traitor.Uplink
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
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 '{id}' on {uid} {ToPrettyString(uid):implanted}");
|
||||
Log.Warning($"Found invalid starting implant '{implantId}' on {uid} {ToPrettyString(uid):implanted}");
|
||||
Del(ent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return ent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1407,8 +1407,7 @@
|
||||
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
|
||||
@@ -1569,8 +1568,7 @@
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -189,6 +189,15 @@
|
||||
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
|
||||
|
||||
@@ -18,12 +18,17 @@
|
||||
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"/>
|
||||
|
||||
Reference in New Issue
Block a user