diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index cabcc0a17d..b849e272e7 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -34,6 +34,21 @@
#define HIJACK_HIJACKER 1 //Needs to be present for shuttle to be hijacked
#define HIJACK_PREVENT 2 //Prevents hijacking same way as non-antags
+//Syndicate Contracts
+#define CONTRACT_STATUS_INACTIVE 1
+#define CONTRACT_STATUS_ACTIVE 2
+#define CONTRACT_STATUS_BOUNTY_CONSOLE_ACTIVE 3
+#define CONTRACT_STATUS_EXTRACTING 4
+#define CONTRACT_STATUS_COMPLETE 5
+#define CONTRACT_STATUS_ABORTED 6
+
+#define CONTRACT_PAYOUT_LARGE 1
+#define CONTRACT_PAYOUT_MEDIUM 2
+#define CONTRACT_PAYOUT_SMALL 3
+
+#define CONTRACT_UPLINK_PAGE_CONTRACTS "CONTRACTS"
+#define CONTRACT_UPLINK_PAGE_HUB "HUB"
+
//Overthrow time to update heads obj
#define OBJECTIVE_UPDATING_TIME 300
diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index dd2d464cf0..b63da0e1b1 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -49,7 +49,7 @@
alert("Invalid name.")
return ""
return sanitize(t)
-
+
/proc/sanitize_filename(t)
return sanitize_simple(t, list("\n"="", "\t"="", "/"="", "\\"="", "?"="", "%"="", "*"="", ":"="", "|"="", "\""="", "<"="", ">"=""))
@@ -798,3 +798,19 @@ GLOBAL_LIST_INIT(binary, list("0","1"))
out += prob(replaceprob)? pick(replacementchars) : char
return out.Join("")
+/proc/readable_corrupted_text(text)
+ var/list/corruption_options = list("..", "£%", "~~\"", "!!", "*", "^", "$!", "-", "}", "?")
+ var/corrupted_text = ""
+ // Have every letter have a chance of creating corruption on either side
+ // Small chance of letters being removed in place of corruption - still overall readable
+ for(var/letter_index = 1; letter_index <= length(text); letter_index++)
+ var/letter = text[letter_index]
+ if(prob(15))
+ corrupted_text += pick(corruption_options)
+ if(prob(95))
+ corrupted_text += letter
+ else
+ corrupted_text += pick(corruption_options)
+ if(prob(15))
+ corrupted_text += pick(corruption_options)
+ return corrupted_text
\ No newline at end of file
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index e84b942151..5fa6dc5a4c 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -1075,7 +1075,7 @@ GLOBAL_LIST_EMPTY(cult_contraband)
var/mob/living/carbon/human/H = owner
H.equip_in_one_of_slots(I, list("backpack" = SLOT_IN_BACKPACK))
hoarded_item = I
-
+
GLOBAL_LIST_EMPTY(possible_sabotages)
@@ -1092,7 +1092,7 @@ GLOBAL_LIST_EMPTY(possible_sabotages)
if(!GLOB.possible_sabotages.len)//Only need to fill the list when it's needed.
for(var/I in subtypesof(/datum/sabotage_objective))
new I
-
+
/datum/objective/sabotage/find_target()
var/list/datum/mind/owners = get_owners()
var/approved_targets = list()
@@ -1155,3 +1155,23 @@ GLOBAL_LIST_EMPTY(possible_sabotages)
/datum/objective/flavor/wizard
flavor_file = "strings/flavor_objectives/wizard.txt"
+
+/datum/objective/contract
+ var/payout = 0
+ var/payout_bonus = 0
+ var/area/dropoff = null
+
+// Generate a random valid area on the station that the dropoff will happen.
+/datum/objective/contract/proc/generate_dropoff()
+ var/found = FALSE
+ while(!found)
+ var/area/dropoff_area = pick(GLOB.sortedAreas)
+ if(dropoff_area && is_station_level(dropoff_area.z) && dropoff_area.valid_territory)
+ dropoff = dropoff_area
+ found = TRUE
+
+// Check if both the contractor and contract target are at the dropoff point.
+/datum/objective/contract/proc/dropoff_check(mob/user, mob/target)
+ var/area/user_area = get_area(user)
+ var/area/target_area = get_area(target)
+ return (istype(user_area, dropoff) && istype(target_area, dropoff))
diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm
index 611870912c..f87e0ea075 100644
--- a/code/game/objects/items/storage/uplink_kits.dm
+++ b/code/game/objects/items/storage/uplink_kits.dm
@@ -397,3 +397,47 @@
/obj/item/storage/box/syndie_kit/revolver/PopulateContents()
new /obj/item/gun/ballistic/revolver(src)
new /obj/item/ammo_box/a357(src)
+
+/obj/item/storage/box/syndie_kit/contract_kit
+ name = "contractor kit"
+ desc = "Supplied to Syndicate contractors in active mission areas."
+
+/obj/item/storage/box/syndie_kit/contract_kit/PopulateContents()
+ new /obj/item/modular_computer/tablet/syndicate_contract_uplink/preset/uplink(src)
+ new /obj/item/storage/box/syndie_kit/space/contract(src)
+ new /obj/item/clothing/under/chameleon(src)
+ new /obj/item/clothing/mask/chameleon(src)
+ new /obj/item/card/id/syndicate(src)
+
+ // All 4 TC or less - some nukeops only items, but fit nicely to the theme.
+ var/list/item_list = list(
+ /obj/item/storage/backpack/duffelbag/syndie/x4,
+ /obj/item/storage/box/syndie_kit/emp,
+ /obj/item/storage/box/syndie_kit/throwing_weapons,
+ /obj/item/gun/syringe/syndicate,
+ /obj/item/pen/edagger,
+ /obj/item/pen/sleepy,
+ /obj/item/gun/ballistic/automatic/toy/pistol/riot,
+ /obj/item/flashlight/emp,
+ /obj/item/reagent_containers/syringe/mulligan,
+ /obj/item/clothing/shoes/chameleon/noslip,
+ /obj/item/storage/toolbox/syndicate,
+ /obj/item/storage/firstaid/tactical,
+ /obj/item/storage/backpack/duffelbag/syndie/surgery,
+ /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
+ )
+
+ var/obj/item1 = pick_n_take(item_list)
+ var/obj/item2 = pick_n_take(item_list)
+
+ // Create two, non repeat items from the list.
+ new item1(src)
+ new item2(src)
+
+/obj/item/storage/box/syndie_kit/space/contract/PopulateContents()
+ new /obj/item/clothing/suit/space/syndicate/black/red(src)
+ new /obj/item/clothing/head/helmet/space/syndicate/black/red(src)
\ No newline at end of file
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index 68963078c5..12c739b725 100644
--- a/code/modules/antagonists/traitor/datum_traitor.dm
+++ b/code/modules/antagonists/traitor/datum_traitor.dm
@@ -13,6 +13,10 @@
var/should_give_codewords = TRUE
var/should_equip = TRUE
var/traitor_kind = TRAITOR_HUMAN //Set on initial assignment
+ var/datum/syndicate_contract/current_contract
+ var/list/assigned_contracts = list()
+ var/contract_TC_payed_out = 0
+ var/contract_TC_to_redeem = 0
hijack_speed = 0.5 //10 seconds per hijack stage by default
/datum/antagonist/traitor/on_gain()
@@ -26,6 +30,14 @@
finalize_traitor()
..()
+/datum/antagonist/traitor/proc/create_contracts()
+ var/contract_generation_quantity = 6
+
+ for(var/i = 1; i <= contract_generation_quantity; i++)
+ var/datum/syndicate_contract/contract_to_add = new(owner)
+ contract_to_add.id = i
+ assigned_contracts.Add(contract_to_add)
+
/datum/antagonist/traitor/apply_innate_effects()
if(owner.assigned_role == "Clown")
var/mob/living/carbon/human/traitor_mob = owner.current
@@ -413,6 +425,20 @@
var/special_role_text = lowertext(name)
+ var/completed_contracts = 0
+ var/tc_total = contract_TC_payed_out + contract_TC_to_redeem
+ for (var/datum/syndicate_contract/contract in assigned_contracts)
+ if (contract.status == CONTRACT_STATUS_COMPLETE)
+ completed_contracts++
+
+
+ if (completed_contracts > 0)
+ var/pluralCheck = "contract"
+ if (completed_contracts > 1)
+ pluralCheck = "contracts"
+ result += "
Completed [completed_contracts] [pluralCheck] for a total of \
+ [tc_total] TC!
"
+
if(traitorwin)
result += "The [special_role_text] was successful!"
else
diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm
index adfbe3308a..d31b18e1d7 100644
--- a/code/modules/cargo/supplypod.dm
+++ b/code/modules/cargo/supplypod.dm
@@ -42,6 +42,8 @@
var/soundVolume = 80 //Volume to play sounds at. Ignores the cap
var/bay //Used specifically for the centcom_podlauncher datum. Holds the current bay the user is launching objects from. Bays are specific rooms on the centcom map.
var/list/explosionSize = list(0,0,2,3)
+ var/stay_after_drop = FALSE
+ var/specialised = TRUE // It's not a general use pod for cargo/admin use
/obj/structure/closet/supplypod/bluespacepod
style = STYLE_BLUESPACE
@@ -49,6 +51,15 @@
explosionSize = list(0,0,1,2)
landingDelay = 15 //Slightly quicker than the supplypod
+/obj/structure/closet/supplypod/extractionpod
+ name = "Syndicate Extraction Pod"
+ desc = "A specalised, blood-red styled pod for extracting high-value targets out of active mission areas."
+ specialised = TRUE
+ style = STYLE_SYNDICATE
+ bluespace = TRUE
+ explosionSize = list(0,0,1,2)
+ landingDelay = 25 //Slightly longer than others
+
/obj/structure/closet/supplypod/centcompod
style = STYLE_CENTCOM
bluespace = TRUE
@@ -56,6 +67,13 @@
landingDelay = 20 //Very speedy!
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+/obj/structure/closet/supplypod/proc/specialisedPod()
+ return 1
+
+/obj/structure/closet/supplypod/extractionpod/specialisedPod(atom/movable/holder)
+ holder.forceMove(pick(GLOB.holdingfacility)) // land in ninja jail
+ open(holder, forced = TRUE)
+
/obj/structure/closet/supplypod/Initialize()
. = ..()
setStyle(style, TRUE) //Upon initialization, give the supplypod an iconstate, name, and description based on the "style" variable. This system is important for the centcom_podlauncher to function correctly
@@ -76,7 +94,7 @@
return
style = chosenStyle
icon_state = POD_STYLES[chosenStyle][POD_ICON_STATE] //POD_STYLES is a 2D array we treat as a dictionary. The style represents the verticle index, with the icon state, name, and desc being stored in the horizontal indexes of the 2D array.
- if (!adminNamed) //We dont want to name it ourselves if it has been specifically named by an admin using the centcom_podlauncher datum
+ if (!adminNamed && !specialised) //We dont want to name it ourselves if it has been specifically named by an admin using the centcom_podlauncher datum
name = POD_STYLES[chosenStyle][POD_NAME]
desc = POD_STYLES[chosenStyle][POD_DESC]
update_icon()
@@ -96,6 +114,30 @@
/obj/structure/closet/supplypod/toggle(mob/living/user) //Supplypods shouldn't be able to be manually opened under any circumstances, as the open() proc generates supply order datums
return
+/obj/structure/closet/supplypod/proc/handleReturningClose(atom/movable/holder, returntobay)
+ opened = FALSE
+ INVOKE_ASYNC(holder, .proc/setClosed) //Use the INVOKE_ASYNC proc to call setClosed() on whatever the holder may be, without giving the atom/movable base class a setClosed() proc definition
+ for(var/atom/movable/O in get_turf(holder))
+ if ((ismob(O) && !isliving(O)) || (is_type_in_typecache(O, GLOB.blacklisted_cargo_types) && !isliving(O))) //We dont want to take ghosts with us, and we don't want blacklisted items going, but we allow mobs.
+ continue
+ O.forceMove(holder) //Put objects inside before we close
+ var/obj/effect/temp_visual/risingPod = new /obj/effect/abstract/DPfall(get_turf(holder), src) //Make a nice animation of flying back up
+ risingPod.pixel_z = 0 //The initial value of risingPod's pixel_z is 200 because it normally comes down from a high spot
+ animate(risingPod, pixel_z = 200, time = 10, easing = LINEAR_EASING) //Animate our rising pod
+ if(returntobay)
+ holder.forceMove(bay) //Move the pod back to centcom, where it belongs
+ QDEL_IN(risingPod, 10)
+ reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open() )
+ bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever
+ open(holder, forced = TRUE)
+ else
+ reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open() )
+ bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever
+ QDEL_IN(risingPod, 10)
+ audible_message("The pod hisses, closing quickly and launching itself away from the station.", "The ground vibrates, the nearby pod launching away from the station.")
+ stay_after_drop = FALSE
+ specialisedPod(holder) // Do special actions for specialised pods - this is likely if we were already doing manual launches
+
/obj/structure/closet/supplypod/proc/preOpen() //Called before the open() proc. Handles anything that occurs right as the pod lands.
var/turf/T = get_turf(src)
var/list/B = explosionSize //Mostly because B is more readable than explosionSize :p
@@ -172,7 +214,8 @@
if (style == STYLE_SEETHROUGH)
depart(src)
else
- addtimer(CALLBACK(src, .proc/depart, holder), departureDelay) //Finish up the pod's duties after a certain amount of time
+ if(!stay_after_drop) // Departing should be handled manually
+ addtimer(CALLBACK(src, .proc/depart, holder), departureDelay) //Finish up the pod's duties after a certain amount of time
/obj/structure/closet/supplypod/proc/depart(atom/movable/holder)
if (leavingSound)
@@ -187,20 +230,14 @@
qdel(holder)
/obj/structure/closet/supplypod/centcompod/close(atom/movable/holder) //Closes the supplypod and sends it back to centcom. Should only ever be called if the "reversing" variable is true
- opened = FALSE
- INVOKE_ASYNC(holder, .proc/setClosed) //Use the INVOKE_ASYNC proc to call setClosed() on whatever the holder may be, without giving the atom/movable base class a setClosed() proc definition
- for (var/atom/movable/O in get_turf(holder))
- if (ismob(O) && !isliving(O)) //We dont want to take ghosts with us
- continue
- O.forceMove(holder) //Put objects inside before we close
- var/obj/effect/temp_visual/risingPod = new /obj/effect/abstract/DPfall(get_turf(holder), src) //Make a nice animation of flying back up
- risingPod.pixel_z = 0 //The initial value of risingPod's pixel_z is 200 because it normally comes down from a high spot
- holder.forceMove(bay) //Move the pod back to centcom, where it belongs
- animate(risingPod, pixel_z = 200, time = 10, easing = LINEAR_EASING) //Animate our rising pod
- QDEL_IN(risingPod, 10)
- reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open() )
- bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever
- open(holder, forced = TRUE)
+ handleReturningClose(holder, TRUE)
+
+/obj/structure/closet/supplypod/extractionpod/close(atom/movable/holder) //handles closing, and returns pod - deletes itself when returned
+ if(!holder)
+ holder = src
+ if(leavingSound)
+ playsound(get_turf(holder), leavingSound, soundVolume, 0, 0)
+ handleReturningClose(holder, FALSE)
/obj/structure/closet/supplypod/proc/setOpened() //Proc exists here, as well as in any atom that can assume the role of a "holder" of a supplypod. Check the open() proc for more details
update_icon()
diff --git a/code/modules/modular_computers/computers/item/tablet.dm b/code/modules/modular_computers/computers/item/tablet.dm
index 54e43a8731..154e3b5e41 100644
--- a/code/modules/modular_computers/computers/item/tablet.dm
+++ b/code/modules/modular_computers/computers/item/tablet.dm
@@ -20,4 +20,16 @@
finish_color = pick("red","blue","brown","green","black")
icon_state = "tablet-[finish_color]"
icon_state_unpowered = "tablet-[finish_color]"
- icon_state_powered = "tablet-[finish_color]"
\ No newline at end of file
+ icon_state_powered = "tablet-[finish_color]"
+
+/obj/item/modular_computer/tablet/syndicate_contract_uplink
+ name = "tablet computer"
+ icon = 'icons/obj/modular_tablet.dmi'
+ icon_state = "tablet-red"
+ icon_state_unpowered = "tablet"
+ icon_state_powered = "tablet"
+ icon_state_menu = "hostile"
+ w_class = WEIGHT_CLASS_SMALL
+ slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT
+ comp_light_luminosity = 4.3
+ finish_color = "red"
\ No newline at end of file
diff --git a/code/modules/modular_computers/computers/item/tablet_presets.dm b/code/modules/modular_computers/computers/item/tablet_presets.dm
index c793ae22f6..c72b3061d0 100644
--- a/code/modules/modular_computers/computers/item/tablet_presets.dm
+++ b/code/modules/modular_computers/computers/item/tablet_presets.dm
@@ -27,3 +27,15 @@
install_component(new /obj/item/computer_hardware/hard_drive/small)
install_component(new /obj/item/computer_hardware/network_card)
install_component(new /obj/item/computer_hardware/printer/mini)
+
+// Given by the syndicate as part of the contract uplink bundle
+/obj/item/modular_computer/tablet/syndicate_contract_uplink/preset/uplink/Initialize()
+ . = ..()
+ var/obj/item/computer_hardware/hard_drive/small/syndicate/hard_drive = new
+ hard_drive.store_file(new /datum/computer_file/program/contract_uplink)
+ install_component(new /obj/item/computer_hardware/processor_unit/small)
+ install_component(new /obj/item/computer_hardware/battery(src, /obj/item/stock_parts/cell/computer))
+ install_component(hard_drive)
+ install_component(new /obj/item/computer_hardware/network_card)
+ install_component(new /obj/item/computer_hardware/card_slot)
+ install_component(new /obj/item/computer_hardware/printer/mini)
\ No newline at end of file
diff --git a/code/modules/modular_computers/hardware/hard_drive.dm b/code/modules/modular_computers/hardware/hard_drive.dm
index 4109b2c3f0..303c74a8f8 100644
--- a/code/modules/modular_computers/hardware/hard_drive.dm
+++ b/code/modules/modular_computers/hardware/hard_drive.dm
@@ -158,6 +158,13 @@
icon_state = "ssd_mini"
w_class = WEIGHT_CLASS_TINY
+// Syndicate variant - very slight better
+/obj/item/computer_hardware/hard_drive/small/syndicate
+ desc = "An efficient SSD for portable devices developed by a rival organisation."
+ power_usage = 8
+ max_capacity = 70
+ var/datum/antagonist/traitor/traitor_data // Syndicate hard drive has the user's data baked directly into it on creation
+
/obj/item/computer_hardware/hard_drive/micro
name = "micro solid state drive"
desc = "A highly efficient SSD chip for portable devices."
diff --git a/code/modules/uplink/uplink_items/uplink_bundles.dm b/code/modules/uplink/uplink_items/uplink_bundles.dm
index 23fb23f198..8b29c246b9 100644
--- a/code/modules/uplink/uplink_items/uplink_bundles.dm
+++ b/code/modules/uplink/uplink_items/uplink_bundles.dm
@@ -30,6 +30,17 @@
cost = 14 // normally 16
include_modes = list(/datum/game_mode/nuclear)
+/datum/uplink_item/bundles_TC/contract_kit
+ name = "Contract Kit"
+ desc = "The Syndicate have a number of urgent contracts for you to take on, become a contractor and complete them for TC. Upon purchase, \
+ you'll be granted your own contract uplink embedded within the supplied tablet computer. Additonally, you'll be granted contractor \
+ gear to help with your mission - comes supplied with the tablet, space suit, chameleon jumpsuit and mask, agent card, and two \
+ randomly selected low cost items."
+ item = /obj/item/storage/box/syndie_kit/contract_kit
+ cost = 20
+ player_minimum = 20
+ exclude_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
+
/datum/uplink_item/bundles_TC/cybernetics_bundle
name = "Cybernetic Implants Bundle"
desc = "A random selection of cybernetic implants. Guaranteed 5 high quality implants. Comes with an autosurgeon."
diff --git a/tgstation.dme b/tgstation.dme
index 6fe8ead35b..44218f00fc 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -79,8 +79,8 @@
#include "code\__DEFINES\obj_flags.dm"
#include "code\__DEFINES\pinpointers.dm"
#include "code\__DEFINES\pipe_construction.dm"
-#include "code\__DEFINES\power.dm"
#include "code\__DEFINES\pool.dm"
+#include "code\__DEFINES\power.dm"
#include "code\__DEFINES\preferences.dm"
#include "code\__DEFINES\procpath.dm"
#include "code\__DEFINES\profile.dm"
@@ -1468,6 +1468,7 @@
#include "code\modules\antagonists\swarmer\swarmer.dm"
#include "code\modules\antagonists\swarmer\swarmer_event.dm"
#include "code\modules\antagonists\traitor\datum_traitor.dm"
+#include "code\modules\antagonists\traitor\syndicate_contract.dm"
#include "code\modules\antagonists\traitor\equipment\Malf_Modules.dm"
#include "code\modules\antagonists\traitor\IAA\internal_affairs.dm"
#include "code\modules\antagonists\valentines\heartbreaker.dm"
@@ -2474,6 +2475,7 @@
#include "code\modules\modular_computers\file_system\programs\nttransfer.dm"
#include "code\modules\modular_computers\file_system\programs\powermonitor.dm"
#include "code\modules\modular_computers\file_system\programs\sm_monitor.dm"
+#include "code\modules\modular_computers\file_system\programs\antagonist\contract_uplink.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\hardware\_hardware.dm"