[MIRROR] Replaceable Traitor Uplinks [MDB IGNORE] (#20350)

* Replaceable Traitor Uplinks

* skyrat edits

---------

Co-authored-by: Sol N <116288367+flowercuco@users.noreply.github.com>
Co-authored-by: lessthnthree <three@lessthanthree.dk>
This commit is contained in:
SkyratBot
2023-04-06 02:06:22 +01:00
committed by GitHub
co-authored by Sol N lessthnthree
parent ceb5fb966c
commit 638cbf8a8e
14 changed files with 274 additions and 38 deletions
@@ -3,6 +3,8 @@
/// Called whenever the uplink handler receives any sort of update. Used by uplinks to update their UI. No arguments passed
#define COMSIG_UPLINK_HANDLER_ON_UPDATE "uplink_handler_on_update"
/// Sent from the uplink handler when the traitor uses the syndicate uplink beacon to order a replacement uplink.
#define COMSIG_UPLINK_HANDLER_REPLACEMENT_ORDERED "uplink_handler_replacement_ordered"
/// Called before the traitor objective is generated
#define COMSIG_TRAITOR_OBJECTIVE_PRE_GENERATE "traitor_objective_pre_generate"
+2
View File
@@ -90,6 +90,8 @@
#define FREQ_SIGNALER 1457 // the default for new signalers
#define FREQ_COMMON 1459 // Common comms frequency, dark green
#define MIN_UNUSED_FREQ 1461 // Prevents rolling AI Private or Common
#define MAX_FREQ 1489 // ------------------------------------------------------
#define MAX_FREE_FREQ 1599 // -------------------------------------------------
+7 -2
View File
@@ -605,10 +605,15 @@
//Mind helpers
/datum/mind/proc/teach_crafting_recipe(R)
/// proc that teaches user a non-standard crafting recipe
/datum/mind/proc/teach_crafting_recipe(recipe)
if(!learned_recipes)
learned_recipes = list()
learned_recipes |= R
learned_recipes |= recipe
/// proc that makes user forget a specific crafting recipe
/datum/mind/proc/forget_crafting_recipe(recipe)
learned_recipes -= recipe
/datum/mind/proc/has_crafting_recipe(mob/user, potential_recipe)
if(!learned_recipes)
@@ -60,3 +60,17 @@
/obj/item/stack/cable_coil = 10,
)
category = CAT_STRUCTURE
/datum/crafting_recipe/syndicate_uplink_beacon
name = "Syndicate Uplink Beacon"
result = /obj/structure/syndicate_uplink_beacon
tool_behaviors = list(TOOL_SCREWDRIVER)
always_available = FALSE
time = 6 SECONDS
reqs = list(
/obj/item/stack/sheet/iron = 5,
/obj/item/stack/cable_coil = 5,
/obj/item/beacon = 1,
/obj/item/stack/ore/bluespace_crystal = 1,
)
category = CAT_STRUCTURE
+35 -4
View File
@@ -65,6 +65,8 @@
RegisterSignal(parent, COMSIG_RADIO_NEW_MESSAGE, PROC_REF(new_message))
else if(istype(parent, /obj/item/pen))
RegisterSignal(parent, COMSIG_PEN_ROTATED, PROC_REF(pen_rotation))
else if(istype(parent, /obj/item/uplink/replacement))
RegisterSignal(parent, COMSIG_MOVABLE_HEAR, PROC_REF(on_heard))
if(owner)
src.owner = owner
@@ -86,6 +88,7 @@
else
uplink_handler = uplink_handler_override
RegisterSignal(uplink_handler, COMSIG_UPLINK_HANDLER_ON_UPDATE, PROC_REF(handle_uplink_handler_update))
RegisterSignal(uplink_handler, COMSIG_UPLINK_HANDLER_REPLACEMENT_ORDERED, PROC_REF(handle_uplink_replaced))
if(!lockable)
active = TRUE
locked = FALSE
@@ -96,6 +99,19 @@
SIGNAL_HANDLER
SStgui.update_uis(src)
/// When a new uplink is made via the syndicate beacon it locks all lockable uplinks and destroys replacement uplinks
/datum/component/uplink/proc/handle_uplink_replaced()
SIGNAL_HANDLER
if(lockable)
lock_uplink()
if(!istype(parent, /obj/item/uplink/replacement))
return
var/obj/item/uplink_item = parent
do_sparks(number = 3, cardinal_only = FALSE, source = uplink_item)
uplink_item.visible_message(span_warning("The [uplink_item] suddenly combusts!"), vision_distance = COMBAT_MESSAGE_RANGE)
new /obj/effect/decal/cleanable/ash(get_turf(uplink_item))
qdel(uplink_item)
/// Adds telecrystals to the uplink. It is bad practice to use this outside of the component itself.
/datum/component/uplink/proc/add_telecrystals(telecrystals_added)
set_telecrystals(uplink_handler.telecrystals + telecrystals_added)
@@ -236,7 +252,7 @@
data["extra_purchasable_stock"] = extra_purchasable_stock
data["current_stock"] = remaining_stock
data["shop_locked"] = uplink_handler.shop_locked
data["purchased_items"] = length(uplink_handler.purchase_log.purchase_log)
data["purchased_items"] = length(uplink_handler.purchase_log?.purchase_log)
return data
/datum/component/uplink/ui_static_data(mob/user)
@@ -277,9 +293,7 @@
if("lock")
if(!lockable)
return TRUE
active = FALSE
locked = TRUE
SStgui.close_uis(src)
lock_uplink()
if(!uplink_handler.has_objectives)
return TRUE
@@ -321,6 +335,12 @@
uplink_handler.abort_objective(objective)
return TRUE
/// Proc that locks uplinks
/datum/component/uplink/proc/lock_uplink()
active = FALSE
locked = TRUE
SStgui.close_uis(src)
// Implant signal responses
/datum/component/uplink/proc/implant_activation()
SIGNAL_HANDLER
@@ -459,6 +479,17 @@
return returnable_code
/// Proc that unlocks a locked replacement uplink when it hears the unlock code from their datum
/datum/component/uplink/proc/on_heard(datum/source, list/hearing_args)
SIGNAL_HANDLER
if(!locked)
return
if(!findtext(hearing_args[HEARING_RAW_MESSAGE], unlock_code))
return
var/atom/replacement_uplink = parent
locked = FALSE
replacement_uplink.balloon_alert_to_viewers("beep", vision_distance = COMBAT_MESSAGE_RANGE)
/datum/component/uplink/proc/failsafe(mob/living/carbon/user)
if(!parent)
return
@@ -132,3 +132,16 @@
mid_sounds = list('sound/effects/bubbles2.ogg' = 1)
mid_length = 7 SECONDS
volume = 25
/datum/looping_sound/typing
mid_sounds = list(
'sound/machines/terminal_button01.ogg' = 1,
'sound/machines/terminal_button02.ogg' = 1,
'sound/machines/terminal_button03.ogg' = 1,
'sound/machines/terminal_button04.ogg' = 1,
'sound/machines/terminal_button05.ogg' = 1,
'sound/machines/terminal_button06.ogg' = 1,
'sound/machines/terminal_button07.ogg' = 1,
'sound/machines/terminal_button08.ogg' = 1,
)
mid_length = 0.3 SECONDS
@@ -0,0 +1,114 @@
/// Device that traitors can craft in order to be sent a new, undisguised uplink
/obj/structure/syndicate_uplink_beacon
name = "suspicious beacon"
icon = 'icons/obj/machines/telecomms.dmi'
icon_state = "relay_traitor"
desc = "This ramshackle device seems capable of recieving and sending signals for some nefarious purpose."
density = TRUE
anchored = TRUE
/// Traitor's code that they speak into the radio
var/uplink_code = ""
/// weakref to person who is going to use the beacon to get a replacement uplink
var/datum/weakref/owner
/// while constructed the teleport beacon is still active
var/obj/item/beacon/teleport_beacon
/// Radio that the device needs to listen to the codeword from the traitor
var/obj/item/radio/listening_radio
/// prevents traitor from activating teleport_beacon proc too much in a small period of time
COOLDOWN_DECLARE(beacon_cooldown)
/obj/structure/syndicate_uplink_beacon/Initialize(mapload)
. = ..()
register_context()
var/static/list/tool_behaviors = list(
TOOL_SCREWDRIVER = list(
SCREENTIP_CONTEXT_RMB = "Deconstruct",
),
)
AddElement(/datum/element/contextual_screentip_tools, tool_behaviors)
listening_radio = new(src)
listening_radio.canhear_range = 0
teleport_beacon = new(src)
/obj/structure/syndicate_uplink_beacon/attack_hand(mob/living/user, list/modifiers)
if(!IS_TRAITOR(user))
balloon_alert(user, "don't know how to use!")
return
if(IS_WEAKREF_OF(owner, user))
balloon_alert(user, "already synchronized to you!")
return
if(owner != null)
balloon_alert(user, "already claimed!")
return
var/datum/looping_sound/typing/typing_sounds = new(src, start_immediately = TRUE)
balloon_alert(user, "synchronizing...")
if(!do_after(user = user, delay = 3 SECONDS, target = src, interaction_key = REF(src)))
typing_sounds.stop()
return
typing_sounds.stop()
probe_traitor(user)
/obj/structure/syndicate_uplink_beacon/screwdriver_act_secondary(mob/living/user, obj/item/tool)
tool.play_tool_sound(src)
balloon_alert(user, "deconstructing...")
if (!do_after(user, 5 SECONDS, target = src))
return FALSE
var/turf/beacon_tile = get_turf(src)
new /obj/item/stack/sheet/iron/five(beacon_tile)
new /obj/item/stack/cable_coil/five(beacon_tile)
teleport_beacon.forceMove(beacon_tile)
teleport_beacon = null
qdel(src)
return TRUE
/obj/structure/syndicate_uplink_beacon/Destroy()
QDEL_NULL(listening_radio)
if(teleport_beacon)
QDEL_NULL(teleport_beacon)
return ..()
/// Proc reads the user, sets radio to the correct frequency and starts to listen for the replacement uplink code
/obj/structure/syndicate_uplink_beacon/proc/probe_traitor(mob/living/user)
owner = WEAKREF(user)
var/datum/antagonist/traitor/traitor_datum = user.mind.has_antag_datum(/datum/antagonist/traitor)
uplink_code = traitor_datum.replacement_uplink_code
listening_radio.set_frequency(traitor_datum.replacement_uplink_frequency)
become_hearing_sensitive()
/obj/structure/syndicate_uplink_beacon/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods, message_range)
if(ismob(speaker) || radio_freq != listening_radio.get_frequency())
return
if(!findtext(message, uplink_code))
return
teleport_uplink()
/// Proc uses owners uplink handler to create replacement uplink and then lock or destroy their other uplinks
/obj/structure/syndicate_uplink_beacon/proc/teleport_uplink()
if(!COOLDOWN_FINISHED(src, beacon_cooldown))
return
COOLDOWN_START(src, beacon_cooldown, 10 MINUTES)
var/mob/living/resolved_owner = owner.resolve()
if(isnull(resolved_owner))
return
var/datum/antagonist/traitor/traitor_datum = resolved_owner.mind.has_antag_datum(/datum/antagonist/traitor)
if(isnull(traitor_datum))
return
var/datum/uplink_handler/uplink_handler = traitor_datum.uplink_handler
SEND_SIGNAL(uplink_handler, COMSIG_UPLINK_HANDLER_REPLACEMENT_ORDERED)
new /obj/item/uplink/replacement(get_turf(src), /*owner = */resolved_owner, /*tc_amount = */0, /*uplink_handler_override = */uplink_handler)
flick("relay_traitor_activate", src)
do_sparks(number = 5, cardinal_only = FALSE, source = src)
log_traitor("[key_name(resolved_owner)] acquired a replacement uplink via the syndicate uplink beacon.")
// Adds screentips
/obj/structure/syndicate_uplink_beacon/add_context(atom/source, list/context, obj/item/held_item, mob/user)
if(held_item || !IS_TRAITOR(user))
return NONE
context[SCREENTIP_CONTEXT_LMB] = "Synchronize with beacon"
return CONTEXTUAL_SCREENTIP_SET
@@ -18,6 +18,10 @@
var/should_give_codewords = TRUE
///give this traitor an uplink?
var/give_uplink = TRUE
/// Code that allows traitor to get a replacement uplink
var/replacement_uplink_code = ""
/// Radio frequency that traitor must speak on to get a replacement uplink
var/replacement_uplink_frequency = ""
///if TRUE, this traitor will always get hijacking as their final objective
var/is_hijacker = FALSE
@@ -47,6 +51,7 @@
if(give_uplink)
owner.give_uplink(silent = TRUE, antag_datum = src)
generate_replacement_codes()
var/datum/component/uplink/uplink = owner.find_syndicate_uplink()
uplink_ref = WEAKREF(uplink)
@@ -82,6 +87,8 @@
pick_employer()
owner.teach_crafting_recipe(/datum/crafting_recipe/syndicate_uplink_beacon)
owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)
return ..()
@@ -139,8 +146,14 @@
result += "<a href='?src=[REF(owner)];common=give_objective'>Force add objective</a><br>"
return result
/// proc that generates the traitors replacement uplink code and radio frequency
/datum/antagonist/traitor/proc/generate_replacement_codes()
replacement_uplink_code = "[pick(GLOB.phonetic_alphabet)] [rand(10,99)]"
replacement_uplink_frequency = sanitize_frequency(rand(MIN_UNUSED_FREQ, MAX_FREQ), free = FALSE, syndie = FALSE)
/datum/antagonist/traitor/on_removal()
owner.special_role = null
owner.forget_crafting_recipe(/datum/crafting_recipe/syndicate_uplink_beacon)
return ..()
/datum/antagonist/traitor/proc/pick_employer()
@@ -258,6 +271,8 @@
data["theme"] = traitor_flavor["ui_theme"]
data["code"] = uplink?.unlock_code
data["failsafe_code"] = uplink?.failsafe_code
data["replacement_code"] = replacement_uplink_code
data["replacement_frequency"] = format_frequency(replacement_uplink_frequency)
data["intro"] = traitor_flavor["introduction"]
data["allies"] = traitor_flavor["allies"]
data["goal"] = traitor_flavor["goal"]
-6
View File
@@ -142,13 +142,7 @@
if(. && ishuman(user))
var/mob/living/carbon/human/H = user
var/open = FALSE
<<<<<<< HEAD
// if(H.dna.features["wings"] != "None") // This was our check before, leaving it here in case it's needed when someone gets around to fixing this.
// if(H.dna.species.mutant_bodyparts["wingsopen"])
var/obj/item/organ/external/wings/functional/wings = H.get_organ_slot(ORGAN_SLOT_EXTERNAL_WINGS)
=======
var/obj/item/organ/external/wings/functional/wings = H.get_organ_slot(ORGAN_SLOT_EXTERNAL_WINGS)
>>>>>>> ecbcef778df (Refactors Regenerate Organs, and a few organ helpers (#74219))
if(istype(wings))
if(wings.wings_open)
open = TRUE
+47 -9
View File
@@ -23,15 +23,25 @@
/// The uplink flag for this type.
/// See [`code/__DEFINES/uplink.dm`]
var/uplink_flag = UPLINK_TRAITORS
/// If the uplink is lockable, which defaults to false which most subtypes of this item are for debug reasons
var/lockable_uplink = FALSE
/obj/item/uplink/Initialize(mapload, owner, tc_amount = 20)
/obj/item/uplink/Initialize(mapload, owner, tc_amount = 20, datum/uplink_handler/uplink_handler_override = null)
. = ..()
AddComponent(/datum/component/uplink, owner, FALSE, TRUE, uplink_flag, tc_amount)
AddComponent(\
/datum/component/uplink, \
owner = owner, \
lockable = lockable_uplink, \
enabled = TRUE, \
uplink_flag = uplink_flag, \
starting_tc = tc_amount, \
uplink_handler_override = uplink_handler_override, \
)
/obj/item/uplink/debug
name = "debug uplink"
/obj/item/uplink/debug/Initialize(mapload, owner, tc_amount = 9000)
/obj/item/uplink/debug/Initialize(mapload, owner, tc_amount = 9000, datum/uplink_handler/uplink_handler_override = null)
. = ..()
var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink)
hidden_uplink.name = "debug uplink"
@@ -44,7 +54,7 @@
name = "debug nuclear uplink"
uplink_flag = UPLINK_NUKE_OPS
/obj/item/uplink/nuclear/debug/Initialize(mapload, owner, tc_amount = 9000)
/obj/item/uplink/nuclear/debug/Initialize(mapload, owner, tc_amount = 9000, datum/uplink_handler/uplink_handler_override = null)
. = ..()
var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink)
hidden_uplink.name = "debug nuclear uplink"
@@ -65,19 +75,47 @@
name = "dusty radio"
desc = "A dusty looking radio."
/obj/item/uplink/old/Initialize(mapload, owner, tc_amount = 10)
/obj/item/uplink/old/Initialize(mapload, owner, tc_amount = 10, datum/uplink_handler/uplink_handler_override = null)
. = ..()
var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink)
hidden_uplink.name = "dusty radio"
// Uplink subtype used as replacement uplink
/obj/item/uplink/replacement
lockable_uplink = TRUE
/obj/item/uplink/replacement/Initialize(mapload, owner, tc_amount = 10, datum/uplink_handler/uplink_handler_override = null)
. = ..()
var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink)
var/mob/living/replacement_needer = owner
if(!istype(replacement_needer))
return
var/datum/antagonist/traitor/traitor_datum = replacement_needer?.mind.has_antag_datum(/datum/antagonist/traitor)
hidden_uplink.unlock_code = traitor_datum?.replacement_uplink_code
become_hearing_sensitive()
/obj/item/uplink/replacement/screwdriver_act_secondary(mob/living/user, obj/item/tool)
tool.play_tool_sound(src)
balloon_alert(user, "deconstructing...")
if (!do_after(user, 3 SECONDS, target = src))
return FALSE
qdel(src)
return TRUE
/obj/item/uplink/replacement/examine(mob/user)
. = ..()
if(!IS_TRAITOR(user))
return
. += span_notice("You can destroy this device with a screwdriver.")
// Multitool uplink
///obj/item/multitool/uplink/Initialize(mapload, owner, tc_amount = 20) //ORIGINAL
/obj/item/multitool/uplink/Initialize(mapload, owner, tc_amount = 35) //SKYRAT EDIT CHANGE
////obj/item/multitool/uplink/Initialize(mapload, owner, tc_amount = 20, datum/uplink_handler/uplink_handler_override = null) //ORIGINAL
/obj/item/multitool/uplink/Initialize(mapload, owner, tc_amount = 35, datum/uplink_handler/uplink_handler_override = null) //SKYRAT EDIT CHANGE
. = ..()
AddComponent(/datum/component/uplink, owner, FALSE, TRUE, UPLINK_TRAITORS, tc_amount)
// Pen uplink
///obj/item/pen/uplink/Initialize(mapload, owner, tc_amount = 20) //ORIGINAL
/obj/item/pen/uplink/Initialize(mapload, owner, tc_amount = 35) //SKYRAT EDIT CHANGE
///obj/item/pen/uplink/Initialize(mapload, owner, tc_amount = 20, datum/uplink_handler/uplink_handler_override = null) //ORIGINAL
/obj/item/pen/uplink/Initialize(mapload, owner, tc_amount = 35, datum/uplink_handler/uplink_handler_override = null) //SKYRAT EDIT CHANGE
. = ..()
AddComponent(/datum/component/uplink, owner, TRUE, FALSE, UPLINK_TRAITORS, tc_amount)
-15
View File
@@ -29,11 +29,8 @@
#_maps/RandomRuins/SpaceRuins/derelict6.dmm
#_maps/RandomRuins/SpaceRuins/derelict7.dmm
#_maps/RandomRuins/SpaceRuins/derelict8.dmm
<<<<<<< HEAD
=======
#_maps/RandomRuins/SpaceRuins/derelict_construction.dmm
#_maps/RandomRuins/SpaceRuins/derelict_sulaco.dmm
>>>>>>> 88677006367 (New space ruin 'Derelict Construction' (#74308))
#_maps/RandomRuins/SpaceRuins/dj_station.dmm
#_maps/RandomRuins/SpaceRuins/emptyshell.dmm
#_maps/RandomRuins/SpaceRuins/thelizardsgas.dmm
@@ -53,11 +50,8 @@
#_maps/RandomRuins/SpaceRuins/pod_crash.dmm
#_maps/RandomRuins/SpaceRuins/russian_derelict.dmm
#_maps/RandomRuins/SpaceRuins/shuttlerelic.dmm
<<<<<<< HEAD
=======
#_maps/RandomRuins/SpaceRuins/space_billboard.dmm
#_maps/RandomRuins/SpaceRuins/space_ghost_restaurant.dmm
>>>>>>> 6ad516e53f1 (March into Mapness: Space Ghost Restaurant (#74182))
#_maps/RandomRuins/SpaceRuins/spacehotel.dmm
#_maps/RandomRuins/SpaceRuins/turretedoutpost.dmm
#_maps/RandomRuins/SpaceRuins/transit_booth.dmm
@@ -66,28 +60,19 @@
#_maps/RandomRuins/SpaceRuins/waystation.dmm
#_maps/RandomRuins/SpaceRuins/whiteshipdock.dmm
#_maps/RandomRuins/SpaceRuins/whiteshipruin_box.dmm
<<<<<<< HEAD
<<<<<<< HEAD
#_maps/RandomRuins/SpaceRuins/medieval1.dmm
#modular_skyrat/modules/mapping/_maps/RandomRuins/SpaceRuins/forgottenship_skyrat.dmm
#modular_skyrat/modules/mapping/_maps/RandomRuins/SpaceRuins/shuttle8532.dmm
#modular_skyrat/modules/ruins/maps/space/dangerous_research.dmm
=======
>>>>>>> 18eeaf06509 (Atmosia Asteroid Space Ruin (#74164))
#_maps/RandomRuins/SpaceRuins/forgottenship.dmm
#_maps/RandomRuins/SpaceRuins/hellfactory.dmm
#_maps/RandomRuins/SpaceRuins/space_billboard.dmm
#_maps/RandomRuins/SpaceRuins/spinwardsmoothies.dmm
#_maps/RandomRuins/SpaceRuins/dangerous_research.dmm
#_maps/RandomRuins/SpaceRuins/the_faceoff.dmm
<<<<<<< HEAD
#_maps/RandomRuins/SpaceRuins/prey_pod.dmm
#_maps/RandomRuins/SpaceRuins/travelers_rest.dmm
=======
#_maps/RandomRuins/SpaceRuins/atmosasteroidruin.dmm
#_maps/RandomRuins/SpaceRuins/prey_pod.dmm
#_maps/RandomRuins/SpaceRuins/travelers_rest.dmm
#_maps/RandomRuins/SpaceRuins/whiteshipruin_box.dmm
>>>>>>> 18eeaf06509 (Atmosia Asteroid Space Ruin (#74164))
=======
>>>>>>> 6440c2489a7 (Abandoned Interdyne research base (#74263))
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 25 KiB

+1
View File
@@ -2218,6 +2218,7 @@
#include "code\game\objects\structures\spawner.dm"
#include "code\game\objects\structures\spirit_board.dm"
#include "code\game\objects\structures\stairs.dm"
#include "code\game\objects\structures\syndicate_uplink_beacon.dm"
#include "code\game\objects\structures\table_frames.dm"
#include "code\game\objects\structures\tables_racks.dm"
#include "code\game\objects\structures\tank_dispenser.dm"
@@ -35,6 +35,8 @@ type Info = {
intro: string;
code: string;
failsafe_code: string;
replacement_code: string;
replacement_frequency: string;
has_uplink: BooleanLike;
uplink_intro: string;
uplink_unlock_info: string;
@@ -120,8 +122,15 @@ const EmployerSection = (props, context) => {
const UplinkSection = (props, context) => {
const { data } = useBackend<Info>(context);
const { has_uplink, uplink_intro, uplink_unlock_info, code, failsafe_code } =
data;
const {
has_uplink,
uplink_intro,
uplink_unlock_info,
code,
failsafe_code,
replacement_code,
replacement_frequency,
} = data;
return (
<Section title="Uplink" mb={!has_uplink && -1}>
<Stack fill>
@@ -149,6 +158,19 @@ const UplinkSection = (props, context) => {
</>
)}
</Stack>
<br />
<Section textAlign="Center">
If you lose your uplink, you can craft a Syndicate Uplink Beacon and
then speak{' '}
<span style={goalstyle}>
<b>{replacement_code}</b>
</span>{' '}
on radio frequency{' '}
<span style={goalstyle}>
<b>{replacement_frequency}</b>
</span>{' '}
after synchronizing with the beacon.
</Section>
</Section>
);
};