From bbfa777d9548e730d584ed32a1a7f81f82e92c35 Mon Sep 17 00:00:00 2001 From: Zonespace <41448081+Zonespace27@users.noreply.github.com> Date: Sun, 20 Mar 2022 13:12:17 -0700 Subject: [PATCH] [SEMI-MODULAR] OPFOR Expansion: Contracting (#12033) * contractor woo * 0 - 0.5 * heh, no * new event * dead. * aye * cleaner code * yep * code doc * corrections * woo * req change --- .../~skyrat_defines/examine_defines.dm | 1 + .../modular_computers/hardware/hard_drive.dm | 2 +- .../contractor/code/datums/contract.dm | 259 +++++++++++ .../code/datums/contractor_datum.dm | 33 ++ .../contractor/code/datums/contractor_hub.dm | 82 ++++ .../code/datums/contractor_items.dm | 176 +++++++ .../code/datums/contractor_support.dm | 53 +++ .../code/datums/contractor_tablet.dm | 213 +++++++++ .../contractor/code/datums/mind_datum.dm | 4 + .../modules/contractor/code/items/items.dm | 138 ++++++ .../modules/contractor/code/items/modsuit.dm | 249 ++++++++++ .../modules/contractor/code/items/tablet.dm | 17 + .../modules/contractor/icons/fulton.dmi | Bin 0 -> 1352 bytes .../modules/contractor/icons/modsuit.dmi | Bin 0 -> 1230 bytes .../contractor/icons/modsuit_modules.dmi | Bin 0 -> 407 bytes .../modules/contractor/icons/worn_modsuit.dmi | Bin 0 -> 1829 bytes .../modules/cryosleep/code/cryopod.dm | 10 + .../modules/examinemore/code/examine_more.dm | 18 + .../opposing_force/code/equipment/loadouts.dm | 6 + .../code/opposing_force_datum.dm | 5 + tgstation.dme | 10 + .../tgui/interfaces/SyndContractor.js | 428 ++++++++++++++++++ 22 files changed, 1703 insertions(+), 1 deletion(-) create mode 100644 modular_skyrat/modules/contractor/code/datums/contract.dm create mode 100644 modular_skyrat/modules/contractor/code/datums/contractor_datum.dm create mode 100644 modular_skyrat/modules/contractor/code/datums/contractor_hub.dm create mode 100644 modular_skyrat/modules/contractor/code/datums/contractor_items.dm create mode 100644 modular_skyrat/modules/contractor/code/datums/contractor_support.dm create mode 100644 modular_skyrat/modules/contractor/code/datums/contractor_tablet.dm create mode 100644 modular_skyrat/modules/contractor/code/datums/mind_datum.dm create mode 100644 modular_skyrat/modules/contractor/code/items/items.dm create mode 100644 modular_skyrat/modules/contractor/code/items/modsuit.dm create mode 100644 modular_skyrat/modules/contractor/code/items/tablet.dm create mode 100644 modular_skyrat/modules/contractor/icons/fulton.dmi create mode 100644 modular_skyrat/modules/contractor/icons/modsuit.dmi create mode 100644 modular_skyrat/modules/contractor/icons/modsuit_modules.dmi create mode 100644 modular_skyrat/modules/contractor/icons/worn_modsuit.dmi create mode 100644 tgui/packages/tgui/interfaces/SyndContractor.js diff --git a/code/__DEFINES/~skyrat_defines/examine_defines.dm b/code/__DEFINES/~skyrat_defines/examine_defines.dm index 2c942cd3215..64cb62cbb80 100644 --- a/code/__DEFINES/~skyrat_defines/examine_defines.dm +++ b/code/__DEFINES/~skyrat_defines/examine_defines.dm @@ -12,3 +12,4 @@ See the examinemore module for information. #define EXAMINE_CHECK_ROLE "role" //For displaying description information based on a specific ROLE, e.g. traitor. Remember to set the special_desc_role var on the item. #define EXAMINE_CHECK_JOB "job" //For displaying descriptors for specific jobs, e.g scientist. Remember to set the special_desc_job var on the item. #define EXAMINE_CHECK_FACTION "faction" //For displaying descriptors for mob factions, e.g. a zombie, or... turrets. Or syndicate. Remember to set special_desc_factions. +#define EXAMINE_CHECK_CONTRACTOR "contractor" // For OPFOR contractors and syndicate agents. diff --git a/code/modules/modular_computers/hardware/hard_drive.dm b/code/modules/modular_computers/hardware/hard_drive.dm index d499fca4443..4c1b1602cea 100644 --- a/code/modules/modular_computers/hardware/hard_drive.dm +++ b/code/modules/modular_computers/hardware/hard_drive.dm @@ -179,7 +179,7 @@ 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 + var/datum/opposing_force/opfor_data // Syndicate hard drive has the user's data baked directly into it on creation //SKYRAT EDIT - OPFOR, NOT TRAITOR /// For tablets given to nuke ops /obj/item/computer_hardware/hard_drive/small/nukeops diff --git a/modular_skyrat/modules/contractor/code/datums/contract.dm b/modular_skyrat/modules/contractor/code/datums/contract.dm new file mode 100644 index 00000000000..6f07fd370e3 --- /dev/null +++ b/modular_skyrat/modules/contractor/code/datums/contract.dm @@ -0,0 +1,259 @@ +#define RANSOM_LOWER 25 +#define RANSOM_UPPER 75 +#define CONTRACTOR_RANSOM_CUT 0.35 + +/datum/syndicate_contract + /// Unique ID tied to the contract + var/id = 0 + /// If the contract is the contractor's current one + var/status = CONTRACT_STATUS_INACTIVE + /// Reference to the objective datum + var/datum/objective/contract/contract = new() + /// Target's job + var/target_rank + /// A number in multiples of 100, anywhere from 2500 credits to 7500, station cost when someone is kidnapped + var/ransom = 0 + /// TC payout size, will be small, medium, or large. + var/payout_type + /// Mad-libs style wanted message, just flavor. + var/wanted_message + /// List of the items the victim had on them prior to kidnapping. + var/list/victim_belongings = list() + +/datum/syndicate_contract/New(contract_owner, blacklist, type = CONTRACT_PAYOUT_SMALL) + contract.owner = contract_owner + payout_type = type + + generate(blacklist) + +/// Generation of the contract, called on New() +/datum/syndicate_contract/proc/generate(blacklist) + contract.find_target(null, blacklist) + + var/datum/data/record/record + if (contract.target) + record = find_record("name", contract.target.name, GLOB.data_core.general) + + if (record) + target_rank = record.fields["rank"] + else + target_rank = "Unknown" + + if (payout_type == CONTRACT_PAYOUT_LARGE) + contract.payout_bonus = rand(9,13) + else if (payout_type == CONTRACT_PAYOUT_MEDIUM) + contract.payout_bonus = rand(6,8) + else + contract.payout_bonus = rand(2,4) + + contract.payout = rand(1, 2) + contract.generate_dropoff() + + ransom = 100 * rand(RANSOM_LOWER, RANSOM_UPPER) + + var/base = pick_list(WANTED_FILE, "basemessage") + var/verb_string = pick_list(WANTED_FILE, "verb") + var/noun = pick_list_weighted(WANTED_FILE, "noun") + var/location = pick_list_weighted(WANTED_FILE, "location") + wanted_message = "[base] [verb_string] [noun] [location]." + +/// Handler to find a valid turn and launch victim collector +/datum/syndicate_contract/proc/handle_extraction(mob/living/user) + if(!(contract.target && contract.dropoff_check(user, contract.target.current))) + return FALSE + + var/turf/free_location = find_obstruction_free_location(3, user, contract.dropoff) + + if(!free_location) + return FALSE + + launch_extraction_pod(free_location) + return TRUE + + +/// Launch the pod to collect our victim. +/datum/syndicate_contract/proc/launch_extraction_pod(turf/empty_pod_turf) + var/obj/structure/closet/supplypod/extractionpod/empty_pod = new() + + RegisterSignal(empty_pod, COMSIG_ATOM_ENTERED, .proc/enter_check) + + empty_pod.stay_after_drop = TRUE + empty_pod.reversing = TRUE + empty_pod.explosionSize = list(0,0,0,1) + empty_pod.leavingSound = 'sound/effects/podwoosh.ogg' + + new /obj/effect/pod_landingzone(empty_pod_turf, empty_pod) + +/datum/syndicate_contract/proc/enter_check(datum/source, mob/living/sent_mob) + SIGNAL_HANDLER + if(!istype(source, /obj/structure/closet/supplypod/extractionpod)) + return + if(!istype(sent_mob)) + return + var/datum/opposing_force/opfor_data = contract.owner.opposing_force + + if (sent_mob == contract.target.current) + opfor_data.contractor_hub.contract_TC_to_redeem += contract.payout + opfor_data.contractor_hub.contracts_completed += 1 + + if (sent_mob.stat != DEAD) + opfor_data.contractor_hub.contract_TC_to_redeem += contract.payout_bonus + + status = CONTRACT_STATUS_COMPLETE + + if (opfor_data.contractor_hub.current_contract == src) + opfor_data.contractor_hub.current_contract = null + + opfor_data.contractor_hub.contract_rep += 2 + else + status = CONTRACT_STATUS_ABORTED // Sending a sent_mob that wasn't even yours is as good as just aborting it + + if (opfor_data.contractor_hub.current_contract == src) + opfor_data.contractor_hub.current_contract = null + + if (iscarbon(sent_mob)) + for(var/obj/item/sent_mob_item in sent_mob) + if (ishuman(sent_mob)) + var/mob/living/carbon/human/sent_mob_human = sent_mob + if(sent_mob_item == sent_mob_human.w_uniform) + continue //So all they're left with are shoes and uniform. + if(sent_mob_item == sent_mob_human.shoes) + continue + + + sent_mob.transferItemToLoc(sent_mob_item) + victim_belongings.Add(sent_mob_item) + + var/obj/structure/closet/supplypod/extractionpod/pod = source + + // Handle the pod returning + pod.startExitSequence(pod) + + if (ishuman(sent_mob)) + var/mob/living/carbon/human/sent_mob_human = sent_mob + + // After we remove items, at least give them what they need to live. + sent_mob_human.dna.species.give_important_for_life(sent_mob_human) + + // After pod is sent we start the victim narrative/heal. + INVOKE_ASYNC(src, .proc/handle_victim_experience, sent_mob) + + // This is slightly delayed because of the sleep calls above to handle the narrative. + // We don't want to tell the station instantly. + var/points_to_check + var/datum/bank_account/bank = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(bank) + points_to_check = bank.account_balance + if(points_to_check >= ransom) + bank.adjust_money(-ransom) + else + bank.adjust_money(-points_to_check) + + priority_announce("One of your crew was captured by a rival organisation - we've needed to pay their ransom to bring them back. \ + As is policy we've taken a portion of the station's funds to offset the overall cost.", null, null, null, "Nanotrasen Asset Protection") + + INVOKE_ASYNC(src, .proc/finish_enter) + +/// Called when person is finished shoving in, awards ransome money +/datum/syndicate_contract/proc/finish_enter() + sleep(3 SECONDS) + + // Pay contractor their portion of ransom + if(!(status == CONTRACT_STATUS_COMPLETE)) + return + var/obj/item/card/id/owner_id = contract.owner.current?.get_idcard(TRUE) + + if(owner_id?.registered_account) + owner_id.registered_account.adjust_money(ransom * CONTRACTOR_RANSOM_CUT) + + owner_id.registered_account.bank_card_talk("We've processed the ransom, agent. Here's your cut - your balance is now \ + [owner_id.registered_account.account_balance] credits.", TRUE) + +/// They're off to holding - handle the return timer and give some text about what's going on. +/datum/syndicate_contract/proc/handle_victim_experience(mob/living/target) + // Ship 'em back - dead or alive, 4 minutes wait. + // Even if they weren't the target, we're still treating them the same. + addtimer(CALLBACK(src, .proc/return_victim, target), 4 MINUTES) + + if (target.stat == DEAD) + return + // Heal them up - gets them out of crit/soft crit. If omnizine is removed in the future, this needs to be replaced with a + // method of healing them, consequence free, to a reasonable amount of health. + target.reagents.add_reagent(/datum/reagent/medicine/omnizine, 20) + + target.flash_act() + target.add_confusion(10) + target.blur_eyes(5) + to_chat(target, span_warning("You feel strange...")) + sleep(6 SECONDS) + to_chat(target, span_warning("That pod did something to you...")) + target.Dizzy(35) + sleep(6 SECONDS) + to_chat(target, span_warning("Your head pounds... It feels like it's going to burst out your skull!")) + target.flash_act() + target.add_confusion(20) + target.blur_eyes(3) + sleep(3 SECONDS) + to_chat(target, span_warning("Your head pounds...")) + sleep(10 SECONDS) + target.flash_act() + target.Unconscious(200) + to_chat(target, span_hypnophrase(span_reallybig(">A million voices echo in your head... \"Your mind held many valuable secrets - \ + we thank you for providing them. Your value is expended, and you will be ransomed back to your station. We always get paid, \ + so it's only a matter of time before we ship you back...\""))) + target.blur_eyes(10) + target.Dizzy(15) + target.add_confusion(20) + +/// We're returning the victim +/datum/syndicate_contract/proc/return_victim(mob/living/target) + var/list/possible_drop_loc = list() + + for(var/turf/possible_drop in contract.dropoff.contents) + if(!(!isspaceturf(possible_drop) && !isclosedturf(possible_drop))) + continue + if(!possible_drop.is_blocked_turf()) + possible_drop_loc.Add(possible_drop) + + if (length(possible_drop_loc) > 0) + var/pod_rand_loc = rand(1, length(possible_drop_loc)) + + var/obj/structure/closet/supplypod/return_pod = new() + return_pod.bluespace = TRUE + return_pod.explosionSize = list(0,0,0,0) + return_pod.style = STYLE_SYNDICATE + + do_sparks(8, FALSE, target) + target.visible_message(span_notice("[target] vanishes...")) + + for(var/obj/item/target_item in target) + if(ishuman(target)) + var/mob/living/carbon/human/human_target = target + if(target_item == human_target.w_uniform) + continue //So all they're left with are shoes and uniform. + if(target_item == human_target.shoes) + continue + target.dropItemToGround(target_item) + + for(var/obj/item/target_item in victim_belongings) + target_item.forceMove(return_pod) + + target.forceMove(return_pod) + + target.flash_act() + target.blur_eyes(30) + target.Dizzy(35) + target.add_confusion(20) + + new /obj/effect/pod_landingzone(possible_drop_loc[pod_rand_loc], return_pod) + else + to_chat(target, "A million voices echo in your head... \"Seems where you got sent here from won't \ + be able to handle our pod... You will die here instead.\"") + if(!iscarbon(target)) + return + var/mob/living/carbon/unlucky_fellow = target + unlucky_fellow.death() + +#undef RANSOM_LOWER +#undef RANSOM_UPPER +#undef CONTRACTOR_RANSOM_CUT diff --git a/modular_skyrat/modules/contractor/code/datums/contractor_datum.dm b/modular_skyrat/modules/contractor/code/datums/contractor_datum.dm new file mode 100644 index 00000000000..f7d4e09f41e --- /dev/null +++ b/modular_skyrat/modules/contractor/code/datums/contractor_datum.dm @@ -0,0 +1,33 @@ +/// End-round generation proc +/datum/opposing_force/proc/contractor_round_end() + var/result = "" + var/total_spent_rep = 0 + + var/completed_contracts = contractor_hub.contracts_completed + var/tc_total = contractor_hub.contract_paid_out + contractor_hub.contract_TC_to_redeem + + var/contractor_item_icons = "" // Icons of purchases + var/contractor_support_unit = "" // Set if they had a support unit - and shows appended to their contracts completed + + // Get all the icons/total cost for all our items bought + for (var/datum/contractor_item/contractor_purchase in contractor_hub.purchased_items) + contractor_item_icons += "\[ [contractor_purchase.name] - [contractor_purchase.cost] Rep

[contractor_purchase.desc]
\]
" + + total_spent_rep += contractor_purchase.cost + + // Special case for reinforcements, we want to show their ckey and name on round end. + if (istype(contractor_purchase, /datum/contractor_item/contractor_partner)) + var/datum/contractor_item/contractor_partner/partner = contractor_purchase + contractor_support_unit += "
[partner.partner_mind.key] played [partner.partner_mind.current.name], their contractor support unit." + + if (length(contractor_hub.purchased_items)) + result += "
(used [total_spent_rep] Rep) " + result += contractor_item_icons + result += "
" + if (completed_contracts > 0) + var/plural_check = "contract[completed_contracts > 1 ? "s" : ""]" + + result += "Completed [span_greentext("[completed_contracts]")] [plural_check] for a total of \ + [span_greentext("[tc_total] TC")]![contractor_support_unit]
" + + return result diff --git a/modular_skyrat/modules/contractor/code/datums/contractor_hub.dm b/modular_skyrat/modules/contractor/code/datums/contractor_hub.dm new file mode 100644 index 00000000000..2afddfed149 --- /dev/null +++ b/modular_skyrat/modules/contractor/code/datums/contractor_hub.dm @@ -0,0 +1,82 @@ +#define LOWEST_TC 30 + +/datum/contractor_hub + /// How much reputation the contractor has + var/contract_rep = 0 + /// What contractor items can be purchased + var/list/hub_items = list() + /// List of what the contractor's purchased + var/list/purchased_items = list() + /// Static of contractor_item subtypes + var/static/list/contractor_items = subtypesof(/datum/contractor_item) + /// Reference to the current contract datum + var/datum/syndicate_contract/current_contract + /// List of all contract datums the contractor has available + var/list/datum/syndicate_contract/assigned_contracts = list() + /// used as a blacklist to make sure we're not assigning targets already assigned + var/list/assigned_targets = list() + /// NUmber of how many contracts you've done + var/contracts_completed = 0 + /// How many TC you've paid out in contracts + var/contract_paid_out = 0 + /// Amount of TC that has yet to be redeemed + var/contract_TC_to_redeem = 0 + +/// Generates a list of all valid hub items to set for purchase +/datum/contractor_hub/proc/create_hub_items() + for(var/path in contractor_items) + var/datum/contractor_item/contractor_item = new path + + hub_items.Add(contractor_item) + +/// Create initial list of contracts +/datum/contractor_hub/proc/create_contracts(datum/mind/owner) + + // 6 initial contracts + var/list/to_generate = list( + CONTRACT_PAYOUT_LARGE, + CONTRACT_PAYOUT_MEDIUM, + CONTRACT_PAYOUT_MEDIUM, + CONTRACT_PAYOUT_SMALL, + CONTRACT_PAYOUT_SMALL, + CONTRACT_PAYOUT_SMALL + ) + + //What the fuck + if(length(to_generate) > length(GLOB.data_core.locked)) + to_generate.Cut(1, length(GLOB.data_core.locked)) + + var/total = 0 + var/lowest_paying_sum = 0 + var/datum/syndicate_contract/lowest_paying_contract + + // Randomise order, so we don't have contracts always in payout order. + to_generate = shuffle(to_generate) + + // Support contract generation happening multiple times + var/start_index = 1 + if (length(assigned_contracts)) + start_index = length(assigned_contracts) + 1 + + // Generate contracts, and find the lowest paying. + for(var/contract_gen in 1 to length(to_generate)) + var/datum/syndicate_contract/contract_to_add = new(owner, assigned_targets, to_generate[contract_gen]) + var/contract_payout_total = contract_to_add.contract.payout + contract_to_add.contract.payout_bonus + + assigned_targets.Add(contract_to_add.contract.target) + + if (!lowest_paying_contract || (contract_payout_total < lowest_paying_sum)) + lowest_paying_sum = contract_payout_total + lowest_paying_contract = contract_to_add + + total += contract_payout_total + contract_to_add.id = start_index + assigned_contracts.Add(contract_to_add) + + start_index++ + + // If the threshold for TC payouts isn't reached, boost the lowest paying contract + if (total < LOWEST_TC) + lowest_paying_contract.contract.payout_bonus += (LOWEST_TC - total) + +#undef LOWEST_TC diff --git a/modular_skyrat/modules/contractor/code/datums/contractor_items.dm b/modular_skyrat/modules/contractor/code/datums/contractor_items.dm new file mode 100644 index 00000000000..5c30ac95af4 --- /dev/null +++ b/modular_skyrat/modules/contractor/code/datums/contractor_items.dm @@ -0,0 +1,176 @@ +/datum/contractor_item + /// Name of the item datum + var/name + /// Description of the item datum + var/desc + /// Item path to spawn, no item path means you need to override `handle_purchase()` + var/item + /// fontawesome icon to use inside the hub - https://fontawesome.com/icons/ + var/item_icon = "broadcast-tower" + /// Any number above 0 for how many times it can be bought in a round for a single traitor. -1 is unlimited. + var/limited = -1 + /// Cost of the item in contract rep. + var/cost + +/// Subtract cost, and spawn if it's an item. +/datum/contractor_item/proc/handle_purchase(datum/contractor_hub/hub, mob/living/user) + if(hub.contract_rep >= cost) + hub.contract_rep -= cost + else + return FALSE + + if(limited >= 1) + limited -= 1 + else + return FALSE + + hub.purchased_items.Add(src) + + user.playsound_local(user, 'sound/machines/uplinkpurchase.ogg', 100) + + if(item && ispath(item)) + var/atom/item_to_create = new item(get_turf(user)) + + if(user.put_in_hands(item_to_create)) + to_chat(user, span_notice("Your purchase materializes into your hands!")) + else + to_chat(user, span_notice("Your purchase materializes onto the floor.")) + + return item_to_create + else if(item && !ispath(item)) + stack_trace("Contractor item [src] has an item that isn't a path.") + return TRUE + +/datum/contractor_item/contract_reroll + name = "Contract Reroll" + desc = "Request a reroll of your current contract list. Will generate a new target, payment, and dropoff for the contracts you currently have available." + item_icon = "dice" + limited = 3 + cost = 0 + +/datum/contractor_item/contract_reroll/handle_purchase(datum/contractor_hub/hub) + . = ..() + + if(!(.)) + return + // We're not regenerating already completed/aborted/extracting contracts, but we don't want to repeat their targets. + var/list/new_target_list = list() + for(var/datum/syndicate_contract/contract_check in hub.assigned_contracts) + if (contract_check.status != CONTRACT_STATUS_ACTIVE && contract_check.status != CONTRACT_STATUS_INACTIVE) + if (contract_check.contract.target) + new_target_list.Add(contract_check.contract.target) + continue + + // Reroll contracts without duplicates + for(var/datum/syndicate_contract/rerolling_contract in hub.assigned_contracts) + if (rerolling_contract.status != CONTRACT_STATUS_ACTIVE && rerolling_contract.status != CONTRACT_STATUS_INACTIVE) + continue + + rerolling_contract.generate(new_target_list) + new_target_list.Add(rerolling_contract.contract.target) + + // Set our target list with the new set we've generated. + hub.assigned_targets = new_target_list + +/datum/contractor_item/contractor_pinpointer + name = "Contractor Pinpointer" + desc = "A pinpointer that finds targets even without active suit sensors. Due to taking advantage of an exploit within the system, it can't pinpoint to the same accuracy as the traditional models. Becomes permanently locked to the user that first activates it." + item = /obj/item/pinpointer/crew/contractor + item_icon = "search-location" + limited = 2 + cost = 1 + +/datum/contractor_item/fulton_extraction_kit + name = "Fulton Extraction Kit" + desc = "For getting your target across the station to those difficult dropoffs. Place the beacon somewhere secure, and link the pack. Activating the pack on your target in space will send them over to the beacon - make sure they're not just going to run away though!" + item = /obj/item/storage/box/contractor/fulton_extraction + item_icon = "parachute-box" + limited = 1 + cost = 1 + +/datum/contractor_item/contractor_partner + name = "Reinforcements" + desc = "Upon purchase we'll contact available units in the area. Should there be an agent free, we'll send them down to assist you immediately. If no units are free, we give a full refund." + item_icon = "user-friends" + limited = 1 + cost = 2 + var/datum/mind/partner_mind = null + +/datum/contractor_item/contractor_partner/handle_purchase(datum/contractor_hub/hub, mob/living/user) + . = ..() + + if(!(.)) + return + to_chat(user, span_notice("The uplink vibrates quietly, connecting to nearby agents...")) + + var/list/mob/dead/observer/candidates = poll_ghost_candidates("Do you want to play as the Contractor Support Unit for [user.real_name]?", ROLE_PAI, FALSE, 100, POLL_IGNORE_CONTRACTOR_SUPPORT) + + if(LAZYLEN(candidates)) + var/mob/dead/observer/picked_obs = pick(candidates) + spawn_contractor_partner(user, picked_obs.key) + else + to_chat(user, span_notice("No available agents at this time, please try again later.")) + + // refund and add the limit back. + limited += 1 + hub.contract_rep += cost + hub.purchased_items -= src + +/datum/contractor_item/contractor_partner/proc/spawn_contractor_partner(mob/living/user, key) + var/mob/living/carbon/human/partner = new() + var/datum/outfit/contractor_partner/partner_outfit = new() + + partner_outfit.equip(partner) + + var/obj/structure/closet/supplypod/arrival_pod = new(null, STYLE_SYNDICATE) + arrival_pod.explosionSize = list(0,0,0,1) + arrival_pod.bluespace = TRUE + + var/turf/free_location = find_obstruction_free_location(2, user) + + // We really want to send them - if we can't find a nice location just land it on top of them. + if (!free_location) + free_location = get_turf(user) + + partner.forceMove(arrival_pod) + partner.ckey = key + + /// We give a reference to the mind that'll be the support unit + partner_mind = partner.mind + partner_mind.make_contractor_support() + + to_chat(partner_mind.current, "\n[span_alertwarning("[user.real_name] is your superior. Follow any, and all orders given by them. You're here to support their mission only.")]") + to_chat(partner_mind.current, "[span_alertwarning("Should they perish, or be otherwise unavailable, you're to assist other active agents in this mission area to the best of your ability.")]\n\n") + + new /obj/effect/pod_landingzone(free_location, arrival_pod) + +/datum/contractor_item/blackout + name = "Blackout" + desc = "Request Syndicate Command to distrupt the station's powernet. Disables power across the station for a short duration." + item_icon = "bolt" + limited = 2 + cost = 3 + +/datum/contractor_item/blackout/handle_purchase(datum/contractor_hub/hub) + . = ..() + + if(!(.)) + return + power_fail(35, 50) + priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", ANNOUNCER_POWEROFF) + +/datum/contractor_item/comms_blackout + name = "Comms Outage" + desc = "Request Syndicate Command to disable station Telecommunications. Disables telecommunications across the station for a medium duration." + item_icon = "phone-slash" + limited = 2 + cost = 3 + +/datum/contractor_item/comms_blackout/handle_purchase(datum/contractor_hub/hub) + . = ..() + + if(!(.)) + return + var/datum/round_event_control/event = locate(/datum/round_event_control/communications_blackout) in SSevents.control + event.runEvent() + diff --git a/modular_skyrat/modules/contractor/code/datums/contractor_support.dm b/modular_skyrat/modules/contractor/code/datums/contractor_support.dm new file mode 100644 index 00000000000..98f3fef3e63 --- /dev/null +++ b/modular_skyrat/modules/contractor/code/datums/contractor_support.dm @@ -0,0 +1,53 @@ +/datum/antagonist/traitor/contractor_support + name = "Contractor Support Unit" + antag_moodlet = /datum/mood_event/focused + + show_in_roundend = FALSE // We're already adding them in to the contractor's roundend. + give_objectives = TRUE // We give them their own custom objective. + show_in_antagpanel = FALSE // Not a proper/full antag. + give_uplink = FALSE // Don't give them an uplink. + /// Team datum that contains the contractor and the support unit + var/datum/team/contractor_team/contractor_team + +/// Team for storing both the contractor and their support unit - only really for the HUD and admin logging. +/datum/team/contractor_team + show_roundend_report = FALSE + +/datum/antagonist/traitor/contractor_support/forge_traitor_objectives() + var/datum/objective/generic_objective = new + + generic_objective.name = "Follow the Contractor's Orders" + generic_objective.explanation_text = "Follow your orders. Assist agents in this mission area." + + generic_objective.completed = TRUE + + objectives += generic_objective + +/datum/outfit/contractor_partner + name = "Contractor Support Unit" + + uniform = /obj/item/clothing/under/chameleon + suit = /obj/item/clothing/suit/chameleon + back = /obj/item/storage/backpack + belt = /obj/item/pda/chameleon + mask = /obj/item/clothing/mask/cigarette/syndicate + shoes = /obj/item/clothing/shoes/chameleon/noslip + ears = /obj/item/radio/headset/chameleon + id = /obj/item/card/id/advanced/chameleon + r_hand = /obj/item/storage/toolbox/syndicate + id_trim = /datum/id_trim/chameleon/operative + + backpack_contents = list( + /obj/item/storage/box/survival, + /obj/item/implanter/uplink, + /obj/item/clothing/mask/chameleon, + /obj/item/storage/fancy/cigarettes/cigpack_syndicate, + /obj/item/lighter + ) + +/datum/outfit/contractor_partner/post_equip(mob/living/carbon/human/partner, visualsOnly) + . = ..() + var/obj/item/clothing/mask/cigarette/syndicate/cig = partner.get_item_by_slot(ITEM_SLOT_MASK) + + // pre-light their cig + cig.light() diff --git a/modular_skyrat/modules/contractor/code/datums/contractor_tablet.dm b/modular_skyrat/modules/contractor/code/datums/contractor_tablet.dm new file mode 100644 index 00000000000..d65d959c3ef --- /dev/null +++ b/modular_skyrat/modules/contractor/code/datums/contractor_tablet.dm @@ -0,0 +1,213 @@ +/datum/computer_file/program/contract_uplink + filename = "contractor uplink" + filedesc = "Syndicate Contractor Uplink" + category = PROGRAM_CATEGORY_MISC + program_icon_state = "assign" + extended_desc = "A standard, Syndicate issued system for handling important contracts while on the field." + size = 10 + requires_ntnet = FALSE + available_on_ntnet = FALSE + unsendable = TRUE + undeletable = TRUE + tgui_id = "SyndContractor" + program_icon = "tasks" + /// Error message if there is one + var/error = "" + /// If the info screen is displayed or not + var/info_screen = TRUE + /// If the contract uplink's been assigned to a person yet + var/assigned = FALSE + /// If this is the first opening of the tablet + var/first_load = TRUE + +/datum/computer_file/program/contract_uplink/run_program(mob/living/user) + . = ..(user) + +/datum/computer_file/program/contract_uplink/ui_act(action, params) + . = ..() + if(.) + return + + var/mob/living/user = usr + var/obj/item/computer_hardware/hard_drive/small/syndicate/hard_drive = computer.all_components[MC_HDD] + + switch(action) + if("PRG_contract-accept") + var/contract_id = text2num(params["contract_id"]) + + // Set as the active contract + hard_drive.opfor_data.contractor_hub.assigned_contracts[contract_id].status = CONTRACT_STATUS_ACTIVE + hard_drive.opfor_data.contractor_hub.current_contract = hard_drive.opfor_data.contractor_hub.assigned_contracts[contract_id] + + program_icon_state = "single_contract" + return TRUE + if("PRG_login") + if(!user.mind.opposing_force) + var/datum/opposing_force/opposing_force = new(user.mind) + user.mind.opposing_force = opposing_force + SSopposing_force.new_opfor(opposing_force) + var/datum/opposing_force/opfor_data = user.mind.opposing_force + + if (!opfor_data) // Just in case + return FALSE + // Only play greet sound, and handle contractor hub when assigning for the first time. + if (!opfor_data.contractor_hub) + user.playsound_local(user, 'sound/effects/contractstartup.ogg', 100, FALSE) + opfor_data.contractor_hub = new + opfor_data.contractor_hub.create_hub_items() + + // Stops any topic exploits such as logging in multiple times on a single system. + if (!assigned) + opfor_data.contractor_hub.create_contracts(opfor_data.mind_reference) + + hard_drive.opfor_data = opfor_data + + program_icon_state = "contracts" + assigned = TRUE + return TRUE + if("PRG_call_extraction") + if (hard_drive.opfor_data.contractor_hub.current_contract.status != CONTRACT_STATUS_EXTRACTING) + if (hard_drive.opfor_data.contractor_hub.current_contract.handle_extraction(user)) + user.playsound_local(user, 'sound/effects/confirmdropoff.ogg', 100, TRUE) + hard_drive.opfor_data.contractor_hub.current_contract.status = CONTRACT_STATUS_EXTRACTING + + program_icon_state = "extracted" + else + user.playsound_local(user, 'sound/machines/uplinkerror.ogg', 50) + error = "Either both you or your target aren't at the dropoff location, or the pod hasn't got a valid place to land. Clear space, or make sure you're both inside." + else + user.playsound_local(user, 'sound/machines/uplinkerror.ogg', 50) + error = "Already extracting... Place the target into the pod. If the pod was destroyed, this contract is no longer possible." + + return TRUE + if("PRG_contract_abort") + var/contract_id = hard_drive.opfor_data.contractor_hub.current_contract.id + + hard_drive.opfor_data.contractor_hub.current_contract = null + hard_drive.opfor_data.contractor_hub.assigned_contracts[contract_id].status = CONTRACT_STATUS_ABORTED + + program_icon_state = "contracts" + + return TRUE + if("PRG_redeem_TC") + if (hard_drive.opfor_data.contractor_hub.contract_TC_to_redeem) + var/obj/item/stack/telecrystal/crystals = new /obj/item/stack/telecrystal(get_turf(user), + hard_drive.opfor_data.contractor_hub.contract_TC_to_redeem) + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.put_in_hands(crystals)) + to_chat(H, span_notice("Your payment materializes into your hands!")) + else + to_chat(user, span_notice("Your payment materializes onto the floor.")) + + hard_drive.opfor_data.contractor_hub.contract_paid_out += hard_drive.opfor_data.contractor_hub.contract_TC_to_redeem + hard_drive.opfor_data.contractor_hub.contract_TC_to_redeem = 0 + return TRUE + else + user.playsound_local(user, 'sound/machines/uplinkerror.ogg', 50) + return TRUE + if ("PRG_clear_error") + error = "" + return TRUE + if("PRG_set_first_load_finished") + first_load = FALSE + return TRUE + if("PRG_toggle_info") + info_screen = !info_screen + return TRUE + if ("buy_hub") + if (hard_drive.opfor_data.mind_reference.current == user) + var/item = params["item"] + + for (var/datum/contractor_item/hub_item in hard_drive.opfor_data.contractor_hub.hub_items) + if (hub_item.name == item) + hub_item.handle_purchase(hard_drive.opfor_data.contractor_hub, user) + else + error = "Invalid user... You weren't recognised as the user of this system." + +/datum/computer_file/program/contract_uplink/ui_data(mob/user) + var/list/data = list() + var/obj/item/computer_hardware/hard_drive/small/syndicate/hard_drive = computer.all_components[MC_HDD] + var/screen_to_be = null + + data["first_load"] = first_load + + if (hard_drive && hard_drive.opfor_data != null) + var/datum/opposing_force/opfor_data = hard_drive.opfor_data + data += get_header_data() + + if (opfor_data.contractor_hub.current_contract) + data["ongoing_contract"] = TRUE + screen_to_be = "single_contract" + if (opfor_data.contractor_hub.current_contract.status == CONTRACT_STATUS_EXTRACTING) + data["extraction_enroute"] = TRUE + screen_to_be = "extracted" + else + data["extraction_enroute"] = FALSE + else + data["ongoing_contract"] = FALSE + data["extraction_enroute"] = FALSE + + data["logged_in"] = TRUE + data["station_name"] = GLOB.station_name + data["redeemable_tc"] = opfor_data.contractor_hub.contract_TC_to_redeem + data["earned_tc"] = opfor_data.contractor_hub.contract_paid_out + data["contracts_completed"] = opfor_data.contractor_hub.contracts_completed + data["contract_rep"] = opfor_data.contractor_hub.contract_rep + + data["info_screen"] = info_screen + + data["error"] = error + + for (var/datum/contractor_item/hub_item in opfor_data.contractor_hub.hub_items) + data["contractor_hub_items"] += list(list( + "name" = hub_item.name, + "desc" = hub_item.desc, + "cost" = hub_item.cost, + "limited" = hub_item.limited, + "item_icon" = hub_item.item_icon + )) + + for (var/datum/syndicate_contract/contract in opfor_data.contractor_hub.assigned_contracts) + if(!contract.contract) + stack_trace("Syndiate contract with null contract objective found in [opfor_data.mind_reference]'s contractor hub!") + contract.status = CONTRACT_STATUS_ABORTED + continue + + data["contracts"] += list(list( + "target" = contract.contract.target, + "target_rank" = contract.target_rank, + "payout" = contract.contract.payout, + "payout_bonus" = contract.contract.payout_bonus, + "dropoff" = contract.contract.dropoff, + "id" = contract.id, + "status" = contract.status, + "message" = contract.wanted_message + )) + + var/direction + if (opfor_data.contractor_hub.current_contract) + var/turf/curr = get_turf(user) + var/turf/dropoff_turf + data["current_location"] = "[get_area_name(curr, TRUE)]" + + for (var/turf/content in opfor_data.contractor_hub.current_contract.contract.dropoff.contents) + if (isturf(content)) + dropoff_turf = content + break + + if(curr.z == dropoff_turf.z) //Direction calculations for same z-level only + direction = uppertext(dir2text(get_dir(curr, dropoff_turf))) //Direction text (East, etc). Not as precise, but still helpful. + if(get_area(user) == opfor_data.contractor_hub.current_contract.contract.dropoff) + direction = "LOCATION CONFIRMED" + else + direction = "???" + + data["dropoff_direction"] = direction + + else + data["logged_in"] = FALSE + + program_icon_state = screen_to_be + update_computer_icon() + return data diff --git a/modular_skyrat/modules/contractor/code/datums/mind_datum.dm b/modular_skyrat/modules/contractor/code/datums/mind_datum.dm new file mode 100644 index 00000000000..91cb9a5ac69 --- /dev/null +++ b/modular_skyrat/modules/contractor/code/datums/mind_datum.dm @@ -0,0 +1,4 @@ +/datum/mind/proc/make_contractor_support() + if(has_antag_datum(/datum/antagonist/traitor/contractor_support)) + return + add_antag_datum(/datum/antagonist/traitor/contractor_support) diff --git a/modular_skyrat/modules/contractor/code/items/items.dm b/modular_skyrat/modules/contractor/code/items/items.dm new file mode 100644 index 00000000000..b90532ba56c --- /dev/null +++ b/modular_skyrat/modules/contractor/code/items/items.dm @@ -0,0 +1,138 @@ +#define SMALL_ITEM_AMOUNT 3 + +/obj/item/storage/box/syndicate/contract_kit + name = "Contract Kit" + special_desc = "Supplied to Syndicate contractors." + special_desc_requirement = EXAMINE_CHECK_CONTRACTOR + icon_state = "syndiebox" + illustration = "writing_syndie" + +/obj/item/storage/box/syndicate/contractor_loadout + name = "Standard Loadout" + special_desc_requirement = EXAMINE_CHECK_CONTRACTOR + special_desc = "Supplied to Syndicate contractors, providing their specialised space suit and chameleon uniform." + icon_state = "syndiebox" + illustration = "writing_syndie" + +/obj/item/paper/contractor_guide + name = "Contractor Guide" + +/obj/item/paper/contractor_guide/Initialize() + info = {"

