[BOUNTY] Re-adds Contractors (#79737)

## About The Pull Request

Adds Contractors back to the game as a kit that Infiltrators (Only
midround/latejoin Traitors, not roundstart) can buy. Buying this will
give you access to the new Contractor items in the uplink (which
replaces the Contractor shop).
I've also refactored things about contractor and how it's handled, such
as removed its sleep calls, swapping its UI to tsx, making it actually
functional within more modern TG code and the reworked Traitors as a
whole, among other things.

I also fixed the contractor guide paper (the text was broken) and made
contractor kit boxes no longer empty.

Contractor items only appear once the contractor kit is purchased, but
they can't be bought until you make an account.

Video demonstration

https://github.com/tgstation/tgstation/assets/53777086/c6aed4e8-bbd4-4ba0-b6a2-9f58fd308d09

Regular Traitors can't access it

![image](https://github.com/tgstation/tgstation/assets/53777086/539b8121-a1fa-4f2d-8e69-ec3d9708152c)

The Contractor UI (Modified to not have a shop anymore) - Ignore the
Invalid Targets, those appear when there's no possible targets

![image](https://github.com/tgstation/tgstation/assets/53777086/e128aecc-0795-4a6b-b8c5-e6f431401330)

Roundend report

![image](https://github.com/tgstation/tgstation/assets/53777086/71b88262-61de-4652-862b-79a7d5ded979)

## Why It's Good For The Game

Contractor was a fun way of playing Traitor that isn't relying on RNG
objectives to pop up, this is instead constant stream of content for the
contractor that encourages non-lethal play, and is now a viable
alternative now that Infiltrators don't have Reputation anymore.

Link to bounty: https://tgstation13.org/phpBB/viewtopic.php?f=5&t=35160

Relevant image from bounty

![image](https://github.com/tgstation/tgstation/assets/53777086/b26a17b0-2f10-4e53-b8fc-25cf8130584e)

## Changelog

🆑
add: Infiltrators (Latejoin/Midround traitors) can now buy and use
Contract kits again.
del: Contractor baton can now only be purchased once.
/🆑

---------

Co-authored-by: Jacquerel <hnevard@gmail.com>
This commit is contained in:
John Willard
2023-11-18 13:38:37 -05:00
committed by GitHub
parent 722a3a5067
commit 6bb2175fa5
37 changed files with 1237 additions and 85 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
#define PROGRAM_ALL ALL
#define PROGRAM_CONSOLE (1<<0)
#define PROGRAM_LAPTOP (1<<1)
#define PROGRAM_TABLET (1<<2)
#define PROGRAM_PDA (1<<2)
//Program categories
#define PROGRAM_CATEGORY_CREW "Crew"
#define PROGRAM_CATEGORY_ENGI "Engineering"
+3
View File
@@ -9,6 +9,9 @@
/// This item is purchasable to clown ops
#define UPLINK_CLOWN_OPS (1 << 2)
/// This item is purchasable to infiltrators (midround traitors)
#define UPLINK_INFILTRATORS (1 << 3)
/// Progression gets turned into a user-friendly form. This is just an abstract equation that makes progression not too large.
#define DISPLAY_PROGRESSION(time) round(time/60, 0.01)
+55 -57
View File
@@ -277,63 +277,6 @@
new /obj/item/implanter/freedom(src) // 5 tc
new /obj/item/stack/telecrystal(src) //The failsafe/self destruct isn't an item we can physically include in the kit, but 1 TC is technically enough to buy the equivalent.
/obj/item/storage/box/syndicate/contract_kit
name = "Contract Kit"
desc = "Supplied to Syndicate contractors."
icon_state = "syndiebox"
illustration = "writing_syndie"
/obj/item/storage/box/syndicate/contractor_loadout
name = "Standard Loadout"
desc = "Supplied to Syndicate contractors, providing their specialised space suit and chameleon uniform."
icon_state = "syndiebox"
illustration = "writing_syndie"
/obj/item/paper/contractor_guide
name = "Contractor Guide"
/obj/item/paper/contractor_guide/Initialize(mapload)
default_raw_text = {"<p>Welcome agent, congratulations on your new position as contractor. On top of your already assigned objectives,
this kit will provide you contracts to take on for TC payments.</p>
<p>Provided within, we give your specialist contractor space suit. It's even more compact, being able to fit into a pocket, and faster than the
Syndicate space suit available to you on the uplink. We also provide your chameleon jumpsuit and mask, both of which can be changed
to any form you need for the moment. The cigarettes are a special blend - it'll heal your injuries slowly overtime.</p>
<p>Your standard issue contractor baton hits harder than the ones you might be used to, and likely be your go to weapon for kidnapping your
targets. The three additional items have been randomly selected from what we had available. We hope they're useful to you for your mission.</p>
<p>The contractor hub, available at the top right of the uplink, will provide you unique items and abilities. These are bought using Contractor Rep,
with two Rep being provided each time you complete a contract.</p>
<h3>Using the tablet</h3>
<ol>
<li>Open the Syndicate Contract Uplink program.</li>
<li>Here, you can accept a contract, and redeem your TC payments from completed contracts.</li>
<li>The payment number shown in brackets is the bonus you'll receive when bringing your target <b>alive</b>. You receive the
other number regardless of if they were alive or dead.</li>
<li>Contracts are completed by bringing the target to designated dropoff, calling for extraction, and putting them
inside the pod.</li>
</ol>
<p>Be careful when accepting a contract. While you'll be able to see the location of the dropoff point, cancelling will make it
unavailable to take on again.</p>
<p>The tablet can also be recharged at any cell charger.</p>
<h3>Extracting</h3>
<ol>
<li>Make sure both yourself and your target are at the dropoff.</li>
<li>Call the extraction, and stand back from the drop point.</li>
<li>If it fails, make sure your target is inside, and there's a free space for the pod to land.</li>
<li>Grab your target, and drag them into the pod.</li>
</ol>
<h3>Ransoms</h3>
<p>We need your target for our own reasons, but we ransom them back to your mission area once their use is served. They will return back
from where you sent them off from in several minutes time. Don't worry, we give you a cut of what we get paid. We pay this into whatever
ID card you have equipped, on top of the TC payment we give.</p>
<p>Good luck agent. You can burn this document with the supplied lighter.</p>"}
return ..()
/obj/item/storage/box/syndie_kit
name = "box"
desc = "A sleek, sturdy box."
@@ -821,6 +764,61 @@
/obj/item/food/grown/apple = 1,
), src)
/obj/item/storage/box/syndicate/contract_kit
name = "Contract Kit"
desc = "Supplied to Syndicate contractors."
icon_state = "syndiebox"
illustration = "writing_syndie"
/obj/item/storage/box/syndicate/contract_kit/PopulateContents()
new /obj/item/modular_computer/pda/syndicate_contract_uplink(src)
new /obj/item/storage/box/syndicate/contractor_loadout(src)
new /obj/item/melee/baton/telescopic/contractor_baton(src)
// All about 4 TC or less - some nukeops only items, but fit nicely to the theme.
var/static/list/item_list = list(
/obj/item/storage/backpack/duffelbag/syndie/x4,
/obj/item/storage/box/syndie_kit/throwing_weapons,
/obj/item/gun/syringe/syndicate,
/obj/item/pen/edagger,
/obj/item/pen/sleepy,
/obj/item/flashlight/emp,
/obj/item/reagent_containers/syringe/mulligan,
/obj/item/clothing/shoes/chameleon/noslip,
/obj/item/storage/medkit/tactical,
/obj/item/encryptionkey/syndicate,
/obj/item/clothing/glasses/thermal/syndi,
/obj/item/slimepotion/slime/sentience/nuclear,
/obj/item/storage/box/syndie_kit/imp_radio,
/obj/item/storage/box/syndie_kit/imp_uplink,
/obj/item/clothing/gloves/krav_maga/combatglovesplus,
/obj/item/gun/ballistic/automatic/c20r/toy/unrestricted/riot,
/obj/item/reagent_containers/hypospray/medipen/stimulants,
/obj/item/storage/box/syndie_kit/imp_freedom,
/obj/item/toy/eightball/haunted,
)
for(var/i in 1 to 3)
var/selected_item = pick_n_take(item_list)
new selected_item(src)
// Paper guide is always last.
new /obj/item/paper/contractor_guide(src)
/obj/item/storage/box/syndicate/contractor_loadout
name = "Standard Loadout"
desc = "Supplied to Syndicate contractors, providing their specialised space suit and chameleon uniform."
icon_state = "syndiebox"
illustration = "writing_syndie"
/obj/item/storage/box/syndicate/contractor_loadout/PopulateContents()
new /obj/item/clothing/head/helmet/space/syndicate/contract(src)
new /obj/item/clothing/suit/space/syndicate/contract(src)
new /obj/item/clothing/under/chameleon(src)
new /obj/item/clothing/mask/chameleon(src)
new /obj/item/storage/fancy/cigarettes/cigpack_syndicate(src)
new /obj/item/card/id/advanced/chameleon(src)
new /obj/item/lighter(src)
#undef KIT_RECON
#undef KIT_BLOODY_SPAI
#undef KIT_STEALTHY
@@ -0,0 +1,72 @@
///Spawns a contractor partner to a spawning user, with a given key to assign to the new player.
/proc/spawn_contractor_partner(mob/living/user, key)
var/mob/living/carbon/human/partner = new()
var/datum/outfit/contractor_partner/partner_outfit = new()
partner_outfit.equip(partner)
var/obj/structure/closet/supplypod/arrival_pod = new(null, STYLE_SYNDICATE)
arrival_pod.explosionSize = list(0,0,0,1)
arrival_pod.bluespace = TRUE
var/turf/free_location = find_obstruction_free_location(2, user)
// We really want to send them - if we can't find a nice location just land it on top of them.
if (!free_location)
free_location = get_turf(user)
partner.forceMove(arrival_pod)
partner.ckey = key
/// We give a reference to the mind that'll be the support unit
var/datum/antagonist/traitor/contractor_support/new_datum = partner.mind.add_antag_datum(/datum/antagonist/traitor/contractor_support)
to_chat(partner, "\n[span_alertwarning("[user.real_name] is your superior. Follow any, and all orders given by them. You're here to support their mission only.")]")
to_chat(partner, "[span_alertwarning("Should they perish, or be otherwise unavailable, you're to assist other active agents in this mission area to the best of your ability.")]")
new /obj/effect/pod_landingzone(free_location, arrival_pod)
return new_datum
/// Support unit gets it's own very basic antag datum for admin logging.
/datum/antagonist/traitor/contractor_support
name = "Contractor Support Unit"
show_in_roundend = FALSE
give_objectives = TRUE
give_uplink = FALSE
/datum/antagonist/traitor/contractor_support/forge_traitor_objectives()
var/datum/objective/generic_objective = new
generic_objective.name = "Follow Contractor's Orders"
generic_objective.explanation_text = "Follow your orders. Assist agents in this mission area."
generic_objective.completed = TRUE
objectives += generic_objective
/datum/antagonist/traitor/contractor_support/forge_ending_objective()
return
/datum/outfit/contractor_partner
name = "Contractor Support Unit"
uniform = /obj/item/clothing/under/chameleon
suit = /obj/item/clothing/suit/chameleon
back = /obj/item/storage/backpack
belt = /obj/item/modular_computer/pda/chameleon
mask = /obj/item/clothing/mask/cigarette/syndicate
shoes = /obj/item/clothing/shoes/chameleon/noslip
ears = /obj/item/radio/headset/chameleon
id = /obj/item/card/id/advanced/chameleon
r_hand = /obj/item/storage/toolbox/syndicate
id_trim = /datum/id_trim/chameleon/operative
backpack_contents = list(
/obj/item/storage/box/survival,
/obj/item/implanter/uplink,
/obj/item/clothing/mask/chameleon,
/obj/item/storage/fancy/cigarettes/cigpack_syndicate,
/obj/item/lighter,
)
/datum/outfit/contractor_partner/post_equip(mob/living/carbon/human/H, visualsOnly)
. = ..()
var/obj/item/clothing/mask/cigarette/syndicate/cig = H.get_item_by_slot(ITEM_SLOT_MASK)
cig.light()
@@ -0,0 +1,69 @@
/datum/contractor_hub
///The current contract in progress, and can be null if no contract is in progress.
var/datum/syndicate_contract/current_contract
///List of all available syndicate contracts that can be taken.
var/list/datum/syndicate_contract/assigned_contracts = list()
///Reference to a contractor teammate, if one has been purchased.
var/datum/antagonist/traitor/contractor_support/contractor_teammate
///List of all people currently used as targets, to not roll doubles.
var/list/assigned_targets = list()
///Amount of contracts that have already been completed, for flavor in the UI & round-end logs.
var/contracts_completed = 0
///How much TC has been paid out, for flavor in the UI & round-end logs.
var/contract_TC_payed_out = 0
///How much TC we can cash out currently. Used when redeeming TC and for round-end logs.
var/contract_TC_to_redeem = 0
/datum/contractor_hub/proc/create_contracts(datum/mind/owner)
// 6 initial contracts
var/list/to_generate = list(
CONTRACT_PAYOUT_LARGE,
CONTRACT_PAYOUT_MEDIUM,
CONTRACT_PAYOUT_SMALL,
CONTRACT_PAYOUT_SMALL,
CONTRACT_PAYOUT_SMALL,
CONTRACT_PAYOUT_SMALL
)
//What the fuck
if(length(to_generate) > length(GLOB.manifest.locked))
to_generate.Cut(1, length(GLOB.manifest.locked))
// We don't want the sum of all the payouts to be under this amount
var/lowest_TC_threshold = 30
var/total = 0
var/lowest_paying_sum = 0
var/datum/syndicate_contract/lowest_paying_contract
// Randomise order, so we don't have contracts always in payout order.
to_generate = shuffle(to_generate)
// Support contract generation happening multiple times
var/start_index = 1
if (assigned_contracts.len != 0)
start_index = assigned_contracts.len + 1
// Generate contracts, and find the lowest paying.
for(var/i in 1 to to_generate.len)
var/datum/syndicate_contract/contract_to_add = new(owner, assigned_targets, to_generate[i])
var/contract_payout_total = contract_to_add.contract.payout + contract_to_add.contract.payout_bonus
assigned_targets.Add(contract_to_add.contract.target)
if (!lowest_paying_contract || (contract_payout_total < lowest_paying_sum))
lowest_paying_sum = contract_payout_total
lowest_paying_contract = contract_to_add
total += contract_payout_total
contract_to_add.id = start_index
assigned_contracts.Add(contract_to_add)
start_index++
// If the threshold for TC payouts isn't reached, boost the lowest paying contract
if (total < lowest_TC_threshold)
lowest_paying_contract.contract.payout_bonus += (lowest_TC_threshold - total)
@@ -0,0 +1,53 @@
/obj/item/pinpointer/crew/contractor
name = "contractor pinpointer"
desc = "A handheld tracking device that locks onto certain signals. Ignores suit sensors, but is much less accurate."
icon_state = "pinpointer_syndicate"
worn_icon_state = "pinpointer_black"
minimum_range = 25
has_owner = TRUE
ignore_suit_sensor_level = TRUE
/obj/item/storage/box/contractor/fulton_extraction
name = "Fulton Extraction Kit"
icon_state = "syndiebox"
illustration = "writing_syndie"
/obj/item/storage/box/contractor/fulton_extraction/PopulateContents()
new /obj/item/extraction_pack/syndicate(src)
new /obj/item/fulton_core(src)
/obj/item/paper/contractor_guide
name = "Contractor Guide"
default_raw_text = {"Welcome agent, congratulations on your new position as contractor. On top of your already assigned objectives,\
this kit will provide you contracts to take on for TC payments.\
<p>Provided within, we give your specialist contractor space suit. It's even more compact, being able to fit into a pocket, and faster than the\
Syndicate space suit available to you on the uplink. We also provide your chameleon jumpsuit and mask, both of which can be changed\
to any form you need for the moment. The cigarettes are a special blend - it'll heal your injuries slowly overtime.</p>\
<p>Your standard issue contractor baton hits harder than the ones you might be used to, and likely be your go to weapon for kidnapping your\
targets. The three additional items have been randomly selected from what we had available. We hope they're useful to you for your mission.</p>\
<p>The contractor hub, available at the top right of the uplink, will provide you unique items and abilities. These are bought using Contractor Rep,\
with two Rep being provided each time you complete a contract.</p>\
<h3>Using the tablet</h3>\
<ol>\
<li>Open the Syndicate Contract Uplink program.</li>\
<li>Here, you can accept a contract, and redeem your TC payments from completed contracts.</li>\
<li>The payment number shown in brackets is the bonus you'll receive when bringing your target <b>alive</b>. You receive the\
other number regardless of if they were alive or dead.</li>\
<li>Contracts are completed by bringing the target to designated dropoff, calling for extraction, and putting them\
inside the pod.</li>\
</ol>\
<p>Be careful when accepting a contract. While you'll be able to see the location of the dropoff point, cancelling will make it\
unavailable to take on again.</p>\
<p>The tablet can also be recharged at any cell charger.</p>\
<h3>Extracting</h3>\
<ol>\
<li>Make sure both yourself and your target are at the dropoff.</li>\
<li>Call the extraction, and stand back from the drop point.</li>\
<li>If it fails, make sure your target is inside, and there's a free space for the pod to land.</li>\
<li>Grab your target, and drag them into the pod.</li>\
</ol>\
<h3>Ransoms</h3>\
<p>We need your target for our own reasons, but we ransom them back to your mission area once their use is served. They will return back\
from where you sent them off from in several minutes time. Don't worry, we give you a cut of what we get paid. We pay this into whatever\
ID card you have equipped, on top of the TC payment we give.</p>\
<p>Good luck agent. You can burn this document with the supplied lighter.</p>"}
@@ -0,0 +1,271 @@
/datum/syndicate_contract
///The 'id' of this particular contract. Used to keep track of statuses from TGUI.
var/id
///The current status of the contract. Starts off by default.
var/status = CONTRACT_STATUS_INACTIVE
///The related Objective datum for the contract, holding the target and such.
var/datum/objective/contract/contract
///The job position of the target.
var/target_rank
///How much we will pay out upon completion. This is not the TC completion, it's typically credits.
var/ransom = 0
///The level of payout, which affects the TC we get paid on completion.
var/payout_type
///Flavortext wanted message for the person we're after.
var/wanted_message
///List of everything found on the victim at the time of contracting, used to return their stuff afterwards.
var/list/victim_belongings = list()
/datum/syndicate_contract/New(contract_owner, blacklist, type=CONTRACT_PAYOUT_SMALL)
contract = new(src)
contract.owner = contract_owner
payout_type = type
generate(blacklist)
/datum/syndicate_contract/proc/generate(blacklist)
contract.find_target(null, blacklist)
var/datum/record/crew/record
if (contract.target)
record = find_record(contract.target.name)
if (record)
target_rank = record.rank
else
target_rank = "Unknown"
if (payout_type == CONTRACT_PAYOUT_LARGE)
contract.payout_bonus = rand(9,13)
else if (payout_type == CONTRACT_PAYOUT_MEDIUM)
contract.payout_bonus = rand(6,8)
else
contract.payout_bonus = rand(2,4)
contract.payout = rand(0, 2)
contract.generate_dropoff()
ransom = 100 * rand(18, 45)
var/base = pick_list(WANTED_FILE, "basemessage")
var/verb_string = pick_list(WANTED_FILE, "verb")
var/noun = pick_list_weighted(WANTED_FILE, "noun")
var/location = pick_list_weighted(WANTED_FILE, "location")
wanted_message = "[base] [verb_string] [noun] [location]."
/datum/syndicate_contract/proc/handle_extraction(mob/living/user)
if (contract.target && contract.dropoff_check(user, contract.target.current))
var/turf/free_location = find_obstruction_free_location(3, user, contract.dropoff)
if (free_location)
// We've got a valid location, launch.
launch_extraction_pod(free_location)
return TRUE
return FALSE
// Launch the pod to collect our victim.
/datum/syndicate_contract/proc/launch_extraction_pod(turf/empty_pod_turf)
var/obj/structure/closet/supplypod/extractionpod/empty_pod = new()
RegisterSignal(empty_pod, COMSIG_ATOM_ENTERED, PROC_REF(enter_check))
empty_pod.stay_after_drop = TRUE
empty_pod.reversing = TRUE
empty_pod.explosionSize = list(0,0,0,1)
empty_pod.leavingSound = 'sound/effects/podwoosh.ogg'
new /obj/effect/pod_landingzone(empty_pod_turf, empty_pod)
/datum/syndicate_contract/proc/enter_check(datum/source, sent_mob)
SIGNAL_HANDLER
if(!istype(source, /obj/structure/closet/supplypod/extractionpod))
return
if(!isliving(sent_mob))
return
var/mob/living/person_sent = sent_mob
var/datum/antagonist/traitor/traitor_data = contract.owner.has_antag_datum(/datum/antagonist/traitor)
if(person_sent == contract.target.current)
traitor_data.uplink_handler.contractor_hub.contract_TC_to_redeem += contract.payout
traitor_data.uplink_handler.contractor_hub.contracts_completed++
if(person_sent.stat != DEAD)
traitor_data.uplink_handler.contractor_hub.contract_TC_to_redeem += contract.payout_bonus
status = CONTRACT_STATUS_COMPLETE
if(traitor_data.uplink_handler.contractor_hub.current_contract == src)
traitor_data.uplink_handler.contractor_hub.current_contract = null
else
status = CONTRACT_STATUS_ABORTED // Sending a target that wasn't even yours is as good as just aborting it
if(traitor_data.uplink_handler.contractor_hub.current_contract == src)
traitor_data.uplink_handler.contractor_hub.current_contract = null
if(iscarbon(person_sent))
for(var/obj/item/person_contents in person_sent.gather_belongings())
if(ishuman(person_sent))
var/mob/living/carbon/human/human_sent = person_sent
if(person_contents == human_sent.w_uniform)
continue //So all they're left with are shoes and uniform.
if(person_contents == human_sent.shoes)
continue
person_sent.transferItemToLoc(person_contents)
victim_belongings.Add(WEAKREF(person_contents))
var/obj/structure/closet/supplypod/extractionpod/pod = source
// Handle the pod returning
pod.startExitSequence(pod)
if(ishuman(person_sent))
var/mob/living/carbon/human/target = person_sent
// After we remove items, at least give them what they need to live.
target.dna.species.give_important_for_life(target)
//we'll start the effects in a few seconds since it takes a moment for the pod to leave.
addtimer(CALLBACK(src, PROC_REF(handle_victim_experience), person_sent), 3 SECONDS)
// This is slightly delayed because of the sleep calls above to handle the narrative.
// We don't want to tell the station instantly.
var/points_to_check
var/datum/bank_account/cargo_account = SSeconomy.get_dep_account(ACCOUNT_CAR)
if(cargo_account)
points_to_check = cargo_account.account_balance
if(points_to_check >= ransom)
cargo_account.adjust_money(-ransom)
else
cargo_account.adjust_money(-points_to_check)
priority_announce(
text = "One of your crew was captured by a rival organisation - we've needed to pay their ransom to bring them back. \
As is policy we've taken a portion of the station's funds to offset the overall cost.",
sender_override = "Nanotrasen Asset Protection")
addtimer(CALLBACK(src, PROC_REF(finish_enter)), 3 SECONDS)
/datum/syndicate_contract/proc/finish_enter()
// Pay contractor their portion of ransom
if(status != CONTRACT_STATUS_COMPLETE)
return
var/obj/item/card/id/contractor_id = contract.owner.current?.get_idcard(TRUE)
if(!contractor_id || !contractor_id.registered_account)
return
contractor_id.registered_account.adjust_money(ransom * 0.35)
contractor_id.registered_account.bank_card_talk("We've processed the ransom, agent. \
Here's your cut - your balance is now [contractor_id.registered_account.account_balance] cr.", TRUE)
#define VICTIM_EXPERIENCE_START 0
#define VICTIM_EXPERIENCE_FIRST_HIT 1
#define VICTIM_EXPERIENCE_SECOND_HIT 2
#define VICTIM_EXPERIENCE_THIRD_HIT 3
#define VICTIM_EXPERIENCE_LAST_HIT 4
/**
* handle_victim_experience
*
* Handles the effects given to victims upon being contracted.
* We heal them up and cause them immersive effects, just for fun.
* Args:
* victim - The person we're harassing
* level - The current stage of harassement they are facing. This increases by itself, looping until finished.
*/
/datum/syndicate_contract/proc/handle_victim_experience(mob/living/victim, level = VICTIM_EXPERIENCE_START)
// Ship 'em back - dead or alive, 4 minutes wait.
// Even if they weren't the target, we're still treating them the same.
if(!level)
addtimer(CALLBACK(src, PROC_REF(return_victim), victim), (60 * 10) * 4)
if(victim.stat == DEAD)
return
var/time_until_next
switch(level)
if(VICTIM_EXPERIENCE_START)
// Heal them up - gets them out of crit/soft crit. If omnizine is removed in the future, this needs to be replaced with a
// method of healing them, consequence free, to a reasonable amount of health.
victim.reagents.add_reagent(/datum/reagent/medicine/omnizine, amount = 20)
victim.flash_act()
victim.adjust_confusion(1 SECONDS)
victim.adjust_eye_blur(5 SECONDS)
to_chat(victim, span_warning("You feel strange..."))
time_until_next = 6 SECONDS
if(VICTIM_EXPERIENCE_FIRST_HIT)
to_chat(victim, span_warning("That pod did something to you..."))
victim.adjust_dizzy(3.5 SECONDS)
time_until_next = 6.5 SECONDS
if(VICTIM_EXPERIENCE_SECOND_HIT)
to_chat(victim, span_warning("Your head pounds... It feels like it's going to burst out your skull!"))
victim.flash_act()
victim.adjust_confusion(2 SECONDS)
victim.adjust_eye_blur(3 SECONDS)
time_until_next = 3 SECONDS
if(VICTIM_EXPERIENCE_THIRD_HIT)
to_chat(victim, span_warning("Your head pounds..."))
time_until_next = 10 SECONDS
if(VICTIM_EXPERIENCE_LAST_HIT)
victim.flash_act()
victim.Unconscious(200)
to_chat(victim, span_hypnophrase("A million voices echo in your head... <i>\"Your mind held many valuable secrets - \
we thank you for providing them. Your value is expended, and you will be ransomed back to your station. We always get paid, \
so it's only a matter of time before we ship you back...\"</i>"))
victim.adjust_eye_blur(10 SECONDS)
victim.adjust_dizzy(1.5 SECONDS)
victim.adjust_confusion(2 SECONDS)
level++ //move onto the next level.
if(time_until_next)
addtimer(CALLBACK(src, PROC_REF(handle_victim_experience), victim, level), time_until_next)
#undef VICTIM_EXPERIENCE_START
#undef VICTIM_EXPERIENCE_FIRST_HIT
#undef VICTIM_EXPERIENCE_SECOND_HIT
#undef VICTIM_EXPERIENCE_THIRD_HIT
#undef VICTIM_EXPERIENCE_LAST_HIT
// We're returning the victim
/datum/syndicate_contract/proc/return_victim(mob/living/victim)
var/list/possible_drop_loc = list()
for(var/turf/possible_drop in contract.dropoff.contents)
if(!isspaceturf(possible_drop) && !isclosedturf(possible_drop))
if(!possible_drop.is_blocked_turf())
possible_drop_loc.Add(possible_drop)
if(!possible_drop_loc.len)
to_chat(victim, span_hypnophrase("A million voices echo in your head... \"Seems where you got sent here from won't \
be able to handle our pod... You will die here instead.\""))
if(iscarbon(victim))
var/mob/living/carbon/carbon_victim = victim
if(carbon_victim.can_heartattack())
carbon_victim.set_heartattack(TRUE)
return
var/pod_rand_loc = rand(1, possible_drop_loc.len)
var/obj/structure/closet/supplypod/return_pod = new()
return_pod.bluespace = TRUE
return_pod.explosionSize = list(0,0,0,0)
return_pod.style = STYLE_SYNDICATE
do_sparks(8, FALSE, victim)
victim.visible_message(span_notice("[victim] vanishes..."))
for(var/datum/weakref/belonging_ref in victim_belongings)
var/obj/item/belonging = belonging_ref.resolve()
if(!belonging)
continue
if(ishuman(victim))
var/mob/living/carbon/human/human_victim = victim
//So all they're left with are shoes and uniform.
if(belonging == human_victim.w_uniform)
continue
if(belonging == human_victim.shoes)
continue
belonging.forceMove(return_pod)
for(var/obj/item/W in victim_belongings)
W.forceMove(return_pod)
victim.forceMove(return_pod)
victim.flash_act()
victim.adjust_eye_blur(3 SECONDS)
victim.adjust_dizzy(3.5 SECONDS)
victim.adjust_confusion(2 SECONDS)
new /obj/effect/pod_landingzone(possible_drop_loc[pod_rand_loc], return_pod)
@@ -17,6 +17,10 @@
can_assign_self_objectives = TRUE
default_custom_objective = "Perform an overcomplicated heist on valuable Nanotrasen assets."
hardcore_random_bonus = TRUE
///The flag of uplink that this traitor is supposed to have.
var/uplink_flag_given = UPLINK_TRAITORS
var/give_objectives = TRUE
/// Whether to give secondary objectives to the traitor, which aren't necessary but can be completed for a progression and TC boost.
var/give_secondary_objectives = TRUE
@@ -53,6 +57,7 @@
// There will still be a timelock on uplink items
name = "\improper Infiltrator"
give_secondary_objectives = FALSE
uplink_flag_given = UPLINK_TRAITORS | UPLINK_INFILTRATORS
/datum/antagonist/traitor/infiltrator/sleeper_agent
name = "\improper Syndicate Sleeper Agent"
@@ -75,6 +80,7 @@
uplink.uplink_handler = uplink_handler
else
uplink_handler = uplink.uplink_handler
uplink_handler.uplink_flag = uplink_flag_given
uplink_handler.primary_objectives = objectives
uplink_handler.has_progression = TRUE
SStraitor.register_uplink_handler(uplink_handler)
@@ -351,6 +357,8 @@
result += objectives_text
if(uplink_handler)
if (uplink_handler.contractor_hub)
result += contractor_round_end()
result += "<br>The traitor had a total of [DISPLAY_PROGRESSION(uplink_handler.progression_points)] Reputation and [uplink_handler.telecrystals] Unused Telecrystals."
var/special_role_text = lowertext(name)
@@ -363,6 +371,23 @@
return result.Join("<br>")
///Tells how many contracts have been completed.
/datum/antagonist/traitor/proc/contractor_round_end()
var/completed_contracts = uplink_handler.contractor_hub.contracts_completed
var/tc_total = uplink_handler.contractor_hub.contract_TC_payed_out + uplink_handler.contractor_hub.contract_TC_to_redeem
var/datum/antagonist/traitor/contractor_support/contractor_support_unit = uplink_handler.contractor_hub.contractor_teammate
if(completed_contracts <= 0)
return
var/plural_check = "contract"
if (completed_contracts > 1)
plural_check = "contracts"
var/sent_data = "Completed [span_greentext("[completed_contracts]")] [plural_check] for a total of [span_greentext("[tc_total] TC")]!<br>"
if(contractor_support_unit)
sent_data += "<b>[contractor_support_unit.owner.key]</b> played <b>[contractor_support_unit.owner.current.name]</b>, their contractor support unit.<br>"
return sent_data
/datum/antagonist/traitor/roundend_report_footer()
var/phrases = jointext(GLOB.syndicate_code_phrase, ", ")
var/responses = jointext(GLOB.syndicate_code_response, ", ")
@@ -227,7 +227,7 @@
if(sent_mob.mind)
ADD_TRAIT(sent_mob.mind, TRAIT_HAS_BEEN_KIDNAPPED, TRAIT_GENERIC)
for(var/obj/item/belonging in gather_belongings(sent_mob))
for(var/obj/item/belonging in sent_mob.gather_belongings())
if(belonging == sent_mob.get_item_by_slot(ITEM_SLOT_ICLOTHING) || belonging == sent_mob.get_item_by_slot(ITEM_SLOT_FEET))
continue
@@ -298,7 +298,7 @@
do_sparks(8, FALSE, sent_mob)
sent_mob.visible_message(span_notice("[sent_mob] vanishes!"))
for(var/obj/item/belonging in gather_belongings(sent_mob))
for(var/obj/item/belonging in sent_mob.gather_belongings())
if(belonging == sent_mob.get_item_by_slot(ITEM_SLOT_ICLOTHING) || belonging == sent_mob.get_item_by_slot(ITEM_SLOT_FEET))
continue
sent_mob.dropItemToGround(belonging) // No souvenirs, except shoes and t-shirts
@@ -317,10 +317,3 @@
sent_mob.dna.species.give_important_for_life(sent_mob) // so plasmamen do not get left for dead
new /obj/effect/pod_landingzone(pick(possible_turfs), return_pod)
/// Returns a list of things that the provided mob has which we would rather that they do not have
/datum/traitor_objective/target_player/kidnapping/proc/gather_belongings(mob/living/carbon/human/kidnapee)
var/list/belongings = kidnapee.get_all_gear()
for (var/obj/item/implant/storage/internal_bag in kidnapee.implants)
belongings += internal_bag.contents
return belongings
@@ -52,6 +52,8 @@
var/datum/callback/can_replace_objectives
/// Callback which performs that operation
var/datum/callback/replace_objectives
///Reference to a contractor hub that the infiltrator can run, if they purchase it.
var/datum/contractor_hub/contractor_hub
/datum/uplink_handler/New()
. = ..()
@@ -73,6 +75,8 @@
/// Checks for uplink flags as well as items restricted to roles and species
/datum/uplink_handler/proc/check_if_restricted(datum/uplink_item/to_purchase)
if(!to_purchase.can_be_bought(src))
return FALSE
if((to_purchase in extra_purchasable))
return TRUE
if(!(to_purchase.purchasable_from & uplink_flag))
@@ -99,7 +103,7 @@
return FALSE
var/current_stock = item_stock[to_purchase.stock_key]
var/stock = current_stock != null? current_stock : INFINITY
var/stock = current_stock != null ? current_stock : INFINITY
if(telecrystals < to_purchase.cost || stock <= 0 || not_enough_reputation(to_purchase))
return FALSE
+4
View File
@@ -243,3 +243,7 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons)
/obj/effect/extraction_holder/singularity_pull()
return
/obj/item/extraction_pack/syndicate
name = "syndicate fulton extraction pack"
can_use_indoors = TRUE
+9 -2
View File
@@ -544,14 +544,21 @@
..() //Don't redraw hands until we have organs for them
//GetAllContents that is reasonable and not stupid
/mob/living/carbon/proc/get_all_gear()
/mob/living/proc/get_all_gear()
var/list/processing_list = get_equipped_items(include_pockets = TRUE, include_accessories = TRUE) + held_items
list_clear_nulls(processing_list) // handles empty hands
var/i = 0
while(i < length(processing_list) )
while(i < length(processing_list))
var/atom/A = processing_list[++i]
if(A.atom_storage)
var/list/item_stuff = list()
A.atom_storage.return_inv(item_stuff)
processing_list += item_stuff
return processing_list
/// Returns a list of things that the provided mob has, including any storage-capable implants.
/mob/living/proc/gather_belongings()
var/list/belongings = get_all_gear()
for (var/obj/item/implant/storage/internal_bag in implants)
belongings += internal_bag.contents
return belongings
@@ -48,7 +48,7 @@
///Flag of the type of device the modular computer is, deciding what types of apps it can run.
var/hardware_flag = NONE
// Options: PROGRAM_ALL | PROGRAM_CONSOLE | PROGRAM_LAPTOP | PROGRAM_TABLET
// Options: PROGRAM_ALL | PROGRAM_CONSOLE | PROGRAM_LAPTOP | PROGRAM_PDA
///The theme, used for the main menu and file browser apps.
var/device_theme = PDA_THEME_NTOS
@@ -6,3 +6,6 @@
/obj/item/computer_disk/syndicate/camera_app
starting_programs = list(/datum/computer_file/program/secureye/syndicate)
/obj/item/computer_disk/syndicate/contractor
starting_programs = list(/datum/computer_file/program/contract_uplink)
@@ -18,7 +18,7 @@
icon_state_menu = "menu"
max_capacity = 64
allow_chunky = TRUE
hardware_flag = PROGRAM_TABLET
hardware_flag = PROGRAM_PDA
max_idle_programs = 2
w_class = WEIGHT_CLASS_SMALL
slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT
@@ -272,6 +272,19 @@
if(msg)
msg.invisible = TRUE
/obj/item/modular_computer/pda/syndicate_contract_uplink
name = "contractor tablet"
device_theme = PDA_THEME_SYNDICATE
icon_state_menu = "contractor-assign"
comp_light_luminosity = 6.3
has_pda_programs = FALSE
greyscale_config = /datum/greyscale_config/tablet/stripe_double
greyscale_colors = "#696969#000000#FFA500"
starting_programs = list(
/datum/computer_file/program/contract_uplink,
)
/**
* Silicon PDA
*
@@ -44,7 +44,7 @@ This is how the base program is setup. the rest is mostly tgui stuff. I'll use t
/// ditto but on emagged syndie net. Use this for antag programs
available_on_syndinet = 0
/// Bitflags (PROGRAM_CONSOLE, PROGRAM_LAPTOP, PROGRAM_TABLET combination)
/// Bitflags (PROGRAM_CONSOLE, PROGRAM_LAPTOP, PROGRAM_PDA combination)
/// or PROGRAM_ALL. Use this to limit what kind of machines can run the
/// program. For example, comms program should be limited to consoles and laptops.
usage_flags = PROGRAM_ALL
@@ -28,7 +28,7 @@
var/requires_ntnet = FALSE
/// NTNet status, updated every tick by computer running this program. Don't use this for checks if NTNet works, computers do that. Use this for calculations, etc.
var/ntnet_status = 1
/// Bitflags (PROGRAM_CONSOLE, PROGRAM_LAPTOP, PROGRAM_TABLET combination) or PROGRAM_ALL
/// Bitflags (PROGRAM_CONSOLE, PROGRAM_LAPTOP, PROGRAM_PDA combination) or PROGRAM_ALL
var/usage_flags = PROGRAM_ALL
/// Whether the program can be downloaded from NTNet. Set to FALSE to disable.
var/available_on_ntnet = TRUE
@@ -0,0 +1,173 @@
/datum/computer_file/program/contract_uplink
filename = "contractor uplink"
filedesc = "Syndicate Contractor Uplink"
extended_desc = "A standard, Syndicate issued system for handling important contracts while on the field."
category = PROGRAM_CATEGORY_MISC
program_icon_state = "contractor-assign"
program_icon = "tasks"
size = 10
requires_ntnet = FALSE
available_on_ntnet = FALSE
available_on_syndinet = FALSE
usage_flags = PROGRAM_PDA //this is all we've got sprites for :sob:
unique_copy = TRUE
undeletable = TRUE
tgui_id = "SyndicateContractor"
///The traitor datum stored on the program. Starts off as null and is set by the player.
var/datum/antagonist/traitor/traitor_data
///The error screen sent to the UI so they can show the player.
var/error = ""
///Boolean on whether the UI is on the Information screen.
var/info_screen = TRUE
///Boolean on whether the program is being loaded for the first time, for a unique screen animation.
var/first_load = TRUE
/datum/computer_file/program/contract_uplink/clone()
var/datum/computer_file/program/contract_uplink/temp = ..()
temp.traitor_data = traitor_data
return temp
/datum/computer_file/program/contract_uplink/Destroy(force)
traitor_data = null
return ..()
/datum/computer_file/program/contract_uplink/ui_act(action, params)
. = ..()
if(.)
return
var/mob/living/user = usr
switch(action)
if("PRG_contract-accept")
var/contract_id = text2num(params["contract_id"])
traitor_data.uplink_handler.contractor_hub.assigned_contracts[contract_id].status = CONTRACT_STATUS_ACTIVE
traitor_data.uplink_handler.contractor_hub.current_contract = traitor_data.uplink_handler.contractor_hub.assigned_contracts[contract_id]
program_icon_state = "contractor-contract"
return TRUE
if("PRG_login")
var/datum/antagonist/traitor/traitor_user = user.mind.has_antag_datum(/datum/antagonist/traitor)
if(!traitor_user)
error = "UNAUTHORIZED USER"
return TRUE
traitor_data = traitor_user
if(!traitor_data.uplink_handler.contractor_hub)
traitor_data.uplink_handler.contractor_hub = new
traitor_data.uplink_handler.contractor_hub.create_contracts(traitor_user.owner)
user.playsound_local(user, 'sound/effects/contractstartup.ogg', 100, FALSE)
program_icon_state = "contractor-contractlist"
return TRUE
if("PRG_call_extraction")
if (traitor_data.uplink_handler.contractor_hub.current_contract.status != CONTRACT_STATUS_EXTRACTING)
if (traitor_data.uplink_handler.contractor_hub.current_contract.handle_extraction(user))
user.playsound_local(user, 'sound/effects/confirmdropoff.ogg', 100, TRUE)
traitor_data.uplink_handler.contractor_hub.current_contract.status = CONTRACT_STATUS_EXTRACTING
program_icon_state = "contractor-extracted"
else
user.playsound_local(user, 'sound/machines/uplinkerror.ogg', 50)
error = "Either both you or your target aren't at the dropoff location, or the pod hasn't got a valid place to land. Clear space, or make sure you're both inside."
else
user.playsound_local(user, 'sound/machines/uplinkerror.ogg', 50)
error = "Already extracting... Place the target into the pod. If the pod was destroyed, this contract is no longer possible."
return TRUE
if("PRG_contract_abort")
var/contract_id = traitor_data.uplink_handler.contractor_hub.current_contract.id
traitor_data.uplink_handler.contractor_hub.current_contract = null
traitor_data.uplink_handler.contractor_hub.assigned_contracts[contract_id].status = CONTRACT_STATUS_ABORTED
program_icon_state = "contractor-contractlist"
return TRUE
if("PRG_redeem_TC")
if (traitor_data.uplink_handler.contractor_hub.contract_TC_to_redeem)
var/obj/item/stack/telecrystal/crystals = new /obj/item/stack/telecrystal(get_turf(user), traitor_data.uplink_handler.contractor_hub.contract_TC_to_redeem)
if(ishuman(user))
var/mob/living/carbon/human/H = user
if(H.put_in_hands(crystals))
to_chat(H, span_notice("Your payment materializes into your hands!"))
else
to_chat(user, span_notice("Your payment materializes onto the floor."))
traitor_data.uplink_handler.contractor_hub.contract_TC_payed_out += traitor_data.uplink_handler.contractor_hub.contract_TC_to_redeem
traitor_data.uplink_handler.contractor_hub.contract_TC_to_redeem = 0
return TRUE
else
user.playsound_local(user, 'sound/machines/uplinkerror.ogg', 50)
return TRUE
if ("PRG_clear_error")
error = ""
return TRUE
if("PRG_set_first_load_finished")
first_load = FALSE
return TRUE
if("PRG_toggle_info")
info_screen = !info_screen
return TRUE
/datum/computer_file/program/contract_uplink/ui_data(mob/user)
var/list/data = list()
data["first_load"] = first_load
data["logged_in"] = !!traitor_data
data["station_name"] = GLOB.station_name
data["info_screen"] = info_screen
data["error"] = error
if(!traitor_data)
data["ongoing_contract"] = FALSE
data["extraction_enroute"] = FALSE
update_computer_icon()
return data
data["ongoing_contract"] = !!traitor_data.uplink_handler.contractor_hub.current_contract
if(traitor_data.uplink_handler.contractor_hub.current_contract)
program_icon_state = "contractor-contract"
if (traitor_data.uplink_handler.contractor_hub.current_contract.status == CONTRACT_STATUS_EXTRACTING)
data["extraction_enroute"] = TRUE
program_icon_state = "contractor-extracted"
else
data["extraction_enroute"] = FALSE
var/turf/curr = get_turf(user)
var/turf/dropoff_turf
data["current_location"] = "[get_area_name(curr, TRUE)]"
for (var/turf/content in traitor_data.uplink_handler.contractor_hub.current_contract.contract.dropoff.contents)
if (isturf(content))
dropoff_turf = content
break
var/direction
if(curr.z == dropoff_turf.z) //Direction calculations for same z-level only
direction = uppertext(dir2text(get_dir(curr, dropoff_turf))) //Direction text (East, etc). Not as precise, but still helpful.
if(get_area(user) == traitor_data.uplink_handler.contractor_hub.current_contract.contract.dropoff)
direction = "LOCATION CONFIRMED"
else
direction = "???"
data["dropoff_direction"] = direction
data["redeemable_tc"] = traitor_data.uplink_handler.contractor_hub.contract_TC_to_redeem
data["earned_tc"] = traitor_data.uplink_handler.contractor_hub.contract_TC_payed_out
data["contracts_completed"] = traitor_data.uplink_handler.contractor_hub.contracts_completed
for (var/datum/syndicate_contract/contract in traitor_data.uplink_handler.contractor_hub.assigned_contracts)
if(!contract.contract)
stack_trace("Syndiate contract with null contract objective found in [traitor_data.owner]'s contractor hub!")
contract.status = CONTRACT_STATUS_ABORTED
continue
data["contracts"] += list(list(
"target" = contract.contract.target,
"target_rank" = contract.target_rank,
"payout" = contract.contract.payout,
"payout_bonus" = contract.contract.payout_bonus,
"dropoff" = contract.contract.dropoff,
"id" = contract.id,
"status" = contract.status,
"message" = contract.wanted_message,
))
update_computer_icon()
return data
@@ -57,7 +57,7 @@
var/list/data = list()
var/turf/turf = get_turf(computer)
data["atmozphereMode"] = atmozphere_mode
data["clickAtmozphereCompatible"] = (computer.hardware_flag & PROGRAM_TABLET)
data["clickAtmozphereCompatible"] = (computer.hardware_flag & PROGRAM_PDA)
switch (atmozphere_mode) //Null air wont cause errors, don't worry.
if(ATMOZPHERE_SCAN_ENV)
var/datum/gas_mixture/air = turf?.return_air()
@@ -74,7 +74,7 @@
atmozphere_mode = ATMOZPHERE_SCAN_ENV
UnregisterSignal(computer, COMSIG_ITEM_ATTACK_SELF_SECONDARY)
return TRUE
if(!(computer.hardware_flag & PROGRAM_TABLET))
if(!(computer.hardware_flag & PROGRAM_PDA))
computer.say("Device incompatible for scanning objects!")
return FALSE
atmozphere_mode = ATMOZPHERE_SCAN_CLICK
@@ -5,7 +5,7 @@
program_icon_state = "request"
extended_desc = "Nanotrasen Internal Requisition Network interface for supply purchasing using a department budget account."
requires_ntnet = TRUE
usage_flags = PROGRAM_LAPTOP | PROGRAM_TABLET
usage_flags = PROGRAM_LAPTOP | PROGRAM_PDA
size = 10
tgui_id = "NtosCargo"
///Are you actually placing orders with it?
@@ -5,7 +5,7 @@
category = PROGRAM_CATEGORY_MISC
extended_desc = "This program allows the taking of pictures."
size = 4
usage_flags = PROGRAM_TABLET
usage_flags = PROGRAM_PDA
tgui_id = "NtosCamera"
program_icon = "camera"
@@ -4,7 +4,7 @@
category = PROGRAM_CATEGORY_MISC
extended_desc = "This program allows the tablet to scan physical objects and display a data output."
size = 2
usage_flags = PROGRAM_TABLET
usage_flags = PROGRAM_PDA
tgui_id = "NtosPhysScanner"
program_icon = "barcode"
/// Information from the last scanned person, to display on the app.
@@ -15,7 +15,7 @@
undeletable = TRUE // It comes by default in tablets, can't be downloaded, takes no space and should obviously not be able to be deleted.
header_program = TRUE
available_on_ntnet = FALSE
usage_flags = PROGRAM_TABLET
usage_flags = PROGRAM_PDA
ui_header = "ntnrc_idle.gif"
tgui_id = "NtosMessenger"
program_icon = "comment-alt"
@@ -6,7 +6,7 @@
program_icon_state = "radarntos"
requires_ntnet = TRUE
available_on_ntnet = FALSE
usage_flags = PROGRAM_LAPTOP | PROGRAM_TABLET
usage_flags = PROGRAM_LAPTOP | PROGRAM_PDA
size = 5
tgui_id = "NtosRadar"
///List of trackable entities. Updated by the scan() proc.
@@ -7,7 +7,7 @@
program_icon_state = "crew"
tgui_id = "NtosRecords"
size = 4
usage_flags = PROGRAM_TABLET | PROGRAM_LAPTOP
usage_flags = PROGRAM_PDA | PROGRAM_LAPTOP
available_on_ntnet = FALSE
detomatix_resistance = DETOMATIX_RESIST_MINOR
@@ -8,7 +8,7 @@
requires_ntnet = FALSE
available_on_ntnet = FALSE
undeletable = TRUE
usage_flags = PROGRAM_TABLET
usage_flags = PROGRAM_PDA
size = 5
tgui_id = "NtosRobotact"
program_icon = "terminal"
@@ -7,7 +7,7 @@
size = 2
tgui_id = "NtosSignaler"
program_icon = "satellite-dish"
usage_flags = PROGRAM_TABLET | PROGRAM_LAPTOP
usage_flags = PROGRAM_PDA | PROGRAM_LAPTOP
///What is the saved signal frequency?
var/signal_frequency = FREQ_SIGNALER
/// What is the saved signal code?
@@ -7,7 +7,7 @@
size = 2
tgui_id = "NtosSkillTracker"
program_icon = "medal"
usage_flags = PROGRAM_TABLET // Must be a handheld device to read read your chakras or whatever
usage_flags = PROGRAM_PDA // Must be a handheld device to read read your chakras or whatever
/datum/computer_file/program/skill_tracker/ui_data(mob/user)
var/list/data = list()
+4
View File
@@ -137,6 +137,10 @@
to_chat(user, span_boldnotice("[A] materializes onto the floor!"))
return A
///For special overrides if an item can be bought or not.
/datum/uplink_item/proc/can_be_bought(datum/uplink_handler/source)
return TRUE
/datum/uplink_category/discounts
name = "Discounted Gear"
weight = -1
@@ -0,0 +1,107 @@
/datum/uplink_category/contractor
name = "Contractor"
weight = 10
/datum/uplink_item/bundles_tc/contract_kit
name = "Contract Kit"
desc = "The Syndicate have offered you the chance to become a contractor, take on kidnapping contracts for TC \
and cash payouts. Upon purchase, you'll be granted your own contract uplink embedded within the supplied \
tablet computer. Additionally, you'll be granted standard contractor gear to help with your mission - \
comes supplied with the tablet, specialised space suit, chameleon jumpsuit and mask, agent card, \
specialised contractor baton, and three randomly selected low cost items. \
Can include otherwise unobtainable items."
item = /obj/item/storage/box/syndicate/contract_kit
category = /datum/uplink_category/contractor
cost = 20
purchasable_from = UPLINK_INFILTRATORS
/datum/uplink_item/bundles_tc/contract_kit/purchase(mob/user, datum/uplink_handler/uplink_handler, atom/movable/source)
. = ..()
for(var/uplink_items in subtypesof(/datum/uplink_item/contractor))
var/datum/uplink_item/uplink_item = new uplink_items
uplink_handler.extra_purchasable += uplink_item
/datum/uplink_item/contractor
restricted = TRUE
category = /datum/uplink_category/contractor
purchasable_from = NONE //they will be added to extra_purchasable
//prevents buying contractor stuff before you make an account.
/datum/uplink_item/contractor/can_be_bought(datum/uplink_handler/uplink_handler)
if(!uplink_handler.contractor_hub)
return FALSE
return ..()
/datum/uplink_item/contractor/reroll
name = "Contract Reroll"
desc = "Request a reroll of your current contract list. Will generate a new target, \
payment, and dropoff for the contracts you currently have available."
item = /obj/effect/gibspawner/generic
limited_stock = 2
cost = 0
/datum/uplink_item/contractor/reroll/spawn_item(spawn_path, mob/user, datum/uplink_handler/uplink_handler, atom/movable/source)
//We're not regenerating already completed/aborted/extracting contracts, but we don't want to repeat their targets.
var/list/new_target_list = list()
for(var/datum/syndicate_contract/contract_check in uplink_handler.contractor_hub.assigned_contracts)
if (contract_check.status != CONTRACT_STATUS_ACTIVE && contract_check.status != CONTRACT_STATUS_INACTIVE)
if (contract_check.contract.target)
new_target_list.Add(contract_check.contract.target)
continue
//Reroll contracts without duplicates
for(var/datum/syndicate_contract/rerolling_contract in uplink_handler.contractor_hub.assigned_contracts)
if (rerolling_contract.status != CONTRACT_STATUS_ACTIVE && rerolling_contract.status != CONTRACT_STATUS_INACTIVE)
continue
rerolling_contract.generate(new_target_list)
new_target_list.Add(rerolling_contract.contract.target)
//Set our target list with the new set we've generated.
uplink_handler.contractor_hub.assigned_targets = new_target_list
return source //for log icon
/datum/uplink_item/contractor/pinpointer
name = "Contractor Pinpointer"
desc = "A pinpointer that finds targets even without active suit sensors. \
Due to taking advantage of an exploit within the system, it can't pinpoint \
to the same accuracy as the traditional models. \
Becomes permanently locked to the user that first activates it."
item = /obj/item/pinpointer/crew/contractor
limited_stock = 2
cost = 1
/datum/uplink_item/contractor/extraction_kit
name = "Fulton Extraction Kit"
desc = "For getting your target across the station to those difficult dropoffs. \
Place the beacon somewhere secure, and link the pack. \
Activating the pack on your target will send them over to the beacon - \
make sure they're not just going to run away though!"
item = /obj/item/storage/box/contractor/fulton_extraction
limited_stock = 1
cost = 1
/datum/uplink_item/contractor/partner
name = "Reinforcements"
desc = "Upon purchase we'll contact available units in the area. Should there be an agent free, \
we'll send them down to assist you immediately. If no units are free, we give a full refund."
item = /obj/effect/gibspawner/generic
limited_stock = 1
cost = 2
/datum/uplink_item/contractor/partner/spawn_item(spawn_path, mob/user, datum/uplink_handler/uplink_handler, atom/movable/source)
to_chat(user, span_notice("The uplink vibrates quietly, connecting to nearby agents..."))
var/list/candidates = poll_ghost_candidates(
question = "Do you want to play as the Contractor Support Unit for [user.real_name]?",
jobban_type = ROLE_TRAITOR,
be_special_flag = ROLE_TRAITOR,
poll_time = 10 SECONDS,
ignore_category = POLL_IGNORE_CONTRACTOR_SUPPORT,
)
if(!LAZYLEN(candidates))
to_chat(user, span_notice("No available agents at this time, please try again later."))
limited_stock++
return //bobux no icon
var/mob/dead/observer/selected_player = pick(candidates)
uplink_handler.contractor_hub.contractor_teammate = spawn_contractor_partner(user, selected_player.key)
return source //for log icon
@@ -101,4 +101,5 @@
item = /obj/item/melee/baton/telescopic/contractor_baton
cost = 12
surplus = 50
limited_stock = 1
purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.
+6
View File
@@ -3110,6 +3110,10 @@
#include "code\modules\antagonists\traitor\components\traitor_objective_helpers.dm"
#include "code\modules\antagonists\traitor\components\traitor_objective_limit_per_time.dm"
#include "code\modules\antagonists\traitor\components\traitor_objective_mind_tracker.dm"
#include "code\modules\antagonists\traitor\contractor\contract_teammate.dm"
#include "code\modules\antagonists\traitor\contractor\contractor_hub.dm"
#include "code\modules\antagonists\traitor\contractor\contractor_items.dm"
#include "code\modules\antagonists\traitor\contractor\syndicate_contract.dm"
#include "code\modules\antagonists\traitor\objectives\assassination.dm"
#include "code\modules\antagonists\traitor\objectives\demoralise_assault.dm"
#include "code\modules\antagonists\traitor\objectives\destroy_heirloom.dm"
@@ -4994,6 +4998,7 @@
#include "code\modules\modular_computers\file_system\programs\techweb.dm"
#include "code\modules\modular_computers\file_system\programs\theme_selector.dm"
#include "code\modules\modular_computers\file_system\programs\wirecarp.dm"
#include "code\modules\modular_computers\file_system\programs\antagonist\contractor_program.dm"
#include "code\modules\modular_computers\file_system\programs\antagonist\dos.dm"
#include "code\modules\modular_computers\file_system\programs\antagonist\revelation.dm"
#include "code\modules\modular_computers\file_system\programs\maintenance\_maintenance_program.dm"
@@ -5730,6 +5735,7 @@
#include "code\modules\uplink\uplink_items\badass.dm"
#include "code\modules\uplink\uplink_items\bundle.dm"
#include "code\modules\uplink\uplink_items\clownops.dm"
#include "code\modules\uplink\uplink_items\contractor.dm"
#include "code\modules\uplink\uplink_items\dangerous.dm"
#include "code\modules\uplink\uplink_items\device_tools.dm"
#include "code\modules\uplink\uplink_items\explosive.dm"
@@ -0,0 +1,51 @@
import { Box } from './Box';
import { Component, Fragment } from 'inferno';
export class FakeTerminal extends Component {
constructor(props) {
super(props);
this.timer = null;
this.state = {
currentIndex: 0,
currentDisplay: [],
};
}
tick() {
const { props, state } = this;
if (state.currentIndex <= props.allMessages.length) {
this.setState((prevState) => {
return {
currentIndex: prevState.currentIndex + 1,
};
});
const { currentDisplay } = state;
currentDisplay.push(props.allMessages[state.currentIndex]);
} else {
clearTimeout(this.timer);
setTimeout(props.onFinished, props.finishedTimeout);
}
}
componentDidMount() {
const { linesPerSecond = 2.5 } = this.props;
this.timer = setInterval(() => this.tick(), 1000 / linesPerSecond);
}
componentWillUnmount() {
clearTimeout(this.timer);
}
render() {
return (
<Box m={1}>
{this.state.currentDisplay.map((value) => (
<Fragment key={value}>
{value}
<br />
</Fragment>
))}
</Box>
);
}
}
@@ -0,0 +1,295 @@
import { BooleanLike } from 'common/react';
import { FakeTerminal } from '../components/FakeTerminal';
import { useBackend } from '../backend';
import { Box, Button, Flex, Grid, Icon, LabeledList, Modal, NoticeBox, Section } from '../components';
import { NtosWindow } from '../layouts';
const CONTRACT_STATUS_INACTIVE = 1;
const CONTRACT_STATUS_ACTIVE = 2;
const CONTRACT_STATUS_BOUNTY_CONSOLE_ACTIVE = 3;
const CONTRACT_STATUS_EXTRACTING = 4;
const CONTRACT_STATUS_COMPLETE = 5;
const CONTRACT_STATUS_ABORTED = 6;
export const SyndicateContractor = (props, context) => {
return (
<NtosWindow width={500} height={600}>
<NtosWindow.Content scrollable>
<SyndicateContractorContent />
</NtosWindow.Content>
</NtosWindow>
);
};
type Data = {
error: string;
logged_in: BooleanLike;
first_load: BooleanLike;
info_screen: BooleanLike;
redeemable_tc: Number;
earned_tc: Number;
contracts_completed: Number;
contracts: ContractData[];
ongoing_contract: BooleanLike;
extraction_enroute: BooleanLike;
dropoff_direction: string;
};
type ContractData = {
id: Number;
status: Number;
target: string;
target_rank: string;
extraction_enroute: BooleanLike;
message: string;
contract: string;
dropoff: string;
payout: Number;
payout_bonus: Number;
};
export const SyndicateContractorContent = (props, context) => {
const { data, act } = useBackend<Data>(context);
const { error, logged_in, first_load, info_screen } = data;
const terminalMessages = [
'Recording biometric data...',
'Analyzing embedded syndicate info...',
'STATUS CONFIRMED',
'Contacting syndicate database...',
'Awaiting response...',
'Awaiting response...',
'Awaiting response...',
'Awaiting response...',
'Awaiting response...',
'Awaiting response...',
'Response received, ack 4851234...',
'CONFIRM ACC ' + Math.round(Math.random() * 20000),
'Setting up private accounts...',
'CONTRACTOR ACCOUNT CREATED',
'Searching for available contracts...',
'Searching for available contracts...',
'Searching for available contracts...',
'Searching for available contracts...',
'CONTRACTS FOUND',
'WELCOME, AGENT',
];
const infoEntries = [
'SyndTract v2.0',
'',
"We've identified potentional high-value targets that are",
'currently assigned to your mission area. They are believed',
'to hold valuable information which could be of immediate',
'importance to our organisation.',
'',
'Listed below are all of the contracts available to you. You',
'are to bring the specified target to the designated',
'drop-off, and contact us via this uplink. We will send',
'a specialised extraction unit to put the body into.',
'',
'We want targets alive - but we will sometimes pay slight',
"amounts if they're not, you just won't recieve the shown",
'bonus. You can redeem your payment through this uplink in',
'the form of raw telecrystals, which can be put into your',
'regular Syndicate uplink to purchase whatever you may need.',
'We provide you with these crystals the moment you send the',
'target up to us, which can be collected at anytime through',
'this system.',
'',
'Targets extracted will be ransomed back to the station once',
'their use to us is fulfilled, with us providing you a small',
'percentage cut. You may want to be mindful of them',
'identifying you when they come back. We provide you with',
'a standard contractor loadout, which will help cover your',
'identity.',
];
const errorPane = !!error && (
<Modal backgroundColor="red">
<Flex align="center">
<Flex.Item mr={2}>
<Icon size={4} name="exclamation-triangle" />
</Flex.Item>
<Flex.Item mr={2} grow={1} textAlign="center">
<Box width="260px" textAlign="left" minHeight="80px">
{error}
</Box>
<Button content="Dismiss" onClick={() => act('PRG_clear_error')} />
</Flex.Item>
</Flex>
</Modal>
);
if (!logged_in) {
return (
<Section minHeight="525px">
<Box width="100%" textAlign="center">
<Button
content="REGISTER USER"
color="transparent"
onClick={() => act('PRG_login')}
/>
</Box>
{!!error && <NoticeBox>{error}</NoticeBox>}
</Section>
);
}
if (logged_in && first_load) {
return (
<Box backgroundColor="rgba(0, 0, 0, 0.8)" minHeight="525px">
<FakeTerminal
allMessages={terminalMessages}
finishedTimeout={3000}
onFinished={() => act('PRG_set_first_load_finished')}
/>
</Box>
);
}
if (info_screen) {
return (
<>
<Box backgroundColor="rgba(0, 0, 0, 0.8)" minHeight="500px">
<FakeTerminal allMessages={infoEntries} linesPerSecond={10} />
</Box>
<Button
fluid
content="CONTINUE"
color="transparent"
textAlign="center"
onClick={() => act('PRG_toggle_info')}
/>
</>
);
}
return (
<>
{errorPane}
<StatusPane state={props.state} />
<ContractsTab />
</>
);
};
export const StatusPane = (props, context) => {
const { act, data } = useBackend<Data>(context);
const { redeemable_tc, earned_tc, contracts_completed } = data;
return (
<Section
title={
<>
Contractor Status
<Button
content="View Information Again"
color="transparent"
mb={0}
ml={1}
onClick={() => act('PRG_toggle_info')}
/>
</>
}>
<Grid>
<Grid.Column size={0.85}>
<LabeledList>
<LabeledList.Item
label="TC Available"
buttons={
<Button
content="Claim"
disabled={redeemable_tc <= 0}
onClick={() => act('PRG_redeem_TC')}
/>
}>
{redeemable_tc}
</LabeledList.Item>
<LabeledList.Item label="TC Earned">{earned_tc}</LabeledList.Item>
</LabeledList>
</Grid.Column>
<Grid.Column>
<LabeledList>
<LabeledList.Item label="Contracts Completed">
{contracts_completed}
</LabeledList.Item>
<LabeledList.Item label="Current Status">ACTIVE</LabeledList.Item>
</LabeledList>
</Grid.Column>
</Grid>
</Section>
);
};
const ContractsTab = (props, context) => {
const { act, data } = useBackend<Data>(context);
const { contracts, ongoing_contract, extraction_enroute, dropoff_direction } =
data;
return (
<>
<Section
title="Available Contracts"
buttons={
<Button
content="Call Extraction"
disabled={!ongoing_contract || extraction_enroute}
onClick={() => act('PRG_call_extraction')}
/>
}>
{contracts.map((contract) => {
if (ongoing_contract && contract.status !== CONTRACT_STATUS_ACTIVE) {
return;
}
const active = contract.status > CONTRACT_STATUS_INACTIVE;
if (contract.status >= CONTRACT_STATUS_COMPLETE) {
return;
}
return (
<Section
key={contract.target}
title={
contract.target
? `${contract.target} (${contract.target_rank})`
: 'Invalid Target'
}
buttons={
<>
<Box inline bold mr={1}>
{contract.payout} (+{contract.payout_bonus}) TC
</Box>
<Button
content={active ? 'Abort' : 'Accept'}
disabled={contract.extraction_enroute}
color={active && 'bad'}
onClick={() =>
act('PRG_contract' + (active ? '_abort' : '-accept'), {
contract_id: contract.id,
})
}
/>
</>
}>
<Grid>
<Grid.Column>{contract.message}</Grid.Column>
<Grid.Column size={0.5}>
<Box bold mb={1}>
Dropoff Location:
</Box>
<Box>{contract.dropoff}</Box>
</Grid.Column>
</Grid>
</Section>
);
})}
</Section>
<Section
title="Dropoff Locator"
textAlign="center"
opacity={ongoing_contract ? 100 : 0}>
<Box bold>{dropoff_direction}</Box>
</Section>
</>
);
};