mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-29 15:08:02 +01:00
[READY] Syndicate Contractors (#14605)
* Syndicate Contractors initial PR * Finalize initial version DNP Finalize initial version * Baton now costs 6 Rep, show dead extraction penalty, fix scroll * Reduce total TC, add zippo, balloon, icon tweaks * Address moxian, AA and Farie * Fix mode.txt * oops * Address Farie 2 * Tweak baton to work around stam crits, address SteelSlayer * Address TM issues, TP menu * Fix wrongly merged DME * Tick contractor DM files again * Remove step_y * TC rewards always higher than preceding difficulty's reward * Address Farie * Remove extraction_turf from handle_target_return * fix doc * Merge part2 * Address AA again
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* # Contractor antagonist datum
|
||||
*
|
||||
* A variant of the Traitor, Contractors rely on kidnapping crew members to earn TC.
|
||||
*
|
||||
* Contractors are supplied with some unique items
|
||||
* and three random low cost contraband items to help kickstart their contracts.
|
||||
* A Traitor may become a Contractor if given the chance (random).
|
||||
* They will forfeit all their initial TC and receive the above items.
|
||||
* The opportunity to become a Contractor goes away after some time or if the traitor spends any initial TC.
|
||||
*/
|
||||
/datum/antagonist/traitor/contractor
|
||||
name = "Contractor"
|
||||
// Settings
|
||||
/// How many telecrystals a traitor must forfeit to become a contractor.
|
||||
var/tc_cost = 20
|
||||
/// How long a traitor's chance to become a contractor lasts before going away. In deciseconds.
|
||||
var/offer_duration = 10 MINUTES
|
||||
// Variables
|
||||
/// The associated contractor uplink. Only present if the offer was accepted.
|
||||
var/obj/item/contractor_uplink/contractor_uplink = null
|
||||
/// world.time at which the offer will expire.
|
||||
var/offer_deadline = -1
|
||||
|
||||
/datum/antagonist/traitor/contractor/finalize_traitor()
|
||||
..()
|
||||
// Setup the vars and contractor stuff in the uplink
|
||||
var/obj/item/uplink/hidden/U = owner.find_syndicate_uplink()
|
||||
if(!U)
|
||||
stack_trace("Potential contractor [owner] spawned without a hidden uplink!")
|
||||
return
|
||||
U.contractor = src
|
||||
offer_deadline = world.time + offer_duration
|
||||
|
||||
// Greet them with the unique message
|
||||
var/greet_text = "Contractors forfeit [tc_cost] telecrystals for the privilege of taking on kidnapping contracts for credit and TC payouts that can add up to more than the normal starting amount of TC.<br>"\
|
||||
+ "If you are interested, simply access your hidden uplink and select the \"Contracting Opportunity\" tab for more information.<br>"
|
||||
to_chat(owner.current, "<b><font size=4 color=red>You have been offered a chance to become a Contractor.</font></b><br>")
|
||||
to_chat(owner.current, "<font color=red>[greet_text]</font>")
|
||||
to_chat(owner.current, "<b><i><font color=red>This offer will expire in 10 minutes starting now (expiry time: <u>[station_time_timestamp(time = offer_deadline)]</u>).</font></i></b>")
|
||||
|
||||
/datum/antagonist/traitor/contractor/update_traitor_icons_added(datum/mind/traitor_mind)
|
||||
if(!contractor_uplink)
|
||||
return ..()
|
||||
var/hud_name = "hudcontractor"
|
||||
if(locate(/datum/objective/hijack) in owner.objectives)
|
||||
hud_name = "hudhijackcontractor"
|
||||
var/datum/atom_hud/antag/traitorhud = GLOB.huds[ANTAG_HUD_TRAITOR]
|
||||
traitorhud.join_hud(owner.current, null)
|
||||
set_antag_hud(owner.current, hud_name)
|
||||
|
||||
/**
|
||||
* Accepts the offer to be a contractor if possible.
|
||||
*/
|
||||
/datum/antagonist/traitor/contractor/proc/become_contractor(mob/living/carbon/human/M, obj/item/uplink/U)
|
||||
if(contractor_uplink || !istype(M))
|
||||
return
|
||||
if(U.uses < tc_cost || world.time >= offer_deadline)
|
||||
var/reason = (U.uses < tc_cost) ? \
|
||||
"you have insufficient telecrystals ([tc_cost] needed in total)" : \
|
||||
"the deadline has passed"
|
||||
to_chat(M, "<span class='warning'>You can no longer become a contractor as [reason].</span>")
|
||||
return
|
||||
|
||||
// Give the kit
|
||||
var/obj/item/storage/box/syndie_kit/contractor/B = new(M)
|
||||
M.put_in_hands(B)
|
||||
contractor_uplink = locate(/obj/item/contractor_uplink, B)
|
||||
contractor_uplink.hub = new(M.mind, contractor_uplink)
|
||||
|
||||
// Update AntagHUD icon
|
||||
update_traitor_icons_added(owner)
|
||||
|
||||
// Remove the TC
|
||||
U.uses -= tc_cost
|
||||
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* # Syndicate Hub
|
||||
*
|
||||
* Describes and manages the contracts and rewards for a single contractor.
|
||||
*/
|
||||
/datum/contractor_hub
|
||||
// Settings
|
||||
/// The number of contracts to generate initially.
|
||||
var/num_contracts = 6
|
||||
/// How much Contractor Rep to earn per contract completion.
|
||||
var/rep_per_completion = 2
|
||||
/// Completing every contract at a given difficulty will always result in a sum of TC greater or equal than the difficulty's threshold.
|
||||
/// Structure: EXTRACTION_DIFFICULTY_(EASY|MEDIUM|HARD) => number
|
||||
var/difficulty_tc_thresholds = list(
|
||||
EXTRACTION_DIFFICULTY_EASY = 20,
|
||||
EXTRACTION_DIFFICULTY_MEDIUM = 30,
|
||||
EXTRACTION_DIFFICULTY_HARD = 40,
|
||||
)
|
||||
/// Maximum variation a single contract's TC reward can have upon generation.
|
||||
/// In other words: final_reward = CEILING((tc_threshold / num_contracts) * (1 + (rand(-100, 100) / 100) * tc_variation), 1)
|
||||
var/tc_variation = 0.25
|
||||
/// TC reward multiplier if the target was extracted DEAD. Should be a penalty so between 0 and 1.
|
||||
/// The final amount is rounded up.
|
||||
var/dead_penalty = 0.2
|
||||
/// List of purchases that can be done for Rep.
|
||||
var/list/datum/rep_purchase/purchases = list(
|
||||
/datum/rep_purchase/reroll,
|
||||
/datum/rep_purchase/item/pinpointer,
|
||||
/datum/rep_purchase/item/fulton,
|
||||
/datum/rep_purchase/blackout,
|
||||
/datum/rep_purchase/item/zippo,
|
||||
/datum/rep_purchase/item/balloon,
|
||||
)
|
||||
// Variables
|
||||
/// The contractor associated to this hub.
|
||||
var/datum/mind/owner = null
|
||||
/// The contractor uplink associated to this hub.
|
||||
var/obj/item/contractor_uplink/contractor_uplink = null
|
||||
/// The current contract in progress.
|
||||
var/datum/syndicate_contract/current_contract = null
|
||||
/// The contracts offered by the hub.
|
||||
var/list/datum/syndicate_contract/contracts = null
|
||||
/// List of targets from each contract in [/datum/contractor_hub/var/contracts].
|
||||
/// Used to make sure two contracts from the same hub don't have the same target.
|
||||
var/list/datum/mind/targets = null
|
||||
/// Amount of telecrystals available for redeeming.
|
||||
var/reward_tc_available = 0
|
||||
/// Total amount of paid out telecrystals since the start.
|
||||
var/reward_tc_paid_out = 0
|
||||
/// The number of completed contracts.
|
||||
var/completed_contracts = 0
|
||||
/// Amount of Contractor Rep available for spending.
|
||||
var/rep = 0
|
||||
/// Current UI page index.
|
||||
var/page = HUB_PAGE_CONTRACTS
|
||||
|
||||
/datum/contractor_hub/New(datum/mind/O, obj/item/contractor_uplink/U)
|
||||
owner = O
|
||||
contractor_uplink = U
|
||||
// Instantiate purchases
|
||||
for(var/i in 1 to length(purchases))
|
||||
if(ispath(purchases[i]))
|
||||
var/datum/rep_purchase/P = purchases[i]
|
||||
purchases[i] = new P
|
||||
else
|
||||
stack_trace("Expected Hub purchase [purchases[i]] to be a type but it wasn't!")
|
||||
|
||||
/datum/contractor_hub/ui_host(mob/user)
|
||||
return contractor_uplink
|
||||
|
||||
/**
|
||||
* Called when the loading animation completes for the first time.
|
||||
*/
|
||||
/datum/contractor_hub/proc/first_login(mob/user)
|
||||
if(!is_user_authorized(user))
|
||||
return
|
||||
user.playsound_local(user, 'sound/effects/contractstartup.ogg', 30, FALSE)
|
||||
generate_contracts()
|
||||
SStgui.update_uis(src)
|
||||
|
||||
/**
|
||||
* Regenerates a list of contracts for the contractor to take up.
|
||||
*/
|
||||
/datum/contractor_hub/proc/generate_contracts()
|
||||
contracts = list()
|
||||
targets = list()
|
||||
|
||||
var/num_to_generate = min(num_contracts, length(GLOB.data_core.locked))
|
||||
if(num_to_generate <= 0) // ?
|
||||
return
|
||||
|
||||
// Contract generation
|
||||
var/total_earnable_tc = list(0, 0, 0)
|
||||
for(var/i in 1 to num_to_generate)
|
||||
var/datum/syndicate_contract/C = new(src, owner, targets)
|
||||
// Calculate TC reward for each difficulty
|
||||
C.reward_tc = list(null, null, null)
|
||||
for(var/difficulty in EXTRACTION_DIFFICULTY_EASY to EXTRACTION_DIFFICULTY_HARD)
|
||||
var/amount_tc = calculate_tc_reward(num_to_generate, difficulty)
|
||||
// Bump up the TC reward a little if it's too close to the lower difficulty's reward
|
||||
if(difficulty > EXTRACTION_DIFFICULTY_EASY)
|
||||
amount_tc = max(amount_tc, C.reward_tc[difficulty - 1] + (difficulty - 1))
|
||||
C.reward_tc[difficulty] = amount_tc
|
||||
total_earnable_tc[difficulty] += amount_tc
|
||||
// Add to lists
|
||||
contracts += C
|
||||
targets += C.contract.target
|
||||
|
||||
// Fill the gap if a difficulty doesn't meet the TC threshold
|
||||
for(var/difficulty in EXTRACTION_DIFFICULTY_EASY to EXTRACTION_DIFFICULTY_HARD)
|
||||
var/total = total_earnable_tc[difficulty]
|
||||
var/missing = difficulty_tc_thresholds[difficulty] - total
|
||||
if(missing <= 0)
|
||||
continue
|
||||
// Just add the missing TC to a random contract
|
||||
var/datum/syndicate_contract/C = pick(contracts)
|
||||
C.reward_tc[difficulty] += missing
|
||||
|
||||
/**
|
||||
* Generates an amount of TC to be used as a contract reward for the given difficulty.
|
||||
*
|
||||
* Arguments:
|
||||
* * total_contracts - The number of contracts being generated.
|
||||
* * difficulty - The difficulty to base the threshold from.
|
||||
*/
|
||||
/datum/contractor_hub/proc/calculate_tc_reward(total_contracts, difficulty = EXTRACTION_DIFFICULTY_EASY)
|
||||
ASSERT(total_contracts > 0)
|
||||
return CEILING((difficulty_tc_thresholds[difficulty] / total_contracts) * (1 + (rand(-100, 100) / 100) * tc_variation), 1)
|
||||
|
||||
/**
|
||||
* Called when a [/datum/syndicate_contract] has been completed.
|
||||
*
|
||||
* Arguments:
|
||||
* * tc - The final amount of TC to award.
|
||||
* * creds - The final amount of credits to award.
|
||||
*/
|
||||
/datum/contractor_hub/proc/on_completion(tc, creds)
|
||||
completed_contracts++
|
||||
reward_tc_available += tc
|
||||
rep += rep_per_completion
|
||||
owner?.initial_account?.credit(creds, pick(list(
|
||||
"CONGRATULATIONS. You are the 10,000th visitor of SquishySlimes.squish. Please find attached your [creds] credits.",
|
||||
"Congratulations on winning your bet in the latest Clown vs. Mime match! Your account was credited with [creds] credits.",
|
||||
"Deer fund beneficiary, We have please to imform you that overdue fund payments has finally is approved and yuor account credited with [creds] creadits.",
|
||||
"Hey bro. How's it going? You bought me a beer a long time ago and I want to pay you back with [creds] creds. Enjoy!",
|
||||
"Thank you for your initial investment of 500 credits! We have credited your account with [creds] as a token of appreciation.",
|
||||
"Your refund request for 100 Dr. Maxman pills with the reason \"I need way more than 100 pills!\" has been received. We have credited your account with [creds] credits.",
|
||||
"Your refund request for your WetSkrell.nt subscription has been received. We have credited your account with [creds] credits.",
|
||||
)))
|
||||
// Clean up
|
||||
current_contract = null
|
||||
|
||||
/**
|
||||
* Gives any unclaimed TC to the given mob.
|
||||
*
|
||||
* Arguments:
|
||||
* * M - The mob to give the TC to.
|
||||
*/
|
||||
/datum/contractor_hub/proc/claim_tc(mob/living/M)
|
||||
if(reward_tc_available <= 0)
|
||||
return
|
||||
|
||||
// Spawn the crystals
|
||||
var/obj/item/stack/telecrystal/TC = new(get_turf(M), reward_tc_available)
|
||||
if(ishuman(M))
|
||||
var/mob/living/carbon/human/H = M
|
||||
if(H.put_in_hands(TC))
|
||||
to_chat(H, "<span class='notice'>Your payment materializes into your hands!</span>")
|
||||
else
|
||||
to_chat(M, "<span class='notice'>Your payment materializes on the floor.</span>")
|
||||
// Update info
|
||||
reward_tc_paid_out += reward_tc_available
|
||||
reward_tc_available = 0
|
||||
|
||||
/**
|
||||
* Returns whether the given mob is allowed to connect to the uplink.
|
||||
*
|
||||
* Arguments:
|
||||
* * M - The mob.
|
||||
*/
|
||||
/datum/contractor_hub/proc/is_user_authorized(mob/living/carbon/M)
|
||||
return M.mind.has_antag_datum(/datum/antagonist/traitor/contractor)
|
||||
@@ -0,0 +1,124 @@
|
||||
|
||||
/datum/contractor_hub/ui_act(action, list/params)
|
||||
if(..())
|
||||
return
|
||||
|
||||
. = TRUE
|
||||
if(!contracts)
|
||||
if(action == "complete_load_animation")
|
||||
first_login(usr)
|
||||
else
|
||||
switch(action)
|
||||
if("page")
|
||||
var/newpage = text2num(params["page"])
|
||||
if(!(newpage in list(HUB_PAGE_CONTRACTS, HUB_PAGE_SHOP)))
|
||||
return
|
||||
page = newpage
|
||||
if("extract")
|
||||
var/error_message = current_contract?.start_extraction_process(ui_host(), usr)
|
||||
if(length(error_message))
|
||||
to_chat(usr, "<span class='warning'>[error_message]</span>")
|
||||
if("claim")
|
||||
claim_tc(usr)
|
||||
if("activate")
|
||||
var/datum/syndicate_contract/C = locateUID(params["uid"])
|
||||
var/difficulty = text2num(params["difficulty"])
|
||||
if(!istype(C) || !(C in contracts) || !(difficulty in list(EXTRACTION_DIFFICULTY_EASY, EXTRACTION_DIFFICULTY_MEDIUM, EXTRACTION_DIFFICULTY_HARD)))
|
||||
return
|
||||
C.initiate(usr, difficulty)
|
||||
if("abort")
|
||||
current_contract?.fail("Aborted by agent.")
|
||||
if("purchase")
|
||||
var/datum/rep_purchase/P = locateUID(params["uid"])
|
||||
if(!istype(P) || !(P in purchases) || rep < P.cost)
|
||||
return
|
||||
P.buy(src, usr)
|
||||
else
|
||||
return FALSE
|
||||
|
||||
var/obj/item/U = ui_host()
|
||||
U?.add_fingerprint(usr)
|
||||
|
||||
/datum/contractor_hub/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "Contractor", "Syndicate Contractor Uplink", 500, 600, master_ui, state)
|
||||
ui.open()
|
||||
ui.set_autoupdate(FALSE)
|
||||
|
||||
/datum/contractor_hub/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
if(!contracts)
|
||||
if(!is_user_authorized(user))
|
||||
data["unauthorized"] = TRUE
|
||||
return data
|
||||
data["load_animation_completed"] = FALSE
|
||||
return data
|
||||
else
|
||||
data["load_animation_completed"] = TRUE
|
||||
|
||||
data["page"] = page
|
||||
data["tc_available"] = reward_tc_available
|
||||
data["tc_paid_out"] = reward_tc_paid_out
|
||||
data["completed_contracts"] = completed_contracts
|
||||
data["contract_active"] = !isnull(current_contract)
|
||||
data["rep"] = rep
|
||||
|
||||
switch(page)
|
||||
if(HUB_PAGE_CONTRACTS)
|
||||
var/list/contracts = list()
|
||||
data["contracts"] = contracts
|
||||
for(var/c in contracts)
|
||||
var/datum/syndicate_contract/C = c
|
||||
if(C.status == CONTRACT_STATUS_INVALID)
|
||||
continue
|
||||
|
||||
var/list/contract_data = list(
|
||||
uid = C.UID(),
|
||||
status = C.status,
|
||||
target_name = C.target_name,
|
||||
fluff_message = C.fluff_message,
|
||||
has_photo = !isnull(C.target_photo),
|
||||
)
|
||||
|
||||
if(C.target_photo)
|
||||
usr << browse_rsc(C.target_photo, "target_photo_[C.UID()].png")
|
||||
|
||||
switch(C.status)
|
||||
if(CONTRACT_STATUS_INACTIVE)
|
||||
var/list/difficulties = list(null, null, null)
|
||||
contract_data["difficulties"] = difficulties
|
||||
for(var/difficulty in EXTRACTION_DIFFICULTY_EASY to EXTRACTION_DIFFICULTY_HARD)
|
||||
difficulties[difficulty] = list(name = C.contract.candidate_zones[difficulty].map_name, reward = C.reward_tc[difficulty])
|
||||
if(CONTRACT_STATUS_ACTIVE)
|
||||
contract_data["world_time"] = world.time
|
||||
contract_data["time_left"] = C.extraction_deadline - world.time
|
||||
if(CONTRACT_STATUS_COMPLETED)
|
||||
contract_data["completed_time"] = C.completed_time
|
||||
contract_data["dead_extraction"] = C.dead_extraction
|
||||
if(CONTRACT_STATUS_FAILED)
|
||||
contract_data["fail_reason"] = C.fail_reason
|
||||
if(C.contract.extraction_zone)
|
||||
contract_data["objective"] = list(
|
||||
extraction_zone = C.contract.extraction_zone.map_name,
|
||||
reward_tc = C.reward_tc[C.chosen_difficulty],
|
||||
reward_credits = C.reward_credits,
|
||||
)
|
||||
contracts += list(contract_data)
|
||||
|
||||
data["can_extract"] = current_contract?.contract.can_start_extraction_process(ui_host(), usr) || FALSE
|
||||
if(HUB_PAGE_SHOP)
|
||||
var/list/buyables = list()
|
||||
for(var/p in purchases)
|
||||
var/datum/rep_purchase/P = p
|
||||
buyables += list(list(
|
||||
uid = P.UID(),
|
||||
name = P.name,
|
||||
description = P.description,
|
||||
cost = P.cost,
|
||||
stock = P.stock,
|
||||
))
|
||||
data["buyables"] = buyables
|
||||
|
||||
return data
|
||||
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* # Contract Objective
|
||||
*
|
||||
* Describes the target to kidnap and the extraction area of a [/datum/syndicate_contract].
|
||||
*/
|
||||
/datum/objective/contract
|
||||
// Settings
|
||||
/// Jobs that cannot be the kidnapping target.
|
||||
var/static/list/forbidden_jobs = list(
|
||||
/datum/job/captain,
|
||||
)
|
||||
/// Static whitelist of area names that can be used as an extraction zone, structured by difficulty.
|
||||
/// An area's difficulty should be measured in how crowded it generally is, how out of the way it is and so on.
|
||||
/// Outdoor or invalid areas are filtered out.
|
||||
/// Structure: EXTRACTION_DIFFICULTY_(EASY|MEDIUM|HARD) => list(<area name>)
|
||||
var/static/list/possible_zone_names = list(
|
||||
EXTRACTION_DIFFICULTY_EASY = list(
|
||||
// Rooms
|
||||
"Alternate Construction Area",
|
||||
"Barber Shop",
|
||||
"Escape Shuttle Hallway Podbay",
|
||||
"Garden",
|
||||
"Incinerator",
|
||||
"Locker Room",
|
||||
"Locker Toilets",
|
||||
"Maintenance Bar",
|
||||
"Medical Secondary Storage",
|
||||
"Mechanic Workshop",
|
||||
"Port Emergency Storage",
|
||||
"Psych Room",
|
||||
"Toxins Launch Room",
|
||||
"Toxins Mixing Room",
|
||||
"Turbine",
|
||||
"Virology",
|
||||
"Waste Disposal",
|
||||
// Maintenance
|
||||
"Aft Port Solar Maintenance",
|
||||
"Aft Starboard Solar Maintenance",
|
||||
"Arrivals North Maintenance",
|
||||
"Bar Maintenance",
|
||||
"Cargo Maintenance",
|
||||
"Dormitory Maintenance",
|
||||
"Electrical Maintenance",
|
||||
"EVA Maintenance",
|
||||
"Engineering Maintenance",
|
||||
"Fore Port Solar Maintenance",
|
||||
"Fore Starboard Solar Maintenance",
|
||||
"Genetics Maintenance",
|
||||
"Locker Room Maintenance",
|
||||
"Medbay Maintenance",
|
||||
"Science Maintenance",
|
||||
),
|
||||
EXTRACTION_DIFFICULTY_MEDIUM = list(
|
||||
// Rooms
|
||||
"Aft Primary Hallway",
|
||||
"Atmospherics",
|
||||
"Arcade",
|
||||
"Assembly Line",
|
||||
"Auxiliary Tool Storage",
|
||||
"Break Room",
|
||||
"Blueshield's Office",
|
||||
"Cargo Bay",
|
||||
"Chapel",
|
||||
"Chapel Office",
|
||||
"Clown's Office",
|
||||
"Construction Area",
|
||||
"Courtroom",
|
||||
"Dormitory Toilets",
|
||||
"Engineering",
|
||||
"Engineering Control Room",
|
||||
"Escape Shuttle Hallway",
|
||||
"Experimentation Lab",
|
||||
"Holodeck Alpha",
|
||||
"Hydroponics",
|
||||
"Library",
|
||||
"Mime's Office",
|
||||
"Mining Dock",
|
||||
"Morgue",
|
||||
"Office Supplies",
|
||||
"Pet Store",
|
||||
"Primary Tool Storage",
|
||||
"Research Division",
|
||||
"Security Checkpoint",
|
||||
"Technical Storage",
|
||||
"Teleporter",
|
||||
"Toxins Storage",
|
||||
"Vacant Office",
|
||||
"Research Testing Lab",
|
||||
"Xenobiology Lab",
|
||||
// Maintenance
|
||||
"Atmospherics Maintenance",
|
||||
"Bridge Maintenance",
|
||||
),
|
||||
EXTRACTION_DIFFICULTY_HARD = list(
|
||||
// No AI Chamber because I'm not that sadistic.
|
||||
// Most Bridge areas are excluded because of they'd be basically impossible. So are Brig areas.
|
||||
"AI Satellite Antechamber",
|
||||
"AI Satellite Atmospherics",
|
||||
"AI Satellite Service",
|
||||
"AI Satellite Hallway",
|
||||
"Bar",
|
||||
"Cargo Office",
|
||||
"Central Primary Hallway",
|
||||
"Chemistry",
|
||||
"Chief Engineer's office",
|
||||
"Chief Medical Officer's office",
|
||||
"Cloning Lab",
|
||||
"Cryogenics",
|
||||
"Dorms",
|
||||
"Engineering Equipment Storage",
|
||||
"Engineering Foyer",
|
||||
"EVA Storage",
|
||||
"Gateway",
|
||||
"Genetics Lab",
|
||||
"Gravity Generator",
|
||||
"Head of Personnel's Office",
|
||||
"Heads of Staff Meeting Room",
|
||||
"Kitchen", // Chef CQC is no joke.
|
||||
"Mech Bay",
|
||||
"Medbay",
|
||||
"Medbay Reception",
|
||||
"Medical Storage",
|
||||
"Medical Treatment Center",
|
||||
"Medbay Patient Ward",
|
||||
"Messaging Server Room",
|
||||
"Mr Chang's",
|
||||
"Nanotrasen Representative's Office",
|
||||
"Paramedic",
|
||||
"Port Primary Hallway",
|
||||
"Quartermaster's Office",
|
||||
"Research Director's Office",
|
||||
"Research and Development",
|
||||
"Robotics Lab",
|
||||
"Surgery 1",
|
||||
"Surgery 2",
|
||||
"Telecoms Central Compartment",
|
||||
"Secure Storage",
|
||||
),
|
||||
)
|
||||
// Variables
|
||||
/// The designated area where the kidnapee must be extracted to complete the objective.
|
||||
var/area/extraction_zone = null
|
||||
/// The contract's difficulty. Determines the reward on completion.
|
||||
var/chosen_difficulty = EXTRACTION_DIFFICULTY_EASY
|
||||
/// Associated lazy list of areas the contractor can pick from and extract the kidnapee there.
|
||||
/// Structure: EXTRACTION_DIFFICULTY_(EASY|MEDIUM|HARD) => /area
|
||||
var/list/area/candidate_zones = null
|
||||
/// List of people who cannot be selected as contract target.
|
||||
var/list/datum/mind/target_blacklist = null
|
||||
/// Static list that is basically [/datum/objective/contract/var/possible_zone_names] but with area names replaced by /area objects if available.
|
||||
var/static/list/possible_zones = null
|
||||
/// The owning [/datum/syndicatce_contract].
|
||||
var/datum/syndicate_contract/owning_contract = null
|
||||
/// Name fixer regex because area names have rogue characters sometimes.
|
||||
var/static/regex/name_fixer = regex("(\[a-z0-9 \\'\]+)$", "ig")
|
||||
|
||||
/datum/objective/contract/New(contract)
|
||||
owning_contract = contract
|
||||
// Init static variable
|
||||
if(!possible_zones)
|
||||
// Compute the list of all zones by their name first
|
||||
var/list/all_areas_by_name = list()
|
||||
for(var/a in GLOB.all_areas)
|
||||
var/area/A = a
|
||||
if(A.outdoors || !is_station_level(A.z))
|
||||
continue
|
||||
var/i = findtext(A.map_name, name_fixer)
|
||||
if(i)
|
||||
var/clean_name = copytext(A.map_name, i)
|
||||
clean_name = replacetext(clean_name, "\\", "")
|
||||
all_areas_by_name[clean_name] = A
|
||||
|
||||
possible_zones = list()
|
||||
for(var/difficulty in EXTRACTION_DIFFICULTY_EASY to EXTRACTION_DIFFICULTY_HARD)
|
||||
var/list/difficulty_areas = list()
|
||||
for(var/area_name in possible_zone_names[difficulty])
|
||||
var/area/A = all_areas_by_name[area_name]
|
||||
if(!A)
|
||||
continue
|
||||
difficulty_areas += A
|
||||
possible_zones += list(difficulty_areas)
|
||||
// Select zones
|
||||
for(var/difficulty in EXTRACTION_DIFFICULTY_EASY to EXTRACTION_DIFFICULTY_HARD)
|
||||
pick_candidate_zone(difficulty)
|
||||
return ..()
|
||||
|
||||
/datum/objective/contract/is_invalid_target(datum/mind/possible_target)
|
||||
if((possible_target.assigned_job in forbidden_jobs) || (target_blacklist && (possible_target in target_blacklist)))
|
||||
return TARGET_INVALID_BLACKLISTED
|
||||
return ..()
|
||||
|
||||
/datum/objective/contract/on_target_cryo()
|
||||
if(owning_contract.status in list(CONTRACT_STATUS_COMPLETED, CONTRACT_STATUS_FAILED))
|
||||
return
|
||||
// We pick the target ourselves so we don't want the default behaviour.
|
||||
owning_contract.invalidate()
|
||||
|
||||
/**
|
||||
* Assigns a randomly selected zone to the contract's selectable zone at the given difficulty.
|
||||
*
|
||||
* Arguments:
|
||||
* * difficulty - The difficulty to assign.
|
||||
*/
|
||||
/datum/objective/contract/proc/pick_candidate_zone(difficulty = EXTRACTION_DIFFICULTY_EASY)
|
||||
if(!candidate_zones)
|
||||
candidate_zones = list(null, null, null)
|
||||
candidate_zones[difficulty] = pick(possible_zones[difficulty])
|
||||
|
||||
/**
|
||||
* Updates the objective's information with the given difficulty.
|
||||
*
|
||||
* Arguments:
|
||||
* * difficulty - The chosen difficulty.
|
||||
* * S - The parent [/datum/syndicate_contract].
|
||||
*/
|
||||
/datum/objective/contract/proc/choose_difficulty(difficulty = EXTRACTION_DIFFICULTY_EASY, datum/syndicate_contract/S)
|
||||
. = FALSE
|
||||
if(!ISINDEXSAFE(candidate_zones, difficulty))
|
||||
return
|
||||
|
||||
var/area/A = candidate_zones[difficulty]
|
||||
extraction_zone = A
|
||||
chosen_difficulty = difficulty
|
||||
explanation_text = "Kidnap [S.target_name] by any means and extract them in [A.map_name] using your Contractor Uplink. You will earn [S.reward_tc[difficulty]] telecrystals and [S.reward_credits] credits upon completion. Your reward will be severely reduced if your target is dead."
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Returns whether the extraction process can be started.
|
||||
*
|
||||
* Arguments:
|
||||
* * M - The contractor.
|
||||
* * target - The target.
|
||||
*/
|
||||
/datum/objective/contract/proc/can_start_extraction_process(mob/living/carbon/human/M, mob/living/carbon/human/target)
|
||||
return get_area(M) == extraction_zone && get_area(target) == extraction_zone
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* # Rep Purchase
|
||||
*
|
||||
* Describes something that can be purchased with Contractor Rep.
|
||||
*/
|
||||
/datum/rep_purchase
|
||||
/// The display name of the purchase.
|
||||
var/name = ""
|
||||
/// The description of the purchase.
|
||||
var/description = "This shouldn't appear."
|
||||
/// The price in Contractor Rep of the purchase.
|
||||
var/cost = 0
|
||||
/// How many times the purchase can be made.
|
||||
/// -1 means infinite stock.
|
||||
var/stock = -1
|
||||
|
||||
/**
|
||||
* Attempts to perform the purchase.
|
||||
*
|
||||
* Returns TRUE or FALSE depending on whether the purchase succeeded.
|
||||
*
|
||||
* Arguments:
|
||||
* * hub - The contractor hub.
|
||||
* * user - The user who is making the purchase.
|
||||
*/
|
||||
/datum/rep_purchase/proc/buy(datum/contractor_hub/hub, mob/living/carbon/human/user)
|
||||
. = FALSE
|
||||
if(hub.owner.current != user)
|
||||
to_chat(user, "<span class='warning'>You were not recognized as this hub's original user.</span>")
|
||||
return
|
||||
if(hub.rep < cost)
|
||||
to_chat(user, "<span class='warning'>You do not have enough Rep.</span>")
|
||||
return
|
||||
if(stock == 0)
|
||||
to_chat(user, "<span class='warning'>This item is out of stock.</span>")
|
||||
return
|
||||
else if(stock > 0)
|
||||
stock--
|
||||
hub.rep -= cost
|
||||
on_buy(hub, user)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Called when the purchase was made successfully.
|
||||
*
|
||||
* Arguments:
|
||||
* * hub - The contractor hub.
|
||||
* * user - The user who made the purchase.
|
||||
*/
|
||||
/datum/rep_purchase/proc/on_buy(datum/contractor_hub/hub, mob/living/carbon/human/user)
|
||||
return
|
||||
|
||||
/**
|
||||
* # Rep Purchase - Item
|
||||
*
|
||||
* Describes an item that can be purchased with Contractor Rep.
|
||||
*/
|
||||
/datum/rep_purchase/item
|
||||
/// The typepath of the item to instantiate and give to the buyer on purchase.
|
||||
var/obj/item/item_type = null
|
||||
|
||||
/datum/rep_purchase/item/on_buy(datum/contractor_hub/hub, mob/living/carbon/human/user)
|
||||
..()
|
||||
var/obj/item/I = new item_type(user)
|
||||
user.put_in_hands(I)
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* # Rep Purchase - Contractor Balloon
|
||||
*/
|
||||
/datum/rep_purchase/item/balloon
|
||||
name = "Contractor Balloon"
|
||||
description = "An unique black and gold balloon with no purpose other than showing off. All contracts must be completed in the hardest location to unlock this."
|
||||
cost = 12
|
||||
stock = 1
|
||||
item_type = /obj/item/toy/syndicateballoon/contractor
|
||||
|
||||
/datum/rep_purchase/item/balloon/buy(datum/contractor_hub/hub, mob/living/carbon/human/user)
|
||||
var/eligible = TRUE
|
||||
for(var/c in hub.contracts)
|
||||
var/datum/syndicate_contract/C = c
|
||||
if(C.status != CONTRACT_STATUS_COMPLETED || C.chosen_difficulty != EXTRACTION_DIFFICULTY_HARD)
|
||||
eligible = FALSE
|
||||
break
|
||||
if(!eligible)
|
||||
to_chat(user, "<span class='warning'>All of your contracts must be completed in the hardest location to be eligible for this item.</span>")
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/obj/item/toy/syndicateballoon/contractor
|
||||
name = "contractor balloon"
|
||||
desc = "A black and gold balloon carried only by legendary Syndicate agents."
|
||||
icon_state = "contractorballoon"
|
||||
item_state = "contractorballoon"
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* # Rep Purchase - Blackout
|
||||
*/
|
||||
/datum/rep_purchase/blackout
|
||||
name = "Blackout"
|
||||
description = "Overloads the station's power net, shorting random APCs."
|
||||
cost = 3
|
||||
// Settings
|
||||
/// How long a contractor must wait before calling another blackout, in deciseconds.
|
||||
var/static/cooldown = 15 MINUTES
|
||||
// Variables
|
||||
/// Static cooldown variable for blackouts.
|
||||
var/static/next_blackout = -1
|
||||
|
||||
/datum/rep_purchase/blackout/buy(datum/contractor_hub/hub, mob/living/carbon/human/user)
|
||||
if(next_blackout > world.time)
|
||||
var/timeleft = (next_blackout - world.time) / 10
|
||||
to_chat(user, "<span class='warning'>Another blackout may not be requested for [seconds_to_clock(timeleft)].</span>")
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/datum/rep_purchase/blackout/on_buy(datum/contractor_hub/hub, mob/living/carbon/human/user)
|
||||
..()
|
||||
next_blackout = world.time + cooldown
|
||||
power_failure()
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* # Rep Purchase - Fulton Extraction Kit
|
||||
*/
|
||||
/datum/rep_purchase/item/fulton
|
||||
name = "Fulton Extraction Kit"
|
||||
description = "A balloon that can be used to extract equipment or personnel to a Fulton Recovery Beacon. Anything not bolted down can be moved. Link the pack to a beacon by using the pack in hand."
|
||||
cost = 1
|
||||
stock = 1
|
||||
item_type = /obj/item/storage/box/contractor/fulton_kit
|
||||
|
||||
/obj/item/storage/box/contractor/fulton_kit
|
||||
name = "fulton extraction kit"
|
||||
icon_state = "box_of_doom"
|
||||
|
||||
/obj/item/storage/box/contractor/fulton_kit/New()
|
||||
..()
|
||||
new /obj/item/extraction_pack(src)
|
||||
new /obj/item/fulton_core(src)
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* # Rep Purchase - Contractor Pinpointer
|
||||
*/
|
||||
/datum/rep_purchase/item/pinpointer
|
||||
name = "Contractor Pinpointer"
|
||||
description = "A low accuracy pinpointer that can track anyone in the sector without the need for suit sensors. Can only be used by the first person to activate it."
|
||||
cost = 1
|
||||
stock = 2
|
||||
item_type = /obj/item/pinpointer/crew/contractor
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* # Rep Purchase - Contract Reroll
|
||||
*/
|
||||
/datum/rep_purchase/reroll
|
||||
name = "Contract Reroll"
|
||||
description = "Replaces your inactive contracts with new ones, containing a new target and extraction zones."
|
||||
cost = 2
|
||||
|
||||
/datum/rep_purchase/reroll/buy(datum/contractor_hub/hub, mob/living/carbon/human/user)
|
||||
var/eligible = FALSE
|
||||
for(var/c in hub.contracts)
|
||||
var/datum/syndicate_contract/C = c
|
||||
if(C.status == CONTRACT_STATUS_INACTIVE)
|
||||
eligible = TRUE
|
||||
break
|
||||
if(!eligible)
|
||||
to_chat(user, "<span class='warning'>There are no inactive contracts that can be rerolled.</span>")
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/datum/rep_purchase/reroll/on_buy(datum/contractor_hub/hub, mob/living/carbon/human/user)
|
||||
..()
|
||||
var/changed = 0
|
||||
for(var/c in hub.contracts)
|
||||
var/datum/syndicate_contract/C = c
|
||||
if(C.status == CONTRACT_STATUS_INACTIVE && C.generate())
|
||||
changed++
|
||||
hub.contractor_uplink?.message_holder("Agent, we have replaced [changed] contract\s with new ones.")
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* # Rep Purchase - Contractor Zippo Lighter
|
||||
*/
|
||||
/datum/rep_purchase/item/zippo
|
||||
name = "Contractor Zippo Lighter"
|
||||
description = "An unique black and gold zippo lighter with no purpose other than showing off."
|
||||
cost = 12
|
||||
stock = 1
|
||||
item_type = /obj/item/lighter/zippo/contractor
|
||||
|
||||
/obj/item/lighter/zippo/contractor
|
||||
name = "contractor zippo lighter"
|
||||
desc = "An unique black and gold zippo commonly carried by elite Syndicate agents."
|
||||
icon_state = "contractorzippo"
|
||||
icon_on = "contractorzippoon"
|
||||
icon_off = "contractorzippo"
|
||||
@@ -0,0 +1,575 @@
|
||||
#define DEFAULT_NAME "Unknown"
|
||||
#define DEFAULT_RANK "Unknown"
|
||||
#define EXTRACTION_PHASE_PREPARE 5 SECONDS
|
||||
#define EXTRACTION_PHASE_PORTAL 5 SECONDS
|
||||
#define COMPLETION_NOTIFY_DELAY 5 SECONDS
|
||||
#define RETURN_BRUISE_CHANCE 50
|
||||
#define RETURN_BRUISE_DAMAGE 20
|
||||
#define RETURN_SOUVENIR_CHANCE 10
|
||||
|
||||
/**
|
||||
* # Syndicate Contract
|
||||
*
|
||||
* Describes a contract that can be completed by a [/datum/antagonist/traitor/contractor].
|
||||
*/
|
||||
/datum/syndicate_contract
|
||||
// Settings
|
||||
/// Cooldown before making another extraction request in deciseconds.
|
||||
var/extraction_cooldown = 10 MINUTES
|
||||
/// How long an extraction portal remains before going away. Should be less than [/datum/syndicate_contract/var/extraction_cooldown].
|
||||
var/portal_duration = 5 MINUTES
|
||||
/// How long a target remains in the Syndicate jail.
|
||||
var/prison_time = 4 MINUTES
|
||||
/// List of items a target can get randomly after their return.
|
||||
var/list/obj/item/souvenirs = list(
|
||||
/obj/item/bedsheet/syndie,
|
||||
/obj/item/clothing/under/syndicate/tacticool,
|
||||
/obj/item/coin/antagtoken/syndicate,
|
||||
/obj/item/poster/syndicate_recruitment,
|
||||
/obj/item/reagent_containers/food/snacks/syndicake,
|
||||
/obj/item/reagent_containers/food/snacks/tatortot,
|
||||
/obj/item/storage/box/fakesyndiesuit,
|
||||
/obj/item/storage/fancy/cigarettes/cigpack_syndicate,
|
||||
/obj/item/toy/figure/syndie,
|
||||
/obj/item/toy/nuke,
|
||||
/obj/item/toy/plushie/nukeplushie,
|
||||
/obj/item/toy/sword,
|
||||
/obj/item/toy/syndicateballoon,
|
||||
)
|
||||
/// The base credits reward upon completion. Multiplied by the two lower bounds below.
|
||||
var/credits_base = 100
|
||||
// The lower bound of the credits reward multiplier.
|
||||
var/credits_lower_mult = 25
|
||||
// The upper bound of the credits reward multiplier.
|
||||
var/credits_upper_mult = 40
|
||||
// Implants (non cybernetic ones) that shouldn't be removed when a victim gets kidnapped.
|
||||
// Typecache; initialized in New()
|
||||
var/static/implants_to_keep = null
|
||||
// Variables
|
||||
/// The owning contractor hub.
|
||||
var/datum/contractor_hub/owning_hub = null
|
||||
/// The [/datum/objective/contract] associated to this contract.
|
||||
var/datum/objective/contract/contract = null
|
||||
/// Current contract status.
|
||||
var/status = CONTRACT_STATUS_INVALID
|
||||
/// Formatted station time at which the contract was completed, if applicable.
|
||||
var/completed_time
|
||||
/// Whether the contract was completed with the victim being dead on extraction.
|
||||
var/dead_extraction = FALSE
|
||||
/// Visual reason as to why the contract failed, if applicable.
|
||||
var/fail_reason
|
||||
/// The selected difficulty.
|
||||
var/chosen_difficulty = -1
|
||||
/// The flare indicating the extraction point.
|
||||
var/obj/effect/contractor_flare/extraction_flare = null
|
||||
/// The extraction portal.
|
||||
var/obj/effect/portal/redspace/contractor/extraction_portal = null
|
||||
/// The world.time at which the current extraction fulton will vanish and another extraction can be requested.
|
||||
var/extraction_deadline = -1
|
||||
/// Name of the target to display on the UI.
|
||||
var/target_name
|
||||
/// Fluff message explaining why the kidnapee is the target.
|
||||
var/fluff_message
|
||||
/// The target's photo to display on the UI.
|
||||
var/image/target_photo = null
|
||||
/// Amount of telecrystals the contract will receive upon completion, depending on the chosen difficulty.
|
||||
/// Structure: EXTRACTION_DIFFICULTY_(EASY|MEDIUM|HARD) => number
|
||||
var/list/reward_tc = null
|
||||
/// Amount of credits the contractor will receive upon completion.
|
||||
var/reward_credits = 0
|
||||
/// The kidnapee's belongings. Set upon extraction by the contractor.
|
||||
var/list/obj/item/victim_belongings = null
|
||||
/// Temporary objects that are available to the kidnapee during their time in jail. These are deleted when the victim is returned.
|
||||
var/list/obj/temp_objs = null
|
||||
/// Deadline reached timer handle. Deletes the portal and tells the agent to call extraction again.
|
||||
var/extraction_timer_handle = null
|
||||
/// Prisoner jail timer handle. On completion, returns the prisoner back to station.
|
||||
var/prisoner_timer_handle = null
|
||||
/// Whether the additional fluff story from any contractor completing all of their contracts was made already or not.
|
||||
var/static/nt_am_board_resigned = FALSE
|
||||
|
||||
/datum/syndicate_contract/New(datum/contractor_hub/hub, datum/mind/owner, list/datum/mind/target_blacklist, target_override)
|
||||
// Init settings
|
||||
if(!implants_to_keep)
|
||||
implants_to_keep = typecacheof(list(
|
||||
// These two are specifically handled in code to prevent usage, but are included here for clarity.
|
||||
/obj/item/implant/storage,
|
||||
/obj/item/implant/uplink,
|
||||
// The rest
|
||||
/obj/item/implant/adrenalin,
|
||||
/obj/item/implant/emp,
|
||||
/obj/item/implant/explosive,
|
||||
/obj/item/implant/freedom,
|
||||
/obj/item/implant/traitor,
|
||||
))
|
||||
// Initialize
|
||||
owning_hub = hub
|
||||
contract = new /datum/objective/contract(src)
|
||||
contract.owner = owner
|
||||
contract.target_blacklist = target_blacklist
|
||||
generate(target_override)
|
||||
|
||||
/**
|
||||
* Fills the contract with valid data to be used.
|
||||
*/
|
||||
/datum/syndicate_contract/proc/generate(target_override)
|
||||
. = FALSE
|
||||
// Select the target
|
||||
var/datum/mind/T
|
||||
if(target_override)
|
||||
contract.target = target_override
|
||||
T = target_override
|
||||
else
|
||||
contract.find_target()
|
||||
T = contract.target
|
||||
if(!T)
|
||||
return
|
||||
|
||||
// In case the contract is invalidated
|
||||
contract.extraction_zone = null
|
||||
contract.target_blacklist |= T
|
||||
for(var/difficulty in EXTRACTION_DIFFICULTY_EASY to EXTRACTION_DIFFICULTY_HARD)
|
||||
contract.pick_candidate_zone(difficulty)
|
||||
|
||||
// Fill data
|
||||
var/datum/data/record/R = find_record("name", T.name, GLOB.data_core.general)
|
||||
target_name = "[R?.fields["name"] || T.current?.real_name || DEFAULT_NAME], the [R?.fields["rank"] || T.assigned_role || DEFAULT_RANK]"
|
||||
reward_credits = credits_base * rand(credits_lower_mult, credits_upper_mult)
|
||||
|
||||
// Fluff message
|
||||
var/base = pick(strings(CONTRACT_STRINGS_WANTED, "basemessage"))
|
||||
var/verb_string = pick(strings(CONTRACT_STRINGS_WANTED, "verb"))
|
||||
var/noun = pickweight(strings(CONTRACT_STRINGS_WANTED, "noun"))
|
||||
var/location = pickweight(strings(CONTRACT_STRINGS_WANTED, "location"))
|
||||
fluff_message = "[base] [verb_string] [noun] [location]."
|
||||
|
||||
// Photo
|
||||
if(R?.fields["photo"])
|
||||
var/icon/temp = new('icons/turf/floors.dmi', pick("floor", "wood", "darkfull", "stairs"))
|
||||
temp.Blend(R.fields["photo"], ICON_OVERLAY)
|
||||
target_photo = temp
|
||||
|
||||
// OK
|
||||
status = CONTRACT_STATUS_INACTIVE
|
||||
fail_reason = ""
|
||||
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Begins the contract if possible.
|
||||
*
|
||||
* Arguments:
|
||||
* * M - The contractor.
|
||||
* * difficulty - The chosen difficulty level.
|
||||
*/
|
||||
/datum/syndicate_contract/proc/initiate(mob/living/M, difficulty = EXTRACTION_DIFFICULTY_EASY)
|
||||
. = FALSE
|
||||
if(status != CONTRACT_STATUS_INACTIVE || !ISINDEXSAFE(reward_tc, difficulty))
|
||||
return
|
||||
else if(owning_hub.current_contract)
|
||||
to_chat(M, "<span class='warning'>You already have an ongoing contract!</span>")
|
||||
return
|
||||
|
||||
if(!contract.choose_difficulty(difficulty, src))
|
||||
return FALSE
|
||||
|
||||
status = CONTRACT_STATUS_ACTIVE
|
||||
chosen_difficulty = difficulty
|
||||
owning_hub.current_contract = src
|
||||
owning_hub.contractor_uplink?.message_holder("Request for this contract confirmed. Good luck, agent.", 'sound/machines/terminal_prompt.ogg')
|
||||
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Marks the contract as completed and gives the rewards to the contractor.
|
||||
*
|
||||
* Arguments:
|
||||
* * target_dead - Whether the target was extracted dead.
|
||||
*/
|
||||
/datum/syndicate_contract/proc/complete(target_dead = FALSE)
|
||||
if(status != CONTRACT_STATUS_ACTIVE)
|
||||
return
|
||||
var/final_tc_reward = reward_tc[chosen_difficulty]
|
||||
if(target_dead)
|
||||
final_tc_reward = CEILING(final_tc_reward * owning_hub.dead_penalty, 1)
|
||||
// Notify the Hub
|
||||
owning_hub.on_completion(final_tc_reward, reward_credits)
|
||||
// Finalize
|
||||
status = CONTRACT_STATUS_COMPLETED
|
||||
completed_time = station_time_timestamp()
|
||||
dead_extraction = target_dead
|
||||
addtimer(CALLBACK(src, .proc/notify_completion, final_tc_reward, reward_credits, target_dead), COMPLETION_NOTIFY_DELAY)
|
||||
|
||||
/**
|
||||
* Marks the contract as invalid and effectively cancels it for later use.
|
||||
*/
|
||||
/datum/syndicate_contract/proc/invalidate()
|
||||
if(!owning_hub)
|
||||
return
|
||||
if(status in list(CONTRACT_STATUS_COMPLETED, CONTRACT_STATUS_FAILED))
|
||||
return
|
||||
|
||||
clean_up()
|
||||
|
||||
var/pre_text
|
||||
if(src == owning_hub.current_contract)
|
||||
owning_hub.current_contract = null
|
||||
pre_text = "Agent, it appears the target you were tasked to kidnap can no longer be reached."
|
||||
else
|
||||
pre_text = "Agent, a still inactive contract can no longer be done as the target has gone off our sensors."
|
||||
|
||||
var/outcome_text
|
||||
if(generate())
|
||||
status = CONTRACT_STATUS_INACTIVE
|
||||
outcome_text = "Luckily, there is another target on station we can interrogate. A new contract can be found in your uplink."
|
||||
else
|
||||
// Too bad.
|
||||
status = CONTRACT_STATUS_INVALID
|
||||
outcome_text = "Unfortunately, we could not find another target to interrogate and thus we cannot give you another contract."
|
||||
|
||||
if(owning_hub.contractor_uplink)
|
||||
owning_hub.contractor_uplink.message_holder("[pre_text] [outcome_text]", 'sound/machines/terminal_prompt_deny.ogg')
|
||||
SStgui.update_uis(owning_hub)
|
||||
|
||||
/**
|
||||
* Marks the contract as failed and stops it.
|
||||
*
|
||||
* Arguments:
|
||||
* * difficulty - The visual reason as to why the contract failed.
|
||||
*/
|
||||
/datum/syndicate_contract/proc/fail(reason)
|
||||
if(status != CONTRACT_STATUS_ACTIVE)
|
||||
return
|
||||
|
||||
// Update info
|
||||
owning_hub.current_contract = null
|
||||
status = CONTRACT_STATUS_FAILED
|
||||
fail_reason = reason
|
||||
// Notify
|
||||
clean_up()
|
||||
owning_hub.contractor_uplink?.message_holder("You failed to kidnap the target, agent. Do not disappoint us again.", 'sound/machines/terminal_prompt_deny.ogg')
|
||||
|
||||
/**
|
||||
* Initiates the extraction process if conditions are met.
|
||||
*
|
||||
* Arguments:
|
||||
* * M - The contractor.
|
||||
*/
|
||||
/datum/syndicate_contract/proc/start_extraction_process(obj/item/contractor_uplink/U, mob/living/carbon/human/M)
|
||||
if(!U?.Adjacent(M))
|
||||
return "Where in space is your uplink?!"
|
||||
else if(status != CONTRACT_STATUS_ACTIVE)
|
||||
return "This contract is not active."
|
||||
else if(extraction_deadline > world.time)
|
||||
return "Another extraction attempt cannot be made yet."
|
||||
|
||||
var/mob/target = contract.target.current
|
||||
if(!target)
|
||||
invalidate()
|
||||
return "The target is no longer on our sensors. Your contract will be invalidated and replaced with another one."
|
||||
else if(!contract.can_start_extraction_process(M, target))
|
||||
return "You and the target must be standing in the extraction area to start the extraction process."
|
||||
|
||||
M.visible_message("<span class='notice'>[M] starts entering a cryptic series of characters on [U].</span>",\
|
||||
"<span class='notice'>You start entering an extraction signal to your handlers on [U]...</span>")
|
||||
if(do_after(M, EXTRACTION_PHASE_PREPARE, target = M))
|
||||
if(!U.Adjacent(M) || extraction_deadline > world.time)
|
||||
return
|
||||
var/obj/effect/contractor_flare/F = new(get_turf(M))
|
||||
extraction_flare = F
|
||||
extraction_deadline = world.time + extraction_cooldown
|
||||
M.visible_message("<span class='notice'>[M] enters a mysterious code on [U] and pulls a black and gold flare from [M.p_their()] belongings before lighting it.</span>",\
|
||||
"<span class='notice'>You finish entering the signal on [U] and light an extraction flare, initiating the extraction process.</span>")
|
||||
addtimer(CALLBACK(src, .proc/open_extraction_portal, U, M, F), EXTRACTION_PHASE_PORTAL)
|
||||
extraction_timer_handle = addtimer(CALLBACK(src, .proc/deadline_reached), portal_duration, TIMER_STOPPABLE)
|
||||
|
||||
/**
|
||||
* Opens the extraction portal.
|
||||
*
|
||||
* Arguments:
|
||||
* * U - The uplink.
|
||||
* * M - The contractor.
|
||||
* * F - The flare.
|
||||
*/
|
||||
/datum/syndicate_contract/proc/open_extraction_portal(obj/item/contractor_uplink/U, mob/living/carbon/human/M, obj/effect/contractor_flare/F)
|
||||
if(!U || !M || status != CONTRACT_STATUS_ACTIVE)
|
||||
invalidate()
|
||||
return
|
||||
else if(!F)
|
||||
U.message_holder("Extraction flare could not be located, agent. Ensure the extraction zone is clear before signaling us.", 'sound/machines/terminal_prompt_deny.ogg')
|
||||
return
|
||||
else if(!ismob(contract.target.current))
|
||||
invalidate()
|
||||
return
|
||||
U.message_holder("Extraction signal received, agent. [GLOB.using_map.full_name]'s bluespace transport jamming systems have been sabotaged. "\
|
||||
+ "We have opened a temporary portal at your flare location - proceed to the target's extraction by inserting them into the portal.", 'sound/effects/confirmdropoff.ogg')
|
||||
// Open a portal
|
||||
var/obj/effect/portal/redspace/contractor/P = new(get_turf(F), pick(GLOB.syndieprisonwarp), null, 0)
|
||||
P.contract = src
|
||||
P.contractor_mind = M.mind
|
||||
P.target_mind = contract.target
|
||||
extraction_portal = P
|
||||
do_sparks(4, FALSE, P.loc)
|
||||
|
||||
/**
|
||||
* Called when a contract target has been extracted through the portal.
|
||||
*
|
||||
* Arguments:
|
||||
* * M - The target mob.
|
||||
* * P - The extraction portal.
|
||||
*/
|
||||
/datum/syndicate_contract/proc/target_received(mob/living/M, obj/effect/portal/redspace/contractor/P)
|
||||
INVOKE_ASYNC(src, .proc/clean_up)
|
||||
complete(M.stat == DEAD)
|
||||
handle_target_experience(M, P)
|
||||
|
||||
/**
|
||||
* Notifies the uplink's holder that a contract has been completed.
|
||||
*
|
||||
* Arguments:
|
||||
* * tc - How many telecrystals they have received.
|
||||
* * creds - How many credits they have received.
|
||||
* * target_dead - Whether the target was extracted dead.
|
||||
*/
|
||||
/datum/syndicate_contract/proc/notify_completion(tc, creds, target_dead)
|
||||
var/penalty_text = ""
|
||||
if(target_dead)
|
||||
penalty_text = " (penalty applied as the target was extracted dead)"
|
||||
owning_hub.contractor_uplink?.message_holder("Well done, agent. The package has been received and will be processed shortly before being returned. "\
|
||||
+ "As agreed, you have been credited with [tc] telecrystals[penalty_text] and [creds] credits.", 'sound/machines/terminal_prompt_confirm.ogg')
|
||||
|
||||
/**
|
||||
* Handles the target's experience from extraction.
|
||||
*
|
||||
* Arguments:
|
||||
* * M - The target mob.
|
||||
* * P - The extraction portal.
|
||||
*/
|
||||
/datum/syndicate_contract/proc/handle_target_experience(mob/living/M, obj/effect/portal/redspace/contractor/P)
|
||||
var/turf/T = get_turf(P)
|
||||
var/mob/living/carbon/human/H = M
|
||||
|
||||
// Prepare their return
|
||||
prisoner_timer_handle = addtimer(CALLBACK(src, .proc/handle_target_return, M, T), prison_time, TIMER_STOPPABLE)
|
||||
LAZYSET(GLOB.prisoner_belongings.prisoners, M, src)
|
||||
|
||||
// Shove all of the victim's items in the secure locker.
|
||||
victim_belongings = list()
|
||||
var/list/obj/item/stuff_to_transfer = list()
|
||||
|
||||
// Cybernetic implants get removed first (to deal with NODROP stuff)
|
||||
for(var/obj/item/organ/internal/cyberimp/I in H.internal_organs)
|
||||
// Greys get to keep their implant
|
||||
if(isgrey(H) && istype(I, /obj/item/organ/internal/cyberimp/brain/speech_translator))
|
||||
continue
|
||||
// Try removing it
|
||||
I = I.remove(H)
|
||||
if(I)
|
||||
stuff_to_transfer += I
|
||||
|
||||
// Regular items get removed in second
|
||||
for(var/obj/item/I in M)
|
||||
// Any items we don't want to take from them?
|
||||
if(istype(H))
|
||||
// Keep their uniform and shoes
|
||||
if(I == H.w_uniform || I == H.shoes)
|
||||
continue
|
||||
// Plasmamen are no use if they're crispy
|
||||
if(isplasmaman(H) && I == H.head)
|
||||
continue
|
||||
|
||||
// Any kind of non-syndie implant gets potentially removed (mindshield, etc)
|
||||
if(istype(I, /obj/item/implant))
|
||||
if(istype(I, /obj/item/implant/storage)) // Storage stays, but items within get confiscated
|
||||
var/obj/item/implant/storage/storage_implant = I
|
||||
for(var/it in storage_implant.storage)
|
||||
storage_implant.storage.remove_from_storage(it)
|
||||
stuff_to_transfer += it
|
||||
continue
|
||||
else if(istype(I, /obj/item/implant/uplink)) // Uplink stays, but is jammed while in jail
|
||||
var/obj/item/implant/uplink/uplink_implant = I
|
||||
uplink_implant.hidden_uplink.is_jammed = TRUE
|
||||
continue
|
||||
else if(is_type_in_typecache(I, implants_to_keep))
|
||||
continue
|
||||
qdel(I)
|
||||
continue
|
||||
|
||||
if(M.unEquip(I))
|
||||
stuff_to_transfer += I
|
||||
|
||||
// Transfer it all (or drop it if not possible)
|
||||
for(var/i in stuff_to_transfer)
|
||||
var/obj/item/I = i
|
||||
if(GLOB.prisoner_belongings.give_item(I))
|
||||
victim_belongings += I
|
||||
else if(!((ABSTRACT|NODROP) in I.flags)) // Anything that can't be put on hold, just drop it on the ground
|
||||
I.forceMove(T)
|
||||
|
||||
// Give some species the necessary to survive. Courtesy of the Syndicate.
|
||||
if(istype(H))
|
||||
var/obj/item/tank/emergency_oxygen/tank
|
||||
var/obj/item/clothing/mask/breath/mask
|
||||
if(isvox(H))
|
||||
tank = new /obj/item/tank/emergency_oxygen/nitrogen(H)
|
||||
mask = new /obj/item/clothing/mask/breath/vox(H)
|
||||
else if(isplasmaman(H))
|
||||
tank = new /obj/item/tank/emergency_oxygen/plasma(H)
|
||||
mask = new /obj/item/clothing/mask/breath(H)
|
||||
|
||||
if(tank)
|
||||
H.equip_to_appropriate_slot(tank)
|
||||
H.equip_to_appropriate_slot(mask)
|
||||
tank.toggle_internals(H, TRUE)
|
||||
|
||||
M.update_icons()
|
||||
|
||||
// Supply them with some chow. How generous is the Syndicate?
|
||||
var/obj/item/reagent_containers/food/snacks/breadslice/food = new(get_turf(M))
|
||||
food.name = "stale bread"
|
||||
food.desc = "Looks like your captors care for their prisoners as much as their bread."
|
||||
food.trash = null
|
||||
food.reagents.add_reagent("nutriment", 5) // It may be stale, but it still has to be nutritive enough for the whole duration!
|
||||
if(prob(10))
|
||||
// Mold adds a bit of spice to it
|
||||
food.name = "moldy bread"
|
||||
food.reagents.add_reagent("fungus", 1)
|
||||
|
||||
var/obj/item/reagent_containers/food/drinks/drinkingglass/drink = new(get_turf(M))
|
||||
drink.reagents.add_reagent("tea", 25) // British coders beware, tea in glasses
|
||||
|
||||
temp_objs = list(food, drink)
|
||||
|
||||
// Narrate their kidnapping and torturing experience.
|
||||
if(M.stat != DEAD)
|
||||
// Heal them up - gets them out of crit/soft crit.
|
||||
M.reagents.add_reagent("omnizine", 20)
|
||||
|
||||
to_chat(M, "<span class='warning'>You feel strange...</span>")
|
||||
M.Paralyse(30 SECONDS_TO_LIFE_CYCLES)
|
||||
M.EyeBlind(35 SECONDS_TO_LIFE_CYCLES)
|
||||
M.EyeBlurry(35 SECONDS_TO_LIFE_CYCLES)
|
||||
M.AdjustConfused(35 SECONDS_TO_LIFE_CYCLES)
|
||||
|
||||
sleep(6 SECONDS)
|
||||
to_chat(M, "<span class='warning'>That portal did something to you...</span>")
|
||||
|
||||
sleep(6.5 SECONDS)
|
||||
to_chat(M, "<span class='warning'>Your head pounds... It feels like it's going to burst out your skull!</span>")
|
||||
|
||||
sleep(3 SECONDS)
|
||||
to_chat(M, "<span class='warning'>Your head pounds...</span>")
|
||||
|
||||
sleep(10 SECONDS)
|
||||
to_chat(M, "<span class='specialnotice'>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 send you back...\"</i></span>")
|
||||
|
||||
/**
|
||||
* Handles the target's return to station.
|
||||
*
|
||||
* Arguments:
|
||||
* * M - The target mob.
|
||||
*/
|
||||
/datum/syndicate_contract/proc/handle_target_return(mob/living/M)
|
||||
var/list/turf/possible_turfs = list()
|
||||
for(var/turf/T in contract.extraction_zone.contents)
|
||||
if(!isspaceturf(T) && !isunsimulatedturf(T) && !is_blocked_turf(T))
|
||||
possible_turfs += T
|
||||
|
||||
var/turf/destination = length(possible_turfs) ? pick(possible_turfs) : pick(GLOB.latejoin)
|
||||
|
||||
// Make a closet to return the target and their items neatly
|
||||
var/obj/structure/closet/closet = new
|
||||
closet.forceMove(destination)
|
||||
|
||||
// Return their items
|
||||
for(var/i in victim_belongings)
|
||||
var/obj/item/I = GLOB.prisoner_belongings.remove_item(i)
|
||||
if(!I)
|
||||
continue
|
||||
I.forceMove(closet)
|
||||
|
||||
victim_belongings = list()
|
||||
|
||||
// Clean up
|
||||
var/obj/item/implant/uplink/uplink_implant = locate() in M
|
||||
uplink_implant?.hidden_uplink?.is_jammed = FALSE
|
||||
|
||||
QDEL_LIST(temp_objs)
|
||||
|
||||
// Chance for souvenir or bruises
|
||||
if(prob(RETURN_SOUVENIR_CHANCE))
|
||||
to_chat(M, "<span class='notice'>Your captors left you a souvenir for your troubles!</span>")
|
||||
var/obj/item/souvenir = pick(souvenirs)
|
||||
new souvenir(closet)
|
||||
else if(prob(RETURN_BRUISE_CHANCE) && M.health >= 50)
|
||||
to_chat(M, "<span class='warning'>You were roughed up a little by your captors before being sent back!</span>")
|
||||
M.adjustBruteLoss(RETURN_BRUISE_DAMAGE)
|
||||
|
||||
// Return them a bit confused.
|
||||
M.visible_message("<span class='notice'>[M] vanishes...</span>")
|
||||
M.forceMove(closet)
|
||||
M.Paralyse(3 SECONDS_TO_LIFE_CYCLES)
|
||||
M.EyeBlurry(5 SECONDS_TO_LIFE_CYCLES)
|
||||
M.AdjustConfused(5 SECONDS_TO_LIFE_CYCLES)
|
||||
M.Dizzy(35)
|
||||
do_sparks(4, FALSE, destination)
|
||||
|
||||
// Newscaster story
|
||||
var/datum/data/record/R = find_record("name", contract.target.name, GLOB.data_core.general)
|
||||
var/initials = ""
|
||||
for(var/s in splittext(R?.fields["name"] || M.real_name || DEFAULT_NAME, " "))
|
||||
initials = initials + "[s[1]]."
|
||||
|
||||
var/datum/feed_message/FM = new
|
||||
FM.author = "Nyx Daily"
|
||||
FM.admin_locked = TRUE
|
||||
FM.body = "Suspected Syndicate activity was reported in the system. Rumours have surfaced about a [R?.fields["rank"] || M?.mind.assigned_role || DEFAULT_RANK] aboard the [GLOB.using_map.full_name] being the victim of a kidnapping.\n\n" +\
|
||||
"A reliable source said the following: There was a note with the victim's initials which were \"[initials]\" and a scribble saying \"[fluff_message]\""
|
||||
GLOB.news_network.get_channel_by_name("Nyx Daily")?.add_message(FM)
|
||||
|
||||
// Bonus story if the contractor has done all their contracts (appears only once per round)
|
||||
if(!nt_am_board_resigned && (owning_hub.completed_contracts >= owning_hub.num_contracts))
|
||||
nt_am_board_resigned = TRUE
|
||||
|
||||
var/datum/feed_message/FM2 = new
|
||||
FM2.author = "Nyx Daily"
|
||||
FM2.admin_locked = TRUE
|
||||
FM2.body = "Nanotrasen's Asset Management board has resigned today after a series of kidnappings aboard the [GLOB.using_map.full_name]." +\
|
||||
"One former member of the board was heard saying: \"I can't do this anymore. How does a single shift on this cursed station manage to cost us over ten million Credits in ransom payments? Is there no security aboard?!\""
|
||||
GLOB.news_network.get_channel_by_name("Nyx Daily")?.add_message(FM2)
|
||||
|
||||
for(var/nc in GLOB.allNewscasters)
|
||||
var/obj/machinery/newscaster/NC = nc
|
||||
NC.alert_news("Nyx Daily")
|
||||
|
||||
prisoner_timer_handle = null
|
||||
GLOB.prisoner_belongings.prisoners[M] = null
|
||||
|
||||
/**
|
||||
* Called when the extraction window closes.
|
||||
*/
|
||||
/datum/syndicate_contract/proc/deadline_reached()
|
||||
clean_up()
|
||||
owning_hub.contractor_uplink?.message_holder("The window for extraction has closed and so did the portal, agent. You will need to call for another extraction so we can open a new portal.")
|
||||
SStgui.update_uis(owning_hub)
|
||||
|
||||
/**
|
||||
* Cleans up the contract.
|
||||
*/
|
||||
/datum/syndicate_contract/proc/clean_up()
|
||||
QDEL_NULL(extraction_flare)
|
||||
QDEL_NULL(extraction_portal)
|
||||
deltimer(extraction_timer_handle)
|
||||
extraction_deadline = -1
|
||||
extraction_timer_handle = null
|
||||
|
||||
#undef DEFAULT_NAME
|
||||
#undef DEFAULT_RANK
|
||||
#undef EXTRACTION_PHASE_PREPARE
|
||||
#undef EXTRACTION_PHASE_PORTAL
|
||||
#undef COMPLETION_NOTIFY_DELAY
|
||||
#undef RETURN_BRUISE_CHANCE
|
||||
#undef RETURN_BRUISE_DAMAGE
|
||||
#undef RETURN_SOUVENIR_CHANCE
|
||||
@@ -0,0 +1,27 @@
|
||||
/obj/item/melee/classic_baton/telescopic/contractor
|
||||
name = "contractor baton"
|
||||
desc = "A compact, specialised baton issued to Syndicate contractors. Applies light electrical shocks to targets."
|
||||
// Overrides
|
||||
affect_silicon = TRUE
|
||||
stun_time = 1
|
||||
cooldown = 2.5 SECONDS
|
||||
force_off = 5
|
||||
force_on = 15
|
||||
item_state_on = "contractor_baton"
|
||||
icon_state_off = "contractor_baton_0"
|
||||
icon_state_on = "contractor_baton_1"
|
||||
stun_sound = 'sound/weapons/contractorbatonhit.ogg'
|
||||
extend_sound = 'sound/weapons/contractorbatonextend.ogg'
|
||||
// Settings
|
||||
/// Stamina damage to deal on stun.
|
||||
var/stamina_damage = 70
|
||||
/// Jitter to deal on stun.
|
||||
var/jitter_amount = 5 SECONDS_TO_JITTER
|
||||
/// Stutter to deal on stun.
|
||||
var/stutter_amount = 10 SECONDS_TO_LIFE_CYCLES
|
||||
|
||||
/obj/item/melee/classic_baton/telescopic/contractor/stun(mob/living/target, mob/living/user)
|
||||
. = ..()
|
||||
target.adjustStaminaLoss(stamina_damage)
|
||||
target.Jitter(jitter_amount)
|
||||
target.AdjustStuttering(stutter_amount)
|
||||
@@ -0,0 +1,98 @@
|
||||
/obj/item/storage/box/syndie_kit/contractor
|
||||
name = "contractor kit"
|
||||
desc = "A box containing supplies destined to Syndicate contractors."
|
||||
// Settings
|
||||
/// Amount of random items to be added to the contractor kit.
|
||||
/// See [/obj/item/storage/box/syndie_kit/contractor/var/item_list] for the available items.
|
||||
var/num_additional_items = 3
|
||||
/// Items that may be part of the random items given to a contractor as part of their kit.
|
||||
/// Ideally all about 5 TC or less and fit the theme. Some of these are nukeops only.
|
||||
/// One item may show up only once.
|
||||
var/list/item_list = list(
|
||||
// Offensive
|
||||
/obj/item/gun/projectile/automatic/c20r/toy,
|
||||
/obj/item/storage/box/syndie_kit/throwing_weapons,
|
||||
/obj/item/pen/edagger,
|
||||
/obj/item/gun/projectile/automatic/toy/pistol/riot,
|
||||
/obj/item/soap/syndie,
|
||||
/obj/item/storage/box/syndie_kit/dart_gun,
|
||||
/obj/item/gun/syringe/rapidsyringe,
|
||||
/obj/item/storage/backpack/duffel/syndie/x4,
|
||||
// Mixed
|
||||
/obj/item/storage/box/syndie_kit/emp,
|
||||
/obj/item/flashlight/emp,
|
||||
// Support
|
||||
/obj/item/storage/box/syndidonkpockets,
|
||||
/obj/item/storage/belt/military/traitor,
|
||||
/obj/item/clothing/shoes/chameleon/noslip,
|
||||
/obj/item/storage/toolbox/syndicate,
|
||||
/obj/item/storage/backpack/duffel/syndie/surgery,
|
||||
/obj/item/multitool/ai_detect,
|
||||
/obj/item/encryptionkey/binary,
|
||||
/obj/item/jammer,
|
||||
/obj/item/implanter/freedom,
|
||||
)
|
||||
|
||||
|
||||
/obj/item/storage/box/syndie_kit/contractor/New()
|
||||
..()
|
||||
new /obj/item/paper/contractor_guide(src)
|
||||
new /obj/item/contractor_uplink(src)
|
||||
new /obj/item/storage/box/syndie_kit/contractor_loadout(src)
|
||||
// Add the random items
|
||||
for(var/i in 1 to num_additional_items)
|
||||
var/obj/item/I = pick_n_take(item_list)
|
||||
new I(src)
|
||||
|
||||
/obj/item/storage/box/syndie_kit/contractor_loadout
|
||||
name = "contractor standard loadout box"
|
||||
desc = "A standard issue box included in a contractor kit."
|
||||
|
||||
/obj/item/storage/box/syndie_kit/contractor_loadout/New()
|
||||
..()
|
||||
new /obj/item/clothing/head/helmet/space/syndicate/contractor(src)
|
||||
new /obj/item/clothing/suit/space/syndicate/contractor(src)
|
||||
new /obj/item/melee/classic_baton/telescopic/contractor(src)
|
||||
new /obj/item/clothing/under/chameleon(src)
|
||||
new /obj/item/clothing/mask/chameleon(src)
|
||||
new /obj/item/card/id/syndicate(src)
|
||||
new /obj/item/storage/fancy/cigarettes/cigpack_syndicate(src)
|
||||
new /obj/item/lighter/zippo(src)
|
||||
|
||||
/obj/item/paper/contractor_guide
|
||||
name = "contractor guide"
|
||||
|
||||
/obj/item/paper/contractor_guide/Initialize()
|
||||
info = {"<p>Welcome agent, congratulations on your new position as a Syndicate contractor. On top of your already assigned objectives,
|
||||
this kit will provide you contracts to take on for telecrystal payments.</p>
|
||||
<p>Provided within is 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 your hidden uplink. We also provide you a chameleon jumpsuit and mask, both of which can be changed
|
||||
to any form you need for the moment. The cigarettes are a special blend - they will heal your injuries slowly over time.</p>
|
||||
<p>Three additional items have been randomly selected from what we had available and included in this kit. We hope they're useful to you for your mission.</p>
|
||||
<p>The Contractor Hub, available in your contractor uplink, can 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 Contractor Uplink</h3>
|
||||
<ol>
|
||||
<li>Take the contractor uplink from this kit and activate it.</li>
|
||||
<li>From there, 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 whether they were alive or not.</li>
|
||||
<li>Contracts are completed by bringing the target to the designated extraction zone, calling for extraction, and putting them
|
||||
inside the extraction portal.</li>
|
||||
</ol>
|
||||
<p>Be careful when accepting a contract. While you'll be able to see its extraction zone beforehand, cancelling will make it
|
||||
unavailable to take on again.</p>
|
||||
<h3>Extracting</h3>
|
||||
<ol>
|
||||
<li>Make sure both yourself and your target are at the extraction zone.</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 extraction portal to appear.</li>
|
||||
<li>Grab your target, and drag them into the extraction portal.</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 ..()
|
||||
@@ -0,0 +1,48 @@
|
||||
/obj/item/pinpointer/crew/contractor
|
||||
name = "contractor pinpointer"
|
||||
desc = "A handheld tracking device that points to crew without needing suit sensors at the cost of accuracy."
|
||||
icon_state = "pinoff_contractor"
|
||||
icon_off = "pinoff_contractor"
|
||||
icon_null = "pinonnull_contractor"
|
||||
icon_direct = "pinondirect_contractor"
|
||||
icon_close = "pinonclose_contractor"
|
||||
icon_medium = "pinonmedium_contractor"
|
||||
icon_far = "pinonfar_contractor"
|
||||
/// The minimum range for the pinpointer to function properly.
|
||||
var/min_range = 20
|
||||
/// The first person to have used the item. If this is set already, no one else can use it.
|
||||
var/mob/owner = null
|
||||
|
||||
/obj/item/pinpointer/crew/contractor/point_at(atom/target)
|
||||
if(target && trackable(target))
|
||||
// Calc dir
|
||||
var/turf/T = get_turf(target)
|
||||
var/turf/L = get_turf(src)
|
||||
dir = get_dir(L, T)
|
||||
// Calc dist
|
||||
var/dist = get_dist(L, T)
|
||||
if(ISINRANGE(dist, -1, min_range))
|
||||
icon_state = icon_direct
|
||||
else if(ISINRANGE(dist, min_range + 1, min_range + 8))
|
||||
icon_state = icon_close
|
||||
else if(ISINRANGE(dist, min_range + 9, min_range + 16))
|
||||
icon_state = icon_medium
|
||||
else if(ISINRANGE(dist, min_range + 16, INFINITY))
|
||||
icon_state = icon_far
|
||||
else
|
||||
icon_state = icon_null
|
||||
|
||||
/obj/item/pinpointer/crew/contractor/trackable(mob/living/carbon/human/H)
|
||||
var/turf/here = get_turf(src)
|
||||
var/turf/there = get_turf(H)
|
||||
return here && there && there.z == here.z
|
||||
|
||||
/obj/item/pinpointer/crew/contractor/attack_self(mob/living/user)
|
||||
if(owner)
|
||||
if(owner != user)
|
||||
to_chat(user, "<span class='warning'>[src] refuses to do anything.</span>")
|
||||
return
|
||||
else
|
||||
owner = user
|
||||
to_chat(user, "<span class='notice'>[src] now recognizes you as its sole user.</span>")
|
||||
return ..()
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* # Contractor Uplink
|
||||
*
|
||||
* A contractor's point of contact with their Contractor Hub.
|
||||
*/
|
||||
/obj/item/contractor_uplink
|
||||
name = "contractor uplink"
|
||||
desc = "A standard, Syndicate issued tablet for handling important contracts while on the field."
|
||||
icon = 'icons/obj/device.dmi'
|
||||
icon_state = "contractor_uplink"
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
slot_flags = SLOT_BELT
|
||||
origin_tech = "programming=5;syndicate=4" // Hackerman encryption
|
||||
/// The Contractor Hub associated with this uplink.
|
||||
var/datum/contractor_hub/hub = null
|
||||
|
||||
/obj/item/contractor_uplink/Destroy()
|
||||
// Right now, one uplink = one hub so this is fine.
|
||||
QDEL_NULL(hub)
|
||||
return ..()
|
||||
|
||||
/obj/item/contractor_uplink/attack_self(mob/user)
|
||||
hub.ui_interact(user)
|
||||
|
||||
/**
|
||||
* Sends a message to the mob holding this item.
|
||||
*
|
||||
* Arguments:
|
||||
* * text - The text to send.
|
||||
* * sndfile - The sound to play to the holder only.
|
||||
*/
|
||||
/obj/item/contractor_uplink/proc/message_holder(text, sndfile)
|
||||
var/mob/living/M = loc
|
||||
while(!istype(M) && M?.loc)
|
||||
M = M.loc
|
||||
if(!istype(M))
|
||||
return
|
||||
|
||||
to_chat(M, "<span class='notice'>[bicon(src)] Incoming encrypted transmission from your handlers. Message as follows:</span><br />"\
|
||||
+ "<span class='boldnotice'>[text]</span>")
|
||||
if(sndfile)
|
||||
M.playsound_local(get_turf(M), sndfile, 30, FALSE)
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* # Contractor Extraction Flare
|
||||
*
|
||||
* Used to designate where the [/obj/effect/portal/redspace/contractor] should spawn during the extraction process.
|
||||
*/
|
||||
/obj/effect/contractor_flare
|
||||
name = "contractor extraction flare"
|
||||
icon = 'icons/obj/lighting.dmi'
|
||||
icon_state = "flare-contractor-on"
|
||||
|
||||
/obj/effect/contractor_flare/New()
|
||||
..()
|
||||
playsound(loc, 'sound/goonstation/misc/matchstick_light.ogg', 50, TRUE)
|
||||
set_light(8, l_color = "#FFD165")
|
||||
|
||||
/obj/effect/contractor_flare/Destroy()
|
||||
new /obj/effect/decal/cleanable/ash(loc)
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* # Contractor Extraction Portal
|
||||
*
|
||||
* Used to extract contract targets and send them to the Syndicate jail for a few minutes.
|
||||
*/
|
||||
/obj/effect/portal/redspace/contractor
|
||||
name = "suspicious portal"
|
||||
icon_state = "portal-syndicate"
|
||||
/// The contract associated with this portal.
|
||||
var/datum/syndicate_contract/contract = null
|
||||
/// The mind of the contractor. Used to tell them they shouldn't be taking the portal.
|
||||
var/datum/mind/contractor_mind = null
|
||||
/// The mind of the kidnapping target. Prevents non-targets from taking the portal.
|
||||
var/datum/mind/target_mind = null
|
||||
|
||||
/obj/effect/portal/redspace/contractor/can_teleport(atom/movable/A)
|
||||
var/mob/living/M = A
|
||||
if(!istype(M))
|
||||
return FALSE
|
||||
if(M == usr && M.mind == contractor_mind)
|
||||
to_chat(M, "<span class='warning'>The portal is here to extract the contract target, not you!</span>")
|
||||
return FALSE
|
||||
if(M.mind != target_mind)
|
||||
if(usr?.mind == contractor_mind) // Contractor shoving a non-target into the portal
|
||||
to_chat(M, "<span class='warning'>Somehow you are not sure [M] is the target you have to kidnap.</span>")
|
||||
return FALSE
|
||||
else if(usr == M) // Non-target trying to enter the portal
|
||||
to_chat(M, "<span class='warning'>Somehow you are not sure this is a good idea.</span>")
|
||||
return FALSE
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/obj/effect/portal/redspace/contractor/teleport(atom/movable/M)
|
||||
. = ..()
|
||||
if(.)
|
||||
contract.target_received(M, src)
|
||||
|
||||
/**
|
||||
* # Prisoner Belongings Closet
|
||||
*
|
||||
* Cannot be opened. Contains the belongings of all kidnapped targets.
|
||||
* Any item added inside stops processing and starts again when removed.
|
||||
*/
|
||||
/obj/structure/closet/secure_closet/contractor
|
||||
anchored = TRUE
|
||||
can_be_emaged = FALSE
|
||||
max_integrity = INFINITY
|
||||
/// Lazy list of atoms which should process again when taken out.
|
||||
var/list/atom/suspended_items = null
|
||||
/// Lazy, associative list of prisoners being held as part of a contract.
|
||||
/// Structure: [/mob/living] => [/datum/syndicate_contract]
|
||||
var/list/prisoners = null
|
||||
|
||||
/obj/structure/closet/secure_closet/contractor/New()
|
||||
..()
|
||||
if(!GLOB.prisoner_belongings)
|
||||
GLOB.prisoner_belongings = src
|
||||
|
||||
/obj/structure/closet/secure_closet/contractor/allowed(mob/M)
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Tries to add an atom for temporary holding, suspending its processing.
|
||||
*
|
||||
* Arguments:
|
||||
* * A - The atom to add.
|
||||
*/
|
||||
/obj/structure/closet/secure_closet/contractor/proc/give_item(atom/A)
|
||||
if(ismob(A)) // No mobs allowed
|
||||
return FALSE
|
||||
var/obj/item/I = A
|
||||
if(!istype(I))
|
||||
return FALSE
|
||||
if(I.isprocessing)
|
||||
LAZYSET(suspended_items, I.UID(), list(I, (I in SSfastprocess.processing)))
|
||||
STOP_PROCESSING(SSobj, I)
|
||||
I.loc = src // No forceMove because we don't want to trigger anything here
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Removes an atom from temporary holding.
|
||||
*
|
||||
* Arguments:
|
||||
* * A - The atom to remove.
|
||||
*/
|
||||
/obj/structure/closet/secure_closet/contractor/proc/remove_item(atom/A)
|
||||
if(!(A in contents))
|
||||
return
|
||||
var/obj/item/I = A
|
||||
if(!istype(I))
|
||||
return FALSE
|
||||
// Resume processing if it was paused
|
||||
var/list/tuple = LAZYACCESS(suspended_items, I.UID())
|
||||
if(tuple)
|
||||
if(tuple[2])
|
||||
START_PROCESSING(SSfastprocess, I)
|
||||
else
|
||||
START_PROCESSING(SSobj, I)
|
||||
suspended_items[I.UID()] = null
|
||||
I.loc = loc // No forceMove because we don't want to trigger anything here
|
||||
return I
|
||||
@@ -12,7 +12,8 @@
|
||||
var/should_equip = TRUE
|
||||
var/traitor_kind = TRAITOR_HUMAN
|
||||
var/list/assigned_targets = list() // This includes assassinate as well as steal objectives. prevents duplicate objectives
|
||||
|
||||
/// Whether the traitor can specialize into a contractor.
|
||||
var/is_contractor = FALSE
|
||||
|
||||
/datum/antagonist/traitor/on_gain()
|
||||
if(owner.current && isAI(owner.current))
|
||||
@@ -410,3 +411,8 @@
|
||||
<b>The code responses were:</b> <span class='redtext'>[responses]</span><br>"
|
||||
|
||||
return message
|
||||
|
||||
/datum/antagonist/traitor/specialization(datum/mind/new_owner)
|
||||
if(isAI(new_owner?.current) || !is_contractor)
|
||||
return ..()
|
||||
return new /datum/antagonist/traitor/contractor
|
||||
|
||||
Reference in New Issue
Block a user