Welcome agent, congratulations on your new position as contractor. On top of your already assigned objectives, + this kit will provide you contracts to take on for TC payments.

+ +

Provided within, we give your specialist contractor space suit. It's even more compact, being able to fit into a pocket, and faster than the + Syndicate space suit available to you on the uplink. We also provide your chameleon jumpsuit and mask, both of which can be changed + to any form you need for the moment. The cigarettes are a special blend - it'll heal your injuries slowly overtime.

+ +

Your standard issue contractor baton hits harder than the ones you might be used to, and likely be your go to weapon for kidnapping your + targets. The three additional items have been randomly selected from what we had available. We hope they're useful to you for your mission.

+ +

The contractor hub, available at the top right of the uplink, will provide you unique items and abilities. These are bought using Contractor Rep, + with two Rep being provided each time you complete a contract.

+ +

Using the tablet

+
    +
  1. Open the Syndicate Contract Uplink program.
  2. +
  3. Here, you can accept a contract, and redeem your TC payments from completed contracts.
  4. +
  5. The payment number shown in brackets is the bonus you'll receive when bringing your target alive. You receive the + other number regardless of if they were alive or dead.
  6. +
  7. Contracts are completed by bringing the target to designated dropoff, calling for extraction, and putting them + inside the pod.
  8. +
+ +

Be careful when accepting a contract. While you'll be able to see the location of the dropoff point, cancelling will make it + unavailable to take on again.

+

The tablet can also be recharged at any cell charger.

+

Extracting

+
    +
  1. Make sure both yourself and your target are at the dropoff.
  2. +
  3. Call the extraction, and stand back from the drop point.
  4. +
  5. If it fails, make sure your target is inside, and there's a free space for the pod to land.
  6. +
  7. Grab your target, and drag them into the pod.
  8. +
+

Ransoms

+

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.

+ +

Good luck agent. You can burn this document with the supplied lighter.

"} + + return ..() + +/obj/item/storage/box/syndicate/contractor_loadout/PopulateContents() + new /obj/item/mod/control/pre_equipped/contractor(src) + new /obj/item/clothing/under/chameleon(src) + new /obj/item/clothing/mask/chameleon(src) + new /obj/item/storage/fancy/cigarettes/cigpack_syndicate(src) + new /obj/item/card/id/advanced/chameleon(src) + new /obj/item/lighter(src) + new /obj/item/uplink/opfor(src) + new /obj/item/jammer(src) + +/obj/item/storage/box/syndicate/contract_kit/PopulateContents() + new /obj/item/modular_computer/tablet/syndicate_contract_uplink/preset/uplink(src) + new /obj/item/storage/box/syndicate/contractor_loadout(src) + new /obj/item/melee/baton/telescopic/contractor_baton(src) + + // All about 4 TC or less - some nukeops only items, but fit nicely to the theme. + var/list/item_list = list( + /obj/item/storage/backpack/duffelbag/syndie/x4, + /obj/item/storage/box/syndie_kit/throwing_weapons, + /obj/item/gun/syringe/syndicate, + /obj/item/pen/edagger, + /obj/item/pen/sleepy, + /obj/item/flashlight/emp, + /obj/item/reagent_containers/syringe/mulligan, + /obj/item/clothing/shoes/chameleon/noslip, + /obj/item/storage/medkit/tactical, + /obj/item/encryptionkey/syndicate, + /obj/item/clothing/glasses/thermal/syndi, + /obj/item/slimepotion/slime/sentience/nuclear, + /obj/item/storage/box/syndie_kit/imp_radio, + /obj/item/storage/box/syndie_kit/imp_uplink, + /obj/item/clothing/gloves/krav_maga/combatglovesplus, + /obj/item/gun/ballistic/automatic/c20r/toy/unrestricted/riot, + /obj/item/reagent_containers/hypospray/medipen/stimulants, + /obj/item/storage/box/syndie_kit/imp_freedom, + /obj/item/crowbar/power/syndicate, + /obj/item/clothing/gloves/tackler/combat/insulated, + /obj/item/storage/box/syndie_kit/emp, + /obj/item/radio/headset/chameleon/advanced, + /obj/item/shield/energy, + /obj/item/healthanalyzer/rad_laser + ) + + for(var/iteration in 1 to SMALL_ITEM_AMOUNT) + var/obj/item/small_item = pick_n_take(item_list) + new small_item(src) + + // Paper guide + new /obj/item/paper/contractor_guide(src) + +/obj/item/pinpointer/crew/contractor + name = "contractor pinpointer" + desc = "A handheld tracking device that locks onto certain signals. Ignores suit sensors, but is much less accurate." + icon_state = "pinpointer_syndicate" + worn_icon_state = "pinpointer_black" + minimum_range = 15 + has_owner = TRUE + ignore_suit_sensor_level = TRUE + +/obj/item/storage/box/contractor/fulton_extraction + name = "Fulton Extraction Kit" + icon_state = "syndiebox" + illustration = "writing_syndie" + +/obj/item/storage/box/contractor/fulton_extraction/PopulateContents() + new /obj/item/extraction_pack/contractor(src) + new /obj/item/fulton_core(src) + +/obj/item/extraction_pack/contractor + name = "black fulton extraction pack" + icon = 'modular_skyrat/modules/contractor/icons/fulton.dmi' + can_use_indoors = TRUE + special_desc_requirement = EXAMINE_CHECK_CONTRACTOR + special_desc = "A modified fulton pack that can be used indoors thanks to Bluespace technology. Favored by Syndicate Contractors." + +#undef SMALL_ITEM_AMOUNT diff --git a/modular_skyrat/modules/contractor/code/items/modsuit.dm b/modular_skyrat/modules/contractor/code/items/modsuit.dm new file mode 100644 index 00000000000..f6f27833759 --- /dev/null +++ b/modular_skyrat/modules/contractor/code/items/modsuit.dm @@ -0,0 +1,249 @@ + +/obj/item/mod/control/pre_equipped/contractor + worn_icon = 'modular_skyrat/modules/contractor/icons/worn_modsuit.dmi' + icon = 'modular_skyrat/modules/contractor/icons/modsuit.dmi' + theme = /datum/mod_theme/contractor + applied_cell = /obj/item/stock_parts/cell/hyper + initial_modules = list( + /obj/item/mod/module/storage/syndicate, + /obj/item/mod/module/tether, + /obj/item/mod/module/flashlight, + /obj/item/mod/module/dna_lock, + /obj/item/mod/module/holster, + ) + +// For the record: modularity makes me want to die +/obj/item/mod/control/pre_equipped/contractor/Initialize(mapload, new_theme, new_skin, new_core) + . = ..() + mod_parts = list() + helmet.mod = null + chestplate.mod = null + gauntlets.mod = null + boots.mod = null + qdel(helmet) + qdel(chestplate) + qdel(gauntlets) + qdel(boots) + helmet = new /obj/item/clothing/head/mod/contractor(src) + helmet.mod = src + mod_parts += helmet + chestplate = new /obj/item/clothing/suit/mod/contractor(src) + chestplate.mod = src + chestplate.allowed = theme.allowed_suit_storage.Copy() + mod_parts += chestplate + gauntlets = new /obj/item/clothing/gloves/mod/contractor(src) + gauntlets.mod = src + mod_parts += gauntlets + boots = new /obj/item/clothing/shoes/mod/contractor(src) + boots.mod = src + mod_parts += boots + var/list/all_parts = mod_parts.Copy() + src + for(var/obj/item/piece as anything in all_parts) + piece.name = "[theme.name] [piece.name]" + piece.desc = "[piece.desc] [theme.desc]" + piece.armor = getArmor(arglist(theme.armor)) + piece.resistance_flags = theme.resistance_flags + piece.flags_1 |= theme.atom_flags //flags like initialization or admin spawning are here, so we cant set, have to add + piece.heat_protection = NONE + piece.cold_protection = NONE + piece.max_heat_protection_temperature = theme.max_heat_protection_temperature + piece.min_cold_protection_temperature = theme.min_cold_protection_temperature + piece.permeability_coefficient = theme.permeability_coefficient + piece.siemens_coefficient = theme.siemens_coefficient + piece.icon_state = "[skin]-[initial(piece.icon_state)]" + +/datum/mod_theme/contractor + name = "contractor" + desc = "A top-tier MODSuit developed with cooperation of Cybersun Industries and the Gorlex Marauders, a favorite of Syndicate Contractors." + extended_desc = "A rare depart from the Syndicate's usual color scheme, the Contractor MODsuit is produced and manufactured \ + for specialty contractors. The build is a streamlined layering consisting of shaped Plastitanium, \ + and composite ceramic, while the under suit is lined with a lightweight Kevlar and durathread hybrid weave \ + to provide ample protection to the user where the plating doesn't, with an illegal onboard electric powered \ + ablative shield module to provide resistance against conventional energy firearms. \ + In addition, it has an in-built chameleon system, allowing you to disguise the suit while undeployed. \ + A small tag hangs off of it reading; 'Property of the Gorlex Marauders, with assistance from Cybersun Industries. \ + All rights reserved, tampering with suit will void warranty." + default_skin = "contractor" + armor = list(MELEE = 40, BULLET = 50, LASER = 30, ENERGY = 40, BOMB = 30, BIO = 30, FIRE = 80, ACID = 85) + atom_flags = PREVENT_CONTENTS_EXPLOSION_1 + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + siemens_coefficient = 0 + slowdown_inactive = 1 + slowdown_active = 0.5 + ui_theme = "syndicate" + inbuilt_modules = list(/obj/item/mod/module/armor_booster/contractor, /obj/item/mod/module/chameleon) + allowed_suit_storage = list( + /obj/item/flashlight, + /obj/item/tank/internals, + /obj/item/ammo_box, + /obj/item/ammo_casing, + /obj/item/restraints/handcuffs, + /obj/item/assembly/flash, + /obj/item/melee/baton, + /obj/item/melee/energy/sword, + /obj/item/shield/energy, + ) + skins = list( + "contractor" = list( + HELMET_LAYER = NECK_LAYER, + HELMET_FLAGS = list( + UNSEALED_CLOTHING = SNUG_FIT, + SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, + UNSEALED_INVISIBILITY = HIDEFACIALHAIR, + SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT, + SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, + ), + CHESTPLATE_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + GAUNTLETS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + BOOTS_FLAGS = list( + UNSEALED_CLOTHING = THICKMATERIAL, + SEALED_CLOTHING = STOPSPRESSUREDAMAGE, + ), + ), + ) + + +/obj/item/mod/module/chameleon + name = "MOD chameleon module" + desc = "An illegal module that lets you disguise your MODsuit as any other kind with the help of chameleon technology. However, due to technological challenges, the module only functions when the MODsuit is undeployed." + icon_state = "chameleon" + icon = 'modular_skyrat/modules/contractor/icons/modsuit_modules.dmi' + module_type = MODULE_USABLE + active_power_cost = DEFAULT_CHARGE_DRAIN * 0.25 + removable = FALSE + incompatible_modules = list(/obj/item/mod/module/chameleon) + cooldown_time = 0.5 SECONDS + /// All of these below are the stored MODsuit original stats + var/chameleon_type = /obj/item/mod/control + var/chameleon_name + var/chameleon_desc + var/chameleon_icon_state + var/chameleon_icon + var/chameleon_worn_icon + var/chameleon_left + var/chameleon_right + /// MODsuit controllers that aren't allowed + var/list/chameleon_blacklist = list() + /// List of MODsuits that can be used + var/list/chameleon_list = list() + /// If the module's in use or not + var/on = FALSE + +/obj/item/mod/module/chameleon/on_select() + if(!length(chameleon_list)) + init_chameleon_list() + for(var/obj/item/part as anything in mod.mod_parts) + if(!(part.loc == mod.wearer)) + continue + balloon_alert(mod.wearer, "parts cannot be deployed to use this!") + playsound(mod, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE) + return FALSE + on_use() + SEND_SIGNAL(mod, COMSIG_MOD_MODULE_SELECTED, src) + +/obj/item/mod/module/chameleon/on_use() + var/obj/item/picked_item + var/picked_name = tgui_input_list(mod.wearer, "Select [chameleon_name] to change into", "Chameleon Settings", sort_list(chameleon_list, /proc/cmp_typepaths_asc)) + if(isnull(picked_name) || isnull(chameleon_list[picked_name])) + return + picked_item = chameleon_list[picked_name] + update_look(mod.wearer, picked_item) + +/obj/item/mod/module/chameleon/on_install() + chameleon_name = initial(mod.name) + chameleon_desc = initial(mod.name) + chameleon_icon_state = "[mod.skin]-[initial(mod.icon_state)]" + chameleon_icon = mod.icon + chameleon_worn_icon = initial(mod.worn_icon) + chameleon_left = initial(mod.lefthand_file) + chameleon_right = initial(mod.righthand_file) + RegisterSignal(mod, COMSIG_MOD_ACTIVATE, .proc/reset_chameleon) + +/obj/item/mod/module/chameleon/on_uninstall() + UnregisterSignal(mod, COMSIG_MOD_ACTIVATE) + reset_chameleon() + +/obj/item/mod/module/chameleon/proc/reset_chameleon() + if(on) + balloon_alert(mod.wearer, "chameleon module disabled!") + on = FALSE + mod.name = chameleon_name + mod.desc = chameleon_desc + mod.icon_state = chameleon_icon_state + mod.icon = chameleon_icon + mod.worn_icon = chameleon_worn_icon + mod.lefthand_file = chameleon_left + mod.righthand_file = chameleon_right + +/obj/item/mod/module/chameleon/proc/update_look(mob/living/user, obj/item/picked_item) + if(!isliving(user)) + return + if(user.stat != CONSCIOUS) + return + + update_item(picked_item) + var/obj/item/thing = mod + thing.update_slot_icon() + on = TRUE + +/obj/item/mod/module/chameleon/proc/update_item(obj/item/picked_item) + var/obj/item/mod/control/modsuit = new picked_item() + mod.name = modsuit.name + mod.desc = modsuit.desc + mod.icon_state = modsuit.icon_state + mod.worn_icon = modsuit.worn_icon + mod.lefthand_file = modsuit.lefthand_file + mod.righthand_file = modsuit.righthand_file + if(modsuit.greyscale_colors) + if(modsuit.greyscale_config_worn) + mod.worn_icon = SSgreyscale.GetColoredIconByType(modsuit.greyscale_config_worn, modsuit.greyscale_colors) + if(modsuit.greyscale_config_inhand_left) + mod.lefthand_file = SSgreyscale.GetColoredIconByType(modsuit.greyscale_config_inhand_left, modsuit.greyscale_colors) + if(modsuit.greyscale_config_inhand_right) + mod.righthand_file = SSgreyscale.GetColoredIconByType(modsuit.greyscale_config_inhand_right, modsuit.greyscale_colors) + mod.worn_icon_state = modsuit.worn_icon_state + mod.inhand_icon_state = modsuit.inhand_icon_state + if(modsuit.greyscale_config && modsuit.greyscale_colors) + mod.icon = SSgreyscale.GetColoredIconByType(modsuit.greyscale_config, modsuit.greyscale_colors) + else + mod.icon = modsuit.icon + update_slot_icon() + qdel(modsuit) + +/obj/item/mod/module/chameleon/proc/init_chameleon_list() + for(var/obj/item/modsuit as anything in typesof(chameleon_type)) + if(chameleon_blacklist[modsuit] || (initial(modsuit.item_flags) & ABSTRACT) || !initial(modsuit.icon_state)) + continue + var/chameleon_item_name = "[modsuit]" + chameleon_list[chameleon_item_name] = modsuit + +/obj/item/mod/module/chameleon/Initialize(mapload) + . = ..() + init_chameleon_list() + +/obj/item/mod/module/armor_booster/contractor // Much flatter distribution because contractor suit gets a shitton of armor already + armor_values = list(MELEE = 20, BULLET = 20, LASER = 20, ENERGY = 20) + added_slowdown = 0.5 //Bulky as shit + +// I absolutely fuckin hate having to do this +/obj/item/clothing/head/mod/contractor + worn_icon = 'modular_skyrat/modules/contractor/icons/worn_modsuit.dmi' + icon = 'modular_skyrat/modules/contractor/icons/modsuit.dmi' + +/obj/item/clothing/suit/mod/contractor + worn_icon = 'modular_skyrat/modules/contractor/icons/worn_modsuit.dmi' + icon = 'modular_skyrat/modules/contractor/icons/modsuit.dmi' + +/obj/item/clothing/gloves/mod/contractor + worn_icon = 'modular_skyrat/modules/contractor/icons/worn_modsuit.dmi' + icon = 'modular_skyrat/modules/contractor/icons/modsuit.dmi' + +/obj/item/clothing/shoes/mod/contractor + worn_icon = 'modular_skyrat/modules/contractor/icons/worn_modsuit.dmi' + icon = 'modular_skyrat/modules/contractor/icons/modsuit.dmi' diff --git a/modular_skyrat/modules/contractor/code/items/tablet.dm b/modular_skyrat/modules/contractor/code/items/tablet.dm new file mode 100644 index 00000000000..644b72ff26a --- /dev/null +++ b/modular_skyrat/modules/contractor/code/items/tablet.dm @@ -0,0 +1,17 @@ +/obj/item/modular_computer/tablet/syndicate_contract_uplink/preset/uplink/Initialize(mapload) + . = ..() + var/obj/item/computer_hardware/hard_drive/small/syndicate/hard_drive = new + var/datum/computer_file/program/contract_uplink/uplink = new + + active_program = uplink + uplink.program_state = PROGRAM_STATE_ACTIVE + uplink.computer = src + + hard_drive.store_file(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) diff --git a/modular_skyrat/modules/contractor/icons/fulton.dmi b/modular_skyrat/modules/contractor/icons/fulton.dmi new file mode 100644 index 0000000000000000000000000000000000000000..424c0e82e4f5b07619c26269a08437d38052fa83 GIT binary patch literal 1352 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[span_userdanger("Your target is no longer within reach. Objective removed!")]") mind.announce_objectives() + else if(istype(objective.target) && objective.target == mob_occupant.mind) + if(istype(objective, /datum/objective/contract)) + var/datum/opposing_force/affected_traitor = objective.owner.opposing_force + var/datum/contractor_hub/affected_contractor_hub = affected_traitor.contractor_hub + for(var/datum/syndicate_contract/affected_contract as anything in affected_contractor_hub.assigned_contracts) + if(affected_contract.contract == objective) + affected_contract.generate(affected_contractor_hub.assigned_targets) + affected_contractor_hub.assigned_targets.Add(affected_contract.contract.target) + to_chat(objective.owner.current, "
[span_userdanger("Contract target out of reach. Contract rerolled.")]") + break else if(istype(objective.target) && objective.target == mob_occupant.mind) var/old_target = objective.target objective.target = null diff --git a/modular_skyrat/modules/examinemore/code/examine_more.dm b/modular_skyrat/modules/examinemore/code/examine_more.dm index 00df29dbda8..b723436eeed 100644 --- a/modular_skyrat/modules/examinemore/code/examine_more.dm +++ b/modular_skyrat/modules/examinemore/code/examine_more.dm @@ -94,6 +94,24 @@ would only be recognisable with someone that had the syndicate trait. composed_message = "You note the following because of your loyalty to [faction_i]:
" composed_message += special_desc . += composed_message + //If they are a syndicate contractor or a syndicate + if(EXAMINE_CHECK_CONTRACTOR) + var/mob/living/carbon/human/human_user = user + if(!user?.mind.opposing_force) + return + for(var/datum/opposing_force_equipment/loadout/contractor/contractor_kit in user.mind.opposing_force.selected_equipment) + composed_message = "You note the following because of your [span_red("Contractor Status")]:
" + composed_message += special_desc + . += composed_message + return + if(HAS_TRAIT(human_user, TRAIT_DETECTIVE)) //Useful detective! + composed_message = "You note the following because of your brilliant Detective skills:
" + composed_message += special_desc + . += composed_message + else if((human_user.mind.special_role == ROLE_TRAITOR) || (ROLE_SYNDICATE in human_user.faction)) + composed_message = "You note the following because of your [span_red("[special_desc_affiliation ? special_desc_affiliation : "Syndicate Affiliation"]")]:
" + composed_message += special_desc + . += composed_message ////////// //Examples: diff --git a/modular_skyrat/modules/opposing_force/code/equipment/loadouts.dm b/modular_skyrat/modules/opposing_force/code/equipment/loadouts.dm index 31d6e043cac..a46786ff6d8 100644 --- a/modular_skyrat/modules/opposing_force/code/equipment/loadouts.dm +++ b/modular_skyrat/modules/opposing_force/code/equipment/loadouts.dm @@ -43,3 +43,9 @@ name = "Snake Eater bundle" item_type = /obj/item/storage/box/syndie_kit/loadout/ocelotfoxtrot description = "A kit themed around one certain gun spinning cat. Includes his famous colt special, and personalised ammo." + +/datum/opposing_force_equipment/loadout/contractor + name = "Contractor bundle" + item_type = /obj/item/storage/box/syndicate/contractor_loadout + description = "A box containing everything you need to take contracts from the Syndicate. Kidnap people and drop them off at specified locations for rewards in the form of Telecrystals (Usable in the provided uplink) and Contractor Points." + admin_note = "WARNING: This bundle is a pretty large change-up of how a person plays a round, giving them access to a swathe of new gear, in addition to a contractor tablet. This contractor tablet lets them take on objectives to non-lethally kidnap people in exhange for telecrystals, usable in the provided uplink." diff --git a/modular_skyrat/modules/opposing_force/code/opposing_force_datum.dm b/modular_skyrat/modules/opposing_force/code/opposing_force_datum.dm index b49b94eb9cc..7cb5f33a7a0 100644 --- a/modular_skyrat/modules/opposing_force/code/opposing_force_datum.dm +++ b/modular_skyrat/modules/opposing_force/code/opposing_force_datum.dm @@ -73,6 +73,8 @@ var/admin_requested_changes = "" /// The ckey of the person that made this application var/ckey + /// Contractor hub datum, used if the user OPFORs for a contractor kit + var/datum/contractor_hub/contractor_hub /// Corresponding stat() click button var/obj/effect/statclick/opfor_specific/stat_button @@ -854,6 +856,9 @@ report += "[opfor_equipment.opposing_force_equipment.name]
" report += "
" + if(contractor_hub) + report += contractor_round_end() + return report.Join("\n") /datum/action/opfor diff --git a/tgstation.dme b/tgstation.dme index 6c14ef31184..5f1a640ab7e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4700,6 +4700,16 @@ #include "modular_skyrat\modules\connecting_computer\code\_computer.dm" #include "modular_skyrat\modules\connecting_computer\code\non_connecting_computers.dm" #include "modular_skyrat\modules\container_emotes\code\container_emotes.dm" +#include "modular_skyrat\modules\contractor\code\datums\contract.dm" +#include "modular_skyrat\modules\contractor\code\datums\contractor_datum.dm" +#include "modular_skyrat\modules\contractor\code\datums\contractor_hub.dm" +#include "modular_skyrat\modules\contractor\code\datums\contractor_items.dm" +#include "modular_skyrat\modules\contractor\code\datums\contractor_support.dm" +#include "modular_skyrat\modules\contractor\code\datums\contractor_tablet.dm" +#include "modular_skyrat\modules\contractor\code\datums\mind_datum.dm" +#include "modular_skyrat\modules\contractor\code\items\items.dm" +#include "modular_skyrat\modules\contractor\code\items\modsuit.dm" +#include "modular_skyrat\modules\contractor\code\items\tablet.dm" #include "modular_skyrat\modules\conveyor_sorter\code\conveyor_sorter.dm" #include "modular_skyrat\modules\cortical_borer\code\_cortical_borer_important.dm" #include "modular_skyrat\modules\cortical_borer\code\cortical_borer.dm" diff --git a/tgui/packages/tgui/interfaces/SyndContractor.js b/tgui/packages/tgui/interfaces/SyndContractor.js new file mode 100644 index 00000000000..b6809dddaee --- /dev/null +++ b/tgui/packages/tgui/interfaces/SyndContractor.js @@ -0,0 +1,428 @@ +import { Component, Fragment } from 'inferno'; +import { useBackend, useLocalState } from '../backend'; +import { Box, Button, Flex, Grid, Icon, LabeledList, Modal, NoticeBox, Section, Table, Tabs } from '../components'; +import { NtosWindow } from '../layouts'; + +const CONTRACT_STATUS_INACTIVE = 1; +const CONTRACT_STATUS_ACTIVE = 2; +const CONTRACT_STATUS_BOUNTY_CONSOLE_ACTIVE = 3; +const CONTRACT_STATUS_EXTRACTING = 4; +const CONTRACT_STATUS_COMPLETE = 5; +const CONTRACT_STATUS_ABORTED = 6; + +export class FakeTerminal extends Component { + constructor(props) { + super(props); + this.timer = null; + this.state = { + currentIndex: 0, + currentDisplay: [], + }; + } + + tick() { + const { props, state } = this; + if (state.currentIndex <= props.allMessages.length) { + this.setState(prevState => { + return ({ + currentIndex: prevState.currentIndex + 1, + }); + }); + const { currentDisplay } = state; + currentDisplay.push(props.allMessages[state.currentIndex]); + } else { + clearTimeout(this.timer); + setTimeout(props.onFinished, props.finishedTimeout); + } + } + + componentDidMount() { + const { + linesPerSecond = 2.5, + } = this.props; + this.timer = setInterval(() => this.tick(), 1000 / linesPerSecond); + } + + componentWillUnmount() { + clearTimeout(this.timer); + } + + render() { + return ( + + {this.state.currentDisplay.map(value => ( + + {value} +
+
+ ))} +
+ ); + } +} + +export const SyndContractor = (props, context) => { + return ( + + + + + + ); +}; + +export const SyndContractorContent = (props, context) => { + const { data, act } = useBackend(context); + + const terminalMessages = [ + "Recording biometric data...", + "Analyzing embedded syndicate info...", + "STATUS CONFIRMED", + "Contacting syndicate database...", + "Awaiting response...", + "Awaiting response...", + "Awaiting response...", + "Awaiting response...", + "Awaiting response...", + "Awaiting response...", + "Response received, ack 4851234...", + "CONFIRM ACC " + (Math.round(Math.random() * 20000)), + "Setting up private accounts...", + "CONTRACTOR ACCOUNT CREATED", + "Searching for available contracts...", + "Searching for available contracts...", + "Searching for available contracts...", + "Searching for available contracts...", + "CONTRACTS FOUND", + "WELCOME, AGENT", + ]; + + const infoEntries = [ + "SyndTract v2.0", + "", + "We've identified potentional high-value targets that are", + "currently assigned to your mission area. They are believed", + "to hold valuable information which could be of immediate", + "importance to our organisation.", + "", + "Listed below are all of the contracts available to you. You", + "are to bring the specified target to the designated", + "drop-off, and contact us via this uplink. We will send", + "a specialised extraction unit to put the body into.", + "", + "We want targets alive - but we will sometimes pay slight", + "amounts if they're not, you just won't recieve the shown", + "bonus. You can redeem your payment through this uplink in", + "the form of raw telecrystals, which can be put into your", + "regular Syndicate uplink to purchase whatever you may need.", + "We provide you with these crystals the moment you send the", + "target up to us, which can be collected at anytime through", + "this system.", + "", + "Targets extracted will be ransomed back to the station once", + "their use to us is fulfilled, with us providing you a small", + "percentage cut. You may want to be mindful of them", + "identifying you when they come back. We provide you with", + "a standard contractor loadout, which will help cover your", + "identity.", + ]; + + const errorPane = !!data.error && ( + + + + + + + + {data.error} + +