mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-31 09:08:30 +01:00
[MIRROR] A red spy has entered the base: Adds Spies, a roundstart antagonist inspired by Goonstation's Spy-Thief (#26711)
* A red spy has entered the base: Adds Spies, a roundstart antagonist inspired by Goonstation's Spy-Thief * Update code/__DEFINES/role_preferences.dm * Update code/__DEFINES/role_preferences.dm * closet fix * I am stupid * fix effects * there we go * is this the end? * ugh * please stop --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Pinta <68373373+softcerv@users.noreply.github.com> Co-authored-by: jjpark-kb <55967837+jjpark-kb@users.noreply.github.com> Co-authored-by: jjpark-kb <mccorvey.norman@gmail.com>
This commit is contained in:
co-authored by
MrMelbert
Pinta
jjpark-kb
jjpark-kb
parent
681b9561fd
commit
4fb4a50167
@@ -146,6 +146,9 @@
|
||||
/// JSON string file for all of our heretic influence flavors
|
||||
#define HERETIC_INFLUENCE_FILE "antagonist_flavor/heretic_influences.json"
|
||||
|
||||
/// JSON file containing spy objectives
|
||||
#define SPY_OBJECTIVE_FILE "antagonist_flavor/spy_objective.json"
|
||||
|
||||
///employers that are from the syndicate
|
||||
GLOBAL_LIST_INIT(syndicate_employers, list(
|
||||
"Animal Rights Consortium",
|
||||
@@ -265,6 +268,8 @@ GLOBAL_LIST_INIT(human_invader_antagonists, list(
|
||||
#define OBJECTIVE_ITEM_TYPE_NORMAL "normal"
|
||||
/// Only appears in traitor objectives
|
||||
#define OBJECTIVE_ITEM_TYPE_TRAITOR "traitor"
|
||||
/// Only appears for spy bounties
|
||||
#define OBJECTIVE_ITEM_TYPE_SPY "spy"
|
||||
|
||||
// Progression traitor defines
|
||||
|
||||
@@ -378,3 +383,11 @@ GLOBAL_LIST_INIT(human_invader_antagonists, list(
|
||||
#define BATON_MODES 4
|
||||
|
||||
#define FREEDOM_IMPLANT_CHARGES 4
|
||||
|
||||
// Spy bounty difficulties
|
||||
/// Can easily be accomplished by any job without any specialized tools, people won't really miss these things
|
||||
#define SPY_DIFFICULTY_EASY "Easy"
|
||||
/// Requires some specialized tools, knowledge, or access to accomplish, may require getting into conflict with the crew
|
||||
#define SPY_DIFFICULTY_MEDIUM "Medium"
|
||||
/// Very difficult to accomplish, almost guaranteed to require crew conflict
|
||||
#define SPY_DIFFICULTY_HARD "Hard"
|
||||
|
||||
@@ -112,3 +112,6 @@
|
||||
#define COMSIG_MOVABLE_EDIT_UNIQUE_IMMERSE_OVERLAY "movable_edit_unique_submerge_overlay"
|
||||
/// From base of area/Exited(): (area/left, direction)
|
||||
#define COMSIG_MOVABLE_EXITED_AREA "movable_exited_area"
|
||||
|
||||
/// Sent to movables when they are being stolen by a spy: (mob/living/spy, datum/spy_bounty/bounty)
|
||||
#define COMSIG_MOVABLE_SPY_STEALING "movable_spy_stealing"
|
||||
|
||||
@@ -314,6 +314,7 @@ GLOBAL_LIST_INIT(book_types, typecacheof(list(
|
||||
#define is_captain_job(job_type) (istype(job_type, /datum/job/captain))
|
||||
#define is_chaplain_job(job_type) (istype(job_type, /datum/job/chaplain))
|
||||
#define is_clown_job(job_type) (istype(job_type, /datum/job/clown))
|
||||
#define is_mime_job(job_type) (istype(job_type, /datum/job/mime))
|
||||
#define is_detective_job(job_type) (istype(job_type, /datum/job/detective))
|
||||
#define is_scientist_job(job_type) (istype(job_type, /datum/job/scientist))
|
||||
#define is_security_officer_job(job_type) (istype(job_type, /datum/job/security_officer))
|
||||
|
||||
@@ -162,6 +162,7 @@
|
||||
#define LOG_CATEGORY_UPLINK_HERETIC "uplink-heretic"
|
||||
#define LOG_CATEGORY_UPLINK_MALF "uplink-malf"
|
||||
#define LOG_CATEGORY_UPLINK_SPELL "uplink-spell"
|
||||
#define LOG_CATEGORY_UPLINK_SPY "uplink-spy"
|
||||
|
||||
// PDA categories
|
||||
#define LOG_CATEGORY_PDA "pda"
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#define ROLE_OPERATIVE "Operative"
|
||||
#define ROLE_TRAITOR "Traitor"
|
||||
#define ROLE_WIZARD "Wizard"
|
||||
#define ROLE_SPY "Spy"
|
||||
// SKYRAT EDIT START
|
||||
#define ROLE_ASSAULT_OPERATIVE "Assault Operative"
|
||||
#define ROLE_OPFOR_CANDIDATE "OPFOR Candidate"
|
||||
@@ -145,6 +146,7 @@ GLOBAL_LIST_INIT(special_roles, list(
|
||||
ROLE_REV_HEAD = 14,
|
||||
ROLE_TRAITOR = 0,
|
||||
ROLE_WIZARD = 14,
|
||||
ROLE_SPY = 0,
|
||||
// SKYRAT EDIT ADDITION
|
||||
ROLE_ASSAULT_OPERATIVE = 14,
|
||||
// SKYRAT EDIT END
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
/// This item is purchasable to infiltrators (midround traitors)
|
||||
#define UPLINK_INFILTRATORS (1 << 3)
|
||||
|
||||
/// Can be randomly given to spies for their bounties
|
||||
#define UPLINK_SPY (1 << 4)
|
||||
|
||||
/// Progression gets turned into a user-friendly form. This is just an abstract equation that makes progression not too large.
|
||||
#define DISPLAY_PROGRESSION(time) round(time/60, 0.01)
|
||||
|
||||
@@ -19,3 +22,12 @@
|
||||
#define TRAITOR_DISCOUNT_BIG "big_discount"
|
||||
#define TRAITOR_DISCOUNT_AVERAGE "average_discount"
|
||||
#define TRAITOR_DISCOUNT_SMALL "small_discount"
|
||||
|
||||
/// Typepath used for uplink items which don't actually produce an item (essentially just a placeholder)
|
||||
/// Future todo: Make this not necessary / make uplink items support item-less items natively
|
||||
#define ABSTRACT_UPLINK_ITEM /obj/effect/gibspawner/generic
|
||||
|
||||
/// Lower threshold for which an uplink items's TC cost is considered "low" for spy bounties picking rewards
|
||||
#define SPY_LOWER_COST_THRESHOLD 5
|
||||
/// Upper threshold for which an uplink items's TC cost is considered "high" for spy bounties picking rewards
|
||||
#define SPY_UPPER_COST_THRESHOLD 12
|
||||
|
||||
@@ -21,3 +21,7 @@
|
||||
/// Logging for wizard powers learned
|
||||
/proc/log_spellbook(text, list/data)
|
||||
logger.Log(LOG_CATEGORY_UPLINK_SPELL, text, data)
|
||||
|
||||
/// Logs bounties completed by spies and their rewards
|
||||
/proc/log_spy(text, list/data)
|
||||
logger.Log(LOG_CATEGORY_UPLINK_SPY, text, data)
|
||||
|
||||
@@ -21,17 +21,20 @@ SUBSYSTEM_DEF(blackmarket)
|
||||
for(var/market in subtypesof(/datum/market))
|
||||
markets[market] += new market
|
||||
|
||||
for(var/item in subtypesof(/datum/market_item))
|
||||
var/datum/market_item/I = new item()
|
||||
if(!I.item)
|
||||
for(var/datum/market_item/item as anything in subtypesof(/datum/market_item))
|
||||
if(!initial(item.item))
|
||||
continue
|
||||
if(!prob(initial(item.availability_prob)))
|
||||
continue
|
||||
|
||||
for(var/M in I.markets)
|
||||
if(!markets[M])
|
||||
stack_trace("SSblackmarket: Item [I] available in market that does not exist.")
|
||||
var/datum/market_item/item_instance = new item()
|
||||
for(var/potential_market in item_instance.markets)
|
||||
if(!markets[potential_market])
|
||||
stack_trace("SSblackmarket: Item [item_instance] available in market that does not exist.")
|
||||
continue
|
||||
markets[M].add_item(item)
|
||||
qdel(I)
|
||||
// If this fails the market item will just be GC'd
|
||||
markets[potential_market].add_item(item_instance)
|
||||
|
||||
return SS_INIT_SUCCESS
|
||||
|
||||
/datum/controller/subsystem/blackmarket/fire(resumed)
|
||||
|
||||
@@ -698,3 +698,45 @@ GLOBAL_VAR_INIT(revolutionary_win, FALSE)
|
||||
create_separatist_nation(department_type, announcement = FALSE, dangerous = FALSE, message_admins = FALSE)
|
||||
|
||||
GLOB.round_default_lawset = /datum/ai_laws/united_nations
|
||||
|
||||
/datum/dynamic_ruleset/roundstart/spies
|
||||
name = "Spies"
|
||||
antag_flag = ROLE_SPY
|
||||
antag_datum = /datum/antagonist/spy
|
||||
minimum_required_age = 0
|
||||
protected_roles = list(
|
||||
JOB_CAPTAIN,
|
||||
JOB_DETECTIVE,
|
||||
JOB_HEAD_OF_PERSONNEL, // AA = bad
|
||||
JOB_HEAD_OF_SECURITY,
|
||||
JOB_PRISONER,
|
||||
JOB_SECURITY_OFFICER,
|
||||
JOB_WARDEN,
|
||||
)
|
||||
restricted_roles = list(
|
||||
JOB_AI,
|
||||
JOB_CYBORG,
|
||||
)
|
||||
required_candidates = 3 // lives or dies by there being a few spies
|
||||
weight = 5
|
||||
cost = 8
|
||||
scaling_cost = 101 // see below
|
||||
minimum_players = 8
|
||||
antag_cap = list("denominator" = 8, "offset" = 1) // should have quite a few spies to work against each other
|
||||
requirements = list(8, 8, 8, 8, 8, 8, 8, 8, 8, 8)
|
||||
|
||||
/datum/dynamic_ruleset/roundstart/spies/pre_execute(population)
|
||||
for(var/i in 1 to get_antag_cap(population) * (scaled_times + 1))
|
||||
if(length(candidates) <= 0)
|
||||
break
|
||||
var/mob/picked_player = pick_n_take(candidates)
|
||||
assigned += picked_player.mind
|
||||
picked_player.mind.special_role = ROLE_SPY
|
||||
picked_player.mind.restricted_roles = restricted_roles
|
||||
GLOB.pre_setup_antags += picked_player.mind
|
||||
return TRUE
|
||||
|
||||
/datum/dynamic_ruleset/roundstart/spies/scale_up(population, max_scale)
|
||||
// Disabled (at least until dynamic can handle scaling this better)
|
||||
// Because spies have a very low demoninator, this can easily spawn like 30 of them
|
||||
return 0
|
||||
|
||||
+45
-42
@@ -106,6 +106,31 @@
|
||||
var/datum/antagonist/rev/revolutionary = has_antag_datum(/datum/antagonist/rev)
|
||||
revolutionary?.remove_revolutionary()
|
||||
|
||||
/**
|
||||
* Gets an item that can be used as an uplink somewhere on the mob's person.
|
||||
*
|
||||
* * desired_location: the location to look for the uplink in. An UPLINK_ define.
|
||||
* If the desired location is not found, defaults to another location.
|
||||
*
|
||||
* Returns the item found, or null if no item was found.
|
||||
*/
|
||||
/mob/living/carbon/proc/get_uplink_location(desired_location = UPLINK_PDA)
|
||||
var/list/all_contents = get_all_contents()
|
||||
var/obj/item/modular_computer/pda/my_pda = locate() in all_contents
|
||||
var/obj/item/radio/my_radio = locate() in all_contents
|
||||
var/obj/item/pen/my_pen = (locate() in my_pda) || (locate() in all_contents)
|
||||
|
||||
switch(desired_location)
|
||||
if(UPLINK_PDA)
|
||||
return my_pda || my_radio || my_pen
|
||||
|
||||
if(UPLINK_RADIO)
|
||||
return my_radio || my_pda || my_pen
|
||||
|
||||
if(UPLINK_PEN)
|
||||
return my_pen || my_pda || my_radio
|
||||
|
||||
return null
|
||||
|
||||
/**
|
||||
* ## give_uplink
|
||||
@@ -116,53 +141,26 @@
|
||||
* * antag_datum: the antag datum of the uplink owner, for storing it in antag memory. optional!
|
||||
*/
|
||||
/datum/mind/proc/give_uplink(silent = FALSE, datum/antagonist/antag_datum)
|
||||
if(!current)
|
||||
if(isnull(current))
|
||||
return
|
||||
var/mob/living/carbon/human/traitor_mob = current
|
||||
if (!istype(traitor_mob))
|
||||
return
|
||||
|
||||
var/list/all_contents = traitor_mob.get_all_contents()
|
||||
var/obj/item/modular_computer/pda/PDA = locate() in all_contents
|
||||
var/obj/item/radio/R = locate() in all_contents
|
||||
var/obj/item/pen/P
|
||||
|
||||
if (PDA) // Prioritize PDA pen, otherwise the pocket protector pens will be chosen, which causes numerous ahelps about missing uplink
|
||||
P = locate() in PDA
|
||||
if (!P) // If we couldn't find a pen in the PDA, or we didn't even have a PDA, do it the old way
|
||||
P = locate() in all_contents
|
||||
|
||||
var/obj/item/uplink_loc
|
||||
var/implant = FALSE
|
||||
|
||||
var/uplink_spawn_location = traitor_mob.client?.prefs?.read_preference(/datum/preference/choiced/uplink_location)
|
||||
var/cant_speak = (HAS_TRAIT(traitor_mob, TRAIT_MUTE) || traitor_mob.mind?.assigned_role.title == JOB_MIME)
|
||||
var/cant_speak = (HAS_TRAIT(traitor_mob, TRAIT_MUTE) || is_mime_job(assigned_role))
|
||||
if(uplink_spawn_location == UPLINK_RADIO && cant_speak)
|
||||
if(!silent)
|
||||
to_chat(traitor_mob, span_warning("You have been deemed ineligible for a radio uplink. Supplying standard uplink instead."))
|
||||
uplink_spawn_location = UPLINK_PDA
|
||||
switch (uplink_spawn_location)
|
||||
if(UPLINK_PDA)
|
||||
uplink_loc = PDA
|
||||
if(!uplink_loc)
|
||||
uplink_loc = R
|
||||
if(!uplink_loc)
|
||||
uplink_loc = P
|
||||
if(UPLINK_RADIO)
|
||||
uplink_loc = R
|
||||
if(!uplink_loc)
|
||||
uplink_loc = PDA
|
||||
if(!uplink_loc)
|
||||
uplink_loc = P
|
||||
if(UPLINK_PEN)
|
||||
uplink_loc = P
|
||||
if(UPLINK_IMPLANT)
|
||||
implant = TRUE
|
||||
|
||||
if(!uplink_loc) // We've looked everywhere, let's just implant you
|
||||
implant = TRUE
|
||||
if(uplink_spawn_location != UPLINK_IMPLANT)
|
||||
uplink_loc = traitor_mob.get_uplink_location(uplink_spawn_location)
|
||||
if(istype(uplink_loc, /obj/item/radio) && cant_speak)
|
||||
uplink_loc = null
|
||||
|
||||
if(implant)
|
||||
if(isnull(uplink_loc))
|
||||
var/obj/item/implant/uplink/starting/new_implant = new(traitor_mob)
|
||||
new_implant.implant(traitor_mob, null, silent = TRUE)
|
||||
if(!silent)
|
||||
@@ -179,22 +177,27 @@
|
||||
new_uplink.uplink_handler.owner = traitor_mob.mind
|
||||
new_uplink.uplink_handler.assigned_role = traitor_mob.mind.assigned_role.title
|
||||
new_uplink.uplink_handler.assigned_species = traitor_mob.dna.species.id
|
||||
if(uplink_loc == R)
|
||||
unlock_text = "Your Uplink is cunningly disguised as your [R.name]. Simply speak \"[new_uplink.unlock_code]\" into frequency [RADIO_TOKEN_UPLINK] to unlock its hidden features."
|
||||
add_memory(/datum/memory/key/traitor_uplink, uplink_loc = R.name, uplink_code = new_uplink.unlock_code)
|
||||
else if(uplink_loc == PDA)
|
||||
unlock_text = "Your Uplink is cunningly disguised as your [PDA.name]. Simply enter the code \"[new_uplink.unlock_code]\" into the ring tone selection to unlock its hidden features."
|
||||
|
||||
unlock_text = "Your Uplink is cunningly disguised as your [uplink_loc.name]. "
|
||||
if(istype(uplink_loc, /obj/item/modular_computer/pda))
|
||||
unlock_text += "Simply enter the code \"[new_uplink.unlock_code]\" into the ring tone selection to unlock its hidden features."
|
||||
add_memory(/datum/memory/key/traitor_uplink, uplink_loc = "PDA", uplink_code = new_uplink.unlock_code)
|
||||
else if(uplink_loc == P)
|
||||
|
||||
else if(istype(uplink_loc, /obj/item/radio))
|
||||
unlock_text += "Simply speak \"[new_uplink.unlock_code]\" into frequency [RADIO_TOKEN_UPLINK] to unlock its hidden features."
|
||||
add_memory(/datum/memory/key/traitor_uplink, uplink_loc = uplink_loc.name, uplink_code = new_uplink.unlock_code)
|
||||
|
||||
else if(istype(uplink_loc, /obj/item/pen))
|
||||
var/instructions = english_list(new_uplink.unlock_code)
|
||||
unlock_text = "Your Uplink is cunningly disguised as your [P.name]. Simply twist the top of the pen [instructions] from its starting position to unlock its hidden features."
|
||||
add_memory(/datum/memory/key/traitor_uplink, uplink_loc = "PDA pen", uplink_code = instructions)
|
||||
unlock_text += "Simply twist the top of the pen [instructions] from its starting position to unlock its hidden features."
|
||||
add_memory(/datum/memory/key/traitor_uplink, uplink_loc = uplink_loc.name, uplink_code = instructions)
|
||||
|
||||
new_uplink.unlock_text = unlock_text
|
||||
if(!silent)
|
||||
to_chat(traitor_mob, span_boldnotice(unlock_text))
|
||||
if(antag_datum)
|
||||
antag_datum.antag_memory += new_uplink.unlock_note + "<br>"
|
||||
return .
|
||||
|
||||
/// Link a new mobs mind to the creator of said mob. They will join any team they are currently on, and will only switch teams when their creator does.
|
||||
/datum/mind/proc/enslave_mind_to_creator(mob/living/creator)
|
||||
|
||||
@@ -22,9 +22,23 @@
|
||||
var/objective_type = OBJECTIVE_ITEM_TYPE_NORMAL
|
||||
/// Whether this item exists on the station map at the start of a round.
|
||||
var/exists_on_map = FALSE
|
||||
/**
|
||||
* How hard it is to steal this item given normal circumstances, ranked on a scale of 1 to 5.
|
||||
*
|
||||
* 1 - Probably found in a public area
|
||||
* 2 - Likely on someone's person, or in a less-than-public but otherwise unguarded area
|
||||
* 3 - Usually on someone's person, or in a locked locker or otherwise secure area
|
||||
* 4 - Always on someone's person, or in a secure area
|
||||
* 5 - You know it when you see it. Things like the Nuke Disc which have a pointer to it at all times.
|
||||
*
|
||||
* Also accepts 0 as "extremely easy to steal" and >5 as "almost impossible to steal"
|
||||
*/
|
||||
var/difficulty = 0
|
||||
/// A hint explaining how one may find the target item.
|
||||
var/steal_hint = "The clown might have one."
|
||||
|
||||
/// For objectives with special checks (does that intellicard have an ai in it? etcetc)
|
||||
/datum/objective_item/proc/check_special_completion()
|
||||
/datum/objective_item/proc/check_special_completion(obj/item/thing)
|
||||
return TRUE
|
||||
|
||||
/// Takes a list of minds and returns true if this is a valid objective to give to a team of these minds
|
||||
@@ -72,6 +86,8 @@
|
||||
excludefromjob = list(JOB_BARTENDER)
|
||||
item_owner = list(JOB_BARTENDER)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 2
|
||||
steal_hint = "A double-barrel shotgun usually found on the bartender's person, or if none are around, in the bar's backroom."
|
||||
|
||||
/obj/item/gun/ballistic/shotgun/doublebarrel/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/gun/ballistic/shotgun/doublebarrel)
|
||||
@@ -91,6 +107,9 @@
|
||||
JOB_STATION_ENGINEER,
|
||||
)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "Only two of these exist on the station - one in the bridge, and one in atmospherics. \
|
||||
You can use a multitool to hack open the case, or break it open the hard way."
|
||||
|
||||
/obj/item/fireaxe/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/fireaxe)
|
||||
@@ -105,6 +124,8 @@
|
||||
)
|
||||
item_owner = list(JOB_ROBOTICIST)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 2
|
||||
steal_hint = "A specialized tool found in the roboticist's lab. You can use a multitool to hack open the case, or break it open the hard way."
|
||||
|
||||
/obj/item/crowbar/mechremoval/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/crowbar/mechremoval)
|
||||
@@ -115,6 +136,9 @@
|
||||
excludefromjob = list(JOB_CHAPLAIN)
|
||||
item_owner = list(JOB_CHAPLAIN)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 2
|
||||
steal_hint = "A holy artifact usually found on the chaplain's person, or if none are around, in the chapel's relic closet. \
|
||||
If there is a chaplain aboard, it is likely be to be transformed into some holy weapon - some of which are... difficult to remove from their person."
|
||||
|
||||
/obj/item/nullrod/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/nullrod)
|
||||
@@ -125,6 +149,8 @@
|
||||
excludefromjob = list(JOB_CLOWN, JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER)
|
||||
item_owner = list(JOB_CLOWN)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 1
|
||||
steal_hint = "The clown's huge, bright shoes. They should always be on the clown's feet."
|
||||
|
||||
/obj/item/clothing/shoes/clown_shoes/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/clothing/shoes/clown_shoes)
|
||||
@@ -135,6 +161,8 @@
|
||||
excludefromjob = list(JOB_MIME, JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER)
|
||||
item_owner = list(JOB_MIME)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 1
|
||||
steal_hint = "The mime's mask. It should always be on the mime's face."
|
||||
|
||||
/obj/item/clothing/mask/gas/mime/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/clothing/mask/gas/mime)
|
||||
@@ -145,6 +173,9 @@
|
||||
excludefromjob = list(JOB_SHAFT_MINER, JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER)
|
||||
item_owner = list(JOB_SHAFT_MINER)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 1
|
||||
steal_hint = "A tool primarily used by shaft miners to mine. Most carry one (or multiple) on their person, \
|
||||
but they can also be found in the Mining Station, Mining office, or Auxiliary Mining Base on the station."
|
||||
|
||||
/obj/item/gun/energy/recharge/kinetic_accelerator/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/gun/energy/recharge/kinetic_accelerator)
|
||||
@@ -155,6 +186,8 @@
|
||||
excludefromjob = list(JOB_COOK, JOB_HEAD_OF_PERSONNEL, JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER)
|
||||
item_owner = list(JOB_COOK)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 1
|
||||
steal_hint = "The chef's fake Italian moustache, either found on their face or in the garbage, depending on who's on duty."
|
||||
|
||||
/obj/item/clothing/mask/fakemoustache/italian/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/clothing/mask/fakemoustache/italian)
|
||||
@@ -164,6 +197,9 @@
|
||||
targetitem = /obj/item/gun/ballistic/revolver/c38/detective
|
||||
excludefromjob = list(JOB_DETECTIVE)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "A .38 special revolver found in the Detective's holder. \
|
||||
Usually found on the Detective's person, or if none are around, in the detective's locker, in their office."
|
||||
|
||||
/obj/item/gun/ballistic/revolver/c38/detective/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/gun/ballistic/revolver/c38/detective)
|
||||
@@ -174,6 +210,8 @@
|
||||
excludefromjob = list(JOB_LAWYER)
|
||||
item_owner = list(JOB_LAWYER)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 1
|
||||
steal_hint = "The lawyer's badge. Usually pinned to their chest, but a spare can be obtained from their clothes vendor."
|
||||
|
||||
/obj/item/clothing/accessory/lawyers_badge/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/clothing/accessory/lawyers_badge)
|
||||
@@ -183,6 +221,8 @@
|
||||
targetitem = /obj/item/storage/belt/utility/chief
|
||||
excludefromjob = list(JOB_CHIEF_ENGINEER)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 2
|
||||
steal_hint = "The chief engineer's toolbelt, strapped to their waist at all times."
|
||||
|
||||
/obj/item/storage/belt/utility/chief/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/storage/belt/utility/chief)
|
||||
@@ -199,6 +239,8 @@
|
||||
JOB_CHIEF_MEDICAL_OFFICER
|
||||
)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "A self-defense weapon standard-issue for all heads of staffs barring the Head of Security. Rarely found off of their person."
|
||||
|
||||
/obj/item/melee/baton/telescopic/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/melee/baton/telescopic)
|
||||
@@ -209,6 +251,9 @@
|
||||
excludefromjob = list(JOB_QUARTERMASTER, JOB_CARGO_TECHNICIAN)
|
||||
item_owner = list(JOB_QUARTERMASTER)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 2
|
||||
steal_hint = "A card that grants access to Cargo's funds. \
|
||||
Normally found in the locker of the Quartermaster, but a particularly keen one may have it on their person or in their wallet."
|
||||
|
||||
/obj/item/card/id/departmental_budget/car/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/card/id/departmental_budget/car)
|
||||
@@ -218,6 +263,9 @@
|
||||
targetitem = /obj/item/mod/control/pre_equipped/magnate
|
||||
excludefromjob = list(JOB_CAPTAIN)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "An expensive, hand-crafted MOD unit made for the station's Captain. \
|
||||
If not being worn by the Captain, you would find it in the Suit Storage Unit in their quarters."
|
||||
|
||||
/obj/item/mod/control/pre_equipped/magnate/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/mod/control/pre_equipped/magnate)
|
||||
@@ -234,6 +282,10 @@
|
||||
JOB_CHIEF_MEDICAL_OFFICER
|
||||
)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 4
|
||||
steal_hint = "The spare ID of the High Lord himself. \
|
||||
If there's no official Captain around, you may find it pinned to the chest of the Acting Captain - one of the Heads of Staff. \
|
||||
Otherwise, you'll have to bust open the golden safe on the bridge with acid or explosives to get to it."
|
||||
|
||||
/obj/item/card/id/advanced/gold/captains_spare/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/card/id/advanced/gold/captains_spare)
|
||||
@@ -246,6 +298,9 @@
|
||||
targetitem = /obj/item/gun/energy/laser/captain
|
||||
excludefromjob = list(JOB_CAPTAIN)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 4
|
||||
steal_hint = "A self-charging laser gun found in a display case in the Captain's Quarters. \
|
||||
Breaking it open may trigger a security alert, so be careful."
|
||||
|
||||
/obj/item/gun/energy/laser/captain/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/gun/energy/laser/captain)
|
||||
@@ -256,6 +311,9 @@
|
||||
excludefromjob = list(JOB_HEAD_OF_SECURITY)
|
||||
item_owner = list(JOB_HEAD_OF_SECURITY)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 4
|
||||
steal_hint = "The Head of Security's unique three mode laser gun. \
|
||||
Always found on their person, if they are alive, but may otherwise be found in their locker."
|
||||
|
||||
/obj/item/gun/energy/e_gun/hos/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/gun/energy/e_gun/hos)
|
||||
@@ -266,6 +324,8 @@
|
||||
excludefromjob = list(JOB_HEAD_OF_SECURITY)
|
||||
item_owner = list(JOB_HEAD_OF_SECURITY)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 4
|
||||
steal_hint = "A miniaturized combat shotgun. May be found in Head of Security's locker or strapped to their back."
|
||||
|
||||
/obj/item/gun/ballistic/shotgun/automatic/combat/compact/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/gun/ballistic/shotgun/automatic/combat/compact)
|
||||
@@ -276,6 +336,9 @@
|
||||
excludefromjob = list(JOB_CAPTAIN, JOB_RESEARCH_DIRECTOR, JOB_HEAD_OF_PERSONNEL)
|
||||
item_owner = list(JOB_CAPTAIN, JOB_RESEARCH_DIRECTOR)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "Only two of these devices exist on the station, with one sitting in the Teleporter Room \
|
||||
for emergencies, and the other in the Captain's Quarters for personal use."
|
||||
|
||||
/obj/item/hand_tele/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/hand_tele)
|
||||
@@ -286,6 +349,8 @@
|
||||
excludefromjob = list(JOB_CAPTAIN)
|
||||
item_owner = list(JOB_CAPTAIN)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "A special yellow jetpack found in the Suit Storage Unit in the Captain's Quarters."
|
||||
|
||||
/obj/item/tank/jetpack/oxygen/captain/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/tank/jetpack/oxygen/captain)
|
||||
@@ -296,6 +361,9 @@
|
||||
excludefromjob = list(JOB_CHIEF_ENGINEER)
|
||||
item_owner = list(JOB_CHIEF_ENGINEER)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "A pair of magnetic boots found in the Chief Engineer's Suit Storage Unit. \
|
||||
May also be found on their person, concealed beneath their MODsuit."
|
||||
|
||||
/obj/item/clothing/shoes/magboots/advance/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/clothing/shoes/magboots/advance)
|
||||
@@ -306,6 +374,9 @@
|
||||
excludefromjob = list(JOB_CAPTAIN)
|
||||
item_owner = list(JOB_CAPTAIN)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "A gold medal found in the medal box in the Captain's Quarters. \
|
||||
The Captain usually also has one pinned to their jumpsuit."
|
||||
|
||||
/obj/item/clothing/accessory/medal/gold/captain/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/clothing/accessory/medal/gold/captain)
|
||||
@@ -318,6 +389,9 @@
|
||||
excludefromjob = list(JOB_CHIEF_MEDICAL_OFFICER)
|
||||
item_owner = list(JOB_CHIEF_MEDICAL_OFFICER)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "The Chief Medical Officer's personal medical injector. \
|
||||
Usually found amongst their medical supplies on their person, in their belt, or otherwise in their locker."
|
||||
|
||||
/obj/item/hypospray/mkii/cmo/add_stealing_item_objective() // SKYRAT EDIT CHANGE
|
||||
return add_item_to_steal(src, /obj/item/hypospray/mkii/cmo) // SKYRAT EDIT CHANGE
|
||||
@@ -326,6 +400,9 @@
|
||||
name = "the nuclear authentication disk"
|
||||
targetitem = /obj/item/disk/nuclear
|
||||
excludefromjob = list(JOB_CAPTAIN)
|
||||
difficulty = 5
|
||||
steal_hint = "THAT disk - you know the one. Carried by the Captain at all times (hopefully). \
|
||||
Difficult to miss, but if you can't find it, the Head of Security and Captain both have devices to track its precise location."
|
||||
|
||||
/obj/item/disk/nuclear/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/disk/nuclear)
|
||||
@@ -339,6 +416,8 @@
|
||||
excludefromjob = list(JOB_HEAD_OF_SECURITY, JOB_WARDEN)
|
||||
item_owner = list(JOB_HEAD_OF_SECURITY)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 4
|
||||
steal_hint = "An ablative trechcoat found on the shelves of the Armory."
|
||||
|
||||
/obj/item/clothing/suit/hooded/ablative/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/clothing/suit/hooded/ablative)
|
||||
@@ -349,6 +428,9 @@
|
||||
excludefromjob = list(JOB_RESEARCH_DIRECTOR)
|
||||
item_owner = list(JOB_RESEARCH_DIRECTOR)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "A special suit of armor found in the possession of the Research Director. \
|
||||
You may otherwise find it in their locker."
|
||||
|
||||
/obj/item/clothing/suit/armor/reactive/teleport/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/clothing/suit/armor/reactive/teleport)
|
||||
@@ -358,6 +440,11 @@
|
||||
valid_containers = list(/obj/item/folder)
|
||||
targetitem = /obj/item/documents
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "A set of papers belonging to a megaconglomerate. \
|
||||
Nanotrasen documents can easily be found in the station's vault. \
|
||||
For other corporations, you may find them in strange and distant places. \
|
||||
A photocopy may also suffice."
|
||||
|
||||
/obj/item/documents/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/documents) //Any set of secret documents. Doesn't have to be NT's
|
||||
@@ -367,6 +454,8 @@
|
||||
valid_containers = list(/obj/item/nuke_core_container)
|
||||
targetitem = /obj/item/nuke_core
|
||||
exists_on_map = TRUE
|
||||
difficulty = 4
|
||||
steal_hint = "The core of the station's self-destruct device, found in the vault."
|
||||
|
||||
/obj/item/nuke_core/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/nuke_core)
|
||||
@@ -381,6 +470,8 @@
|
||||
excludefromjob = list(JOB_RESEARCH_DIRECTOR, JOB_SCIENTIST, JOB_ROBOTICIST, JOB_GENETICIST)
|
||||
item_owner = list(JOB_RESEARCH_DIRECTOR, JOB_SCIENTIST)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 4
|
||||
steal_hint = "The hard drive of the master research server, found in R&D's server room."
|
||||
|
||||
/obj/item/computer_disk/hdd_theft/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/computer_disk/hdd_theft)
|
||||
@@ -394,6 +485,8 @@
|
||||
name = "a sliver of a supermatter crystal"
|
||||
targetitem = /obj/item/nuke_core/supermatter_sliver
|
||||
valid_containers = list(/obj/item/nuke_core_container/supermatter)
|
||||
difficulty = 5
|
||||
steal_hint = "A small shard of the station's supermatter crystal engine."
|
||||
|
||||
/datum/objective_item/steal/supermatter/New()
|
||||
special_equipment += /obj/item/storage/box/syndie_kit/supermatter
|
||||
@@ -406,6 +499,8 @@
|
||||
/datum/objective_item/steal/functionalai
|
||||
name = "a functional AI"
|
||||
targetitem = /obj/item/aicard
|
||||
difficulty = 5
|
||||
steal_hint = "An intellicard (or MODsuit) containing an active, functional AI."
|
||||
|
||||
/datum/objective_item/steal/functionalai/New()
|
||||
. = ..()
|
||||
@@ -439,6 +534,8 @@
|
||||
item_owner = list(JOB_CHIEF_ENGINEER)
|
||||
altitems = list(/obj/item/photo)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "The blueprints of the station, found in the Chief Engineer's locker, or on their person. A picture may suffice."
|
||||
|
||||
/obj/item/areaeditor/blueprints/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/areaeditor/blueprints)
|
||||
@@ -457,6 +554,8 @@
|
||||
targetitem = /obj/item/blackbox
|
||||
excludefromjob = list(JOB_CHIEF_ENGINEER, JOB_STATION_ENGINEER, JOB_ATMOSPHERIC_TECHNICIAN)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 4
|
||||
steal_hint = "The station's data Blackbox, found solely within Telecommunications."
|
||||
|
||||
/obj/item/blackbox/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/blackbox)
|
||||
@@ -470,6 +569,8 @@
|
||||
excludefromjob = list(JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER, JOB_ATMOSPHERIC_TECHNICIAN, JOB_STATION_ENGINEER, JOB_CHIEF_ENGINEER)
|
||||
item_owner = list(JOB_STATION_ENGINEER, JOB_CHIEF_ENGINEER)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 1
|
||||
steal_hint = "A basic pair of insulated gloves, usually worn by Assistants, Engineers, or Cargo Technicians."
|
||||
|
||||
/obj/item/clothing/gloves/color/yellow/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/clothing/gloves/color/yellow)
|
||||
@@ -479,6 +580,8 @@
|
||||
targetitem = /obj/item/toy/plush/moth
|
||||
excludefromjob = list(JOB_PSYCHOLOGIST, JOB_PARAMEDIC, JOB_CHEMIST, JOB_MEDICAL_DOCTOR, JOB_VIROLOGIST, JOB_CHIEF_MEDICAL_OFFICER, JOB_CORONER)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 1
|
||||
steal_hint = "A moth plush toy. The Psychologist has one to help console patients."
|
||||
|
||||
/obj/item/toy/plush/moth/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/toy/plush/moth)
|
||||
@@ -487,6 +590,8 @@
|
||||
name = "cute lizard plush toy"
|
||||
targetitem = /obj/item/toy/plush/lizard_plushie
|
||||
exists_on_map = TRUE
|
||||
difficulty = 1
|
||||
steal_hint = "A lizard plush toy. Often found hidden in maintenance."
|
||||
|
||||
/obj/item/toy/plush/lizard_plushie/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/toy/plush/lizard_plushie)
|
||||
@@ -496,6 +601,8 @@
|
||||
targetitem = /obj/item/stamp/denied
|
||||
excludefromjob = list(JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER, JOB_SHAFT_MINER)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 1
|
||||
steal_hint = "Cargo often has multiple of these red stamps lying around to process paperwork."
|
||||
|
||||
/obj/item/stamp/denied/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/stamp/denied)
|
||||
@@ -505,6 +612,8 @@
|
||||
targetitem = /obj/item/stamp/granted
|
||||
excludefromjob = list(JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER, JOB_SHAFT_MINER)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 1
|
||||
steal_hint = "Cargo often has multiple of these green stamps lying around to process paperwork."
|
||||
|
||||
/obj/item/stamp/granted/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/stamp/granted)
|
||||
@@ -514,6 +623,9 @@
|
||||
targetitem = /obj/item/book/manual/wiki/security_space_law
|
||||
excludefromjob = list(JOB_SECURITY_OFFICER, JOB_WARDEN, JOB_HEAD_OF_SECURITY, JOB_LAWYER, JOB_DETECTIVE)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 1
|
||||
steal_hint = "Sometimes found in the possession of members of Security and Lawyers. \
|
||||
The courtroom and the library are also good places to look."
|
||||
|
||||
/obj/item/book/manual/wiki/security_space_law/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/book/manual/wiki/security_space_law)
|
||||
@@ -524,6 +636,8 @@
|
||||
excludefromjob = list(JOB_ATMOSPHERIC_TECHNICIAN, JOB_STATION_ENGINEER, JOB_CHIEF_ENGINEER, JOB_SCIENTIST, JOB_RESEARCH_DIRECTOR, JOB_GENETICIST, JOB_ROBOTICIST)
|
||||
item_owner = list(JOB_CHIEF_ENGINEER)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 1
|
||||
steal_hint = "A tool often used by Engineers, Atmospherics Technicians, and Ordnance Technicians."
|
||||
|
||||
/obj/item/pipe_dispenser/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/pipe_dispenser)
|
||||
@@ -533,6 +647,254 @@
|
||||
targetitem = /obj/item/storage/fancy/donut_box
|
||||
excludefromjob = list(JOB_CAPTAIN, JOB_CHIEF_ENGINEER, JOB_HEAD_OF_PERSONNEL, JOB_HEAD_OF_SECURITY, JOB_QUARTERMASTER, JOB_CHIEF_MEDICAL_OFFICER, JOB_RESEARCH_DIRECTOR, JOB_SECURITY_OFFICER, JOB_WARDEN, JOB_LAWYER, JOB_DETECTIVE)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 1
|
||||
steal_hint = "Everyone has a box of donuts - you may most commonly find them on the Bridge, within Security, or in any department's break room."
|
||||
|
||||
/obj/item/storage/fancy/donut_box/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/storage/fancy/donut_box)
|
||||
|
||||
/datum/objective_item/steal/spy
|
||||
objective_type = OBJECTIVE_ITEM_TYPE_SPY
|
||||
|
||||
/datum/objective_item/steal/spy/lamarr
|
||||
name = "The Research Director's pet headcrab"
|
||||
targetitem = /obj/item/clothing/mask/facehugger/lamarr
|
||||
excludefromjob = list(JOB_RESEARCH_DIRECTOR)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "The Research Director's pet headcrab, Lamarr, found in a secure cage in their office."
|
||||
|
||||
/obj/item/clothing/mask/facehugger/lamarr/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/clothing/mask/facehugger/lamarr)
|
||||
|
||||
/datum/objective_item/steal/spy/disabler
|
||||
name = "a disabler"
|
||||
targetitem = /obj/item/gun/energy/disabler
|
||||
excludefromjob = list(
|
||||
JOB_CAPTAIN,
|
||||
JOB_DETECTIVE,
|
||||
JOB_HEAD_OF_PERSONNEL,
|
||||
JOB_HEAD_OF_SECURITY,
|
||||
JOB_SECURITY_OFFICER,
|
||||
JOB_WARDEN,
|
||||
)
|
||||
difficulty = 2
|
||||
steal_hint = "A hand-held disabler, often found in the possession of Security Officers."
|
||||
|
||||
/datum/objective_item/steal/spy/energy_gun
|
||||
name = "an energy gun"
|
||||
targetitem = /obj/item/gun/energy/e_gun
|
||||
excludefromjob = list(
|
||||
JOB_CAPTAIN,
|
||||
JOB_CHIEF_ENGINEER,
|
||||
JOB_CHIEF_MEDICAL_OFFICER,
|
||||
JOB_DETECTIVE,
|
||||
JOB_HEAD_OF_PERSONNEL,
|
||||
JOB_HEAD_OF_SECURITY,
|
||||
JOB_QUARTERMASTER,
|
||||
JOB_RESEARCH_DIRECTOR,
|
||||
JOB_SECURITY_OFFICER,
|
||||
JOB_WARDEN,
|
||||
)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 2
|
||||
steal_hint = "A two-mode energy gun, found in the station's Armory, as well as in the hands of some heads of staff for personal defense."
|
||||
|
||||
/datum/objective_item/steal/spy/energy_gun/check_special_completion(obj/item/thing)
|
||||
return thing.type == /obj/item/gun/energy/e_gun
|
||||
|
||||
/obj/item/gun/energy/e_gun/add_stealing_item_objective()
|
||||
if(type == /obj/item/gun/energy/e_gun)
|
||||
return add_item_to_steal(src, /obj/item/gun/energy/e_gun)
|
||||
|
||||
/datum/objective_item/steal/spy/laser_gun
|
||||
name = "a laser gun"
|
||||
targetitem = /obj/item/gun/energy/laser
|
||||
excludefromjob = list(
|
||||
JOB_CAPTAIN,
|
||||
JOB_CHIEF_ENGINEER,
|
||||
JOB_CHIEF_MEDICAL_OFFICER,
|
||||
JOB_DETECTIVE,
|
||||
JOB_HEAD_OF_PERSONNEL,
|
||||
JOB_HEAD_OF_SECURITY,
|
||||
JOB_QUARTERMASTER,
|
||||
JOB_RESEARCH_DIRECTOR,
|
||||
JOB_SECURITY_OFFICER,
|
||||
JOB_WARDEN,
|
||||
)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "A simple laser gun, found in the station's Armory."
|
||||
|
||||
/datum/objective_item/steal/spy/laser_gun/check_special_completion(obj/item/thing)
|
||||
return thing.type == /obj/item/gun/energy/laser
|
||||
|
||||
/obj/item/gun/energy/laser/add_stealing_item_objective()
|
||||
if(type == /obj/item/gun/energy/laser)
|
||||
return add_item_to_steal(src, /obj/item/gun/energy/laser)
|
||||
|
||||
/datum/objective_item/steal/spy/shotgun
|
||||
name = "a riot shotgun"
|
||||
targetitem = /obj/item/gun/ballistic/shotgun/riot
|
||||
excludefromjob = list(
|
||||
JOB_DETECTIVE,
|
||||
JOB_HEAD_OF_PERSONNEL,
|
||||
JOB_HEAD_OF_SECURITY,
|
||||
JOB_SECURITY_OFFICER,
|
||||
JOB_WARDEN,
|
||||
)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "A shotgun found in the station's Armory for riot suppression. Doesn't miss."
|
||||
|
||||
/obj/item/gun/ballistic/shotgun/riot/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/gun/ballistic/shotgun/riot)
|
||||
|
||||
/datum/objective_item/steal/spy/temp_gun
|
||||
name = "security's temperature gun"
|
||||
targetitem = /obj/item/gun/energy/temperature/security
|
||||
excludefromjob = list(
|
||||
JOB_DETECTIVE,
|
||||
JOB_HEAD_OF_PERSONNEL,
|
||||
JOB_HEAD_OF_SECURITY,
|
||||
JOB_SECURITY_OFFICER,
|
||||
JOB_WARDEN,
|
||||
)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 2 // lowered for the meme
|
||||
steal_hint = "Security's TRUSTY temperature gun, found in the station's Armory."
|
||||
|
||||
/obj/item/gun/energy/temperature/security/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/gun/energy/temperature/security)
|
||||
|
||||
/datum/objective_item/steal/spy/stamp
|
||||
name = "a head of staff's stamp"
|
||||
targetitem = /obj/item/stamp/head
|
||||
excludefromjob = list(
|
||||
JOB_CAPTAIN,
|
||||
JOB_CHIEF_ENGINEER,
|
||||
JOB_CHIEF_MEDICAL_OFFICER,
|
||||
JOB_HEAD_OF_PERSONNEL,
|
||||
JOB_HEAD_OF_SECURITY,
|
||||
JOB_QUARTERMASTER,
|
||||
JOB_RESEARCH_DIRECTOR,
|
||||
)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 1
|
||||
steal_hint = "A stamp owned by a head of staff, from their offices."
|
||||
|
||||
/obj/item/stamp/head/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/stamp/head)
|
||||
|
||||
/datum/objective_item/steal/spy/sunglasses
|
||||
name = "sunglasses"
|
||||
targetitem = /obj/item/clothing/glasses/sunglasses
|
||||
excludefromjob = list(
|
||||
JOB_CAPTAIN,
|
||||
JOB_CHIEF_ENGINEER,
|
||||
JOB_CHIEF_MEDICAL_OFFICER,
|
||||
JOB_HEAD_OF_PERSONNEL,
|
||||
JOB_HEAD_OF_SECURITY,
|
||||
JOB_LAWYER,
|
||||
JOB_QUARTERMASTER,
|
||||
JOB_RESEARCH_DIRECTOR,
|
||||
JOB_SECURITY_OFFICER,
|
||||
JOB_WARDEN,
|
||||
)
|
||||
difficulty = 1
|
||||
steal_hint = "A pair of sunglasses. Lawyers often have a few pairs, as do some heads of staff. \
|
||||
You can also obtain a pair from dissassembling hudglasses."
|
||||
|
||||
/datum/objective_item/steal/spy/ce_modsuit
|
||||
name = "the cheif engineer's advanced MOD control unit"
|
||||
targetitem = /obj/item/mod/control/pre_equipped/advanced
|
||||
excludefromjob = list(JOB_CHIEF_ENGINEER)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 2
|
||||
steal_hint = "An advanced version of the standard Engineering MODsuit commonly worn by the Chief Engineer."
|
||||
|
||||
/obj/item/mod/control/pre_equipped/advanced/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/mod/control/pre_equipped/advanced)
|
||||
|
||||
/datum/objective_item/steal/spy/rd_modsuit
|
||||
name = "the research director's research MOD control unit"
|
||||
targetitem = /obj/item/mod/control/pre_equipped/research
|
||||
excludefromjob = list(JOB_RESEARCH_DIRECTOR)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 2
|
||||
steal_hint = "A bulky MODsuit commonly worn by the Research Director to protect themselves from the hazards of their work."
|
||||
|
||||
/obj/item/mod/control/pre_equipped/research/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/mod/control/pre_equipped/research)
|
||||
|
||||
/datum/objective_item/steal/spy/cmo_modsuit
|
||||
name = "the chief medical officer's rescure MOD control unit"
|
||||
targetitem = /obj/item/mod/control/pre_equipped/rescue
|
||||
excludefromjob = list(JOB_CHIEF_MEDICAL_OFFICER)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 2
|
||||
steal_hint = "A MODsuit sometimes equipped by the Chief Medical Officer to perform rescue opperations in hazardous environments."
|
||||
|
||||
/obj/item/mod/control/pre_equipped/rescue/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/mod/control/pre_equipped/rescue)
|
||||
|
||||
/datum/objective_item/steal/spy/hos_modsuit
|
||||
name = "the head of security's safeguard MOD control unit"
|
||||
targetitem = /obj/item/mod/control/pre_equipped/safeguard
|
||||
excludefromjob = list(JOB_HEAD_OF_SECURITY)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 2
|
||||
steal_hint = "An advanced MODsuit sometimes worn by the Head of Security when needing to detain hostiles invading the station."
|
||||
|
||||
/obj/item/mod/control/pre_equipped/safeguard/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/mod/control/pre_equipped/safeguard)
|
||||
|
||||
/datum/objective_item/steal/spy/stun_baton
|
||||
name = "a stun baton"
|
||||
targetitem = /obj/item/melee/baton/security
|
||||
excludefromjob = list(
|
||||
JOB_CAPTAIN,
|
||||
JOB_DETECTIVE,
|
||||
JOB_HEAD_OF_PERSONNEL,
|
||||
JOB_HEAD_OF_SECURITY,
|
||||
JOB_SECURITY_OFFICER,
|
||||
JOB_WARDEN,
|
||||
)
|
||||
difficulty = 2
|
||||
steal_hint = "Steal any stun baton from Security."
|
||||
|
||||
/datum/objective_item/steal/spy/stun_baton/check_special_completion(obj/item/thing)
|
||||
return !istype(thing, /obj/item/melee/baton/security/cattleprod)
|
||||
|
||||
/datum/objective_item/steal/spy/det_baton
|
||||
name = "the detective's baton"
|
||||
targetitem = /obj/item/melee/baton
|
||||
excludefromjob = list(
|
||||
JOB_CAPTAIN,
|
||||
JOB_DETECTIVE,
|
||||
JOB_HEAD_OF_PERSONNEL,
|
||||
JOB_HEAD_OF_SECURITY,
|
||||
JOB_SECURITY_OFFICER,
|
||||
JOB_WARDEN,
|
||||
)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 2
|
||||
steal_hint = "The detective's old wooden truncheon, commonly found on their person for self defense."
|
||||
|
||||
/datum/objective_item/steal/spy/det_baton/check_special_completion(obj/item/thing)
|
||||
return thing.type == /obj/item/melee/baton
|
||||
|
||||
/obj/item/melee/baton/add_stealing_item_objective()
|
||||
if(type == /obj/item/melee/baton)
|
||||
return add_item_to_steal(src, /obj/item/melee/baton)
|
||||
|
||||
/datum/objective_item/steal/spy/captain_sabre_sheathe
|
||||
name = "the captain's sabre sheathe"
|
||||
targetitem = /obj/item/storage/belt/sabre
|
||||
excludefromjob = list(JOB_CAPTAIN)
|
||||
exists_on_map = TRUE
|
||||
difficulty = 3
|
||||
steal_hint = "The sheathe for the captain's sabre, found in their closet or strapped to their waist at all times."
|
||||
|
||||
/obj/item/storage/belt/sabre/add_stealing_item_objective()
|
||||
return add_item_to_steal(src, /obj/item/storage/belt/sabre)
|
||||
|
||||
@@ -202,9 +202,104 @@ effective or pretty fucking useless.
|
||||
target = round(target)
|
||||
wavelength = clamp(target, 0, 120)
|
||||
|
||||
/datum/action/item_action/stealth_mode
|
||||
name = "Toggle Stealth"
|
||||
desc = "Makes you invisible to the naked eye."
|
||||
button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
|
||||
button_icon_state = "ninja_cloak"
|
||||
/// Whether stealth is active or not
|
||||
var/stealth_engaged = FALSE
|
||||
/// The amount of time the stealth mode can be active for, drains to 0 when active
|
||||
var/charge = 30 SECONDS
|
||||
/// The maximum amount of time the stealth mode can be active for
|
||||
var/max_charge = 30 SECONDS
|
||||
/// The minimum alpha value for the stealth mode
|
||||
var/min_alpha = 0
|
||||
/// Whether the stealth mode recharges while active
|
||||
/// if TRUE standing in darkness will recharge even while active
|
||||
/// if FALSE it will not uncharge, but not recharge while in darkness
|
||||
var/recharge_while_active = TRUE
|
||||
|
||||
/datum/action/item_action/stealth_mode/is_action_active(atom/movable/screen/movable/action_button/current_button)
|
||||
return stealth_engaged
|
||||
|
||||
/datum/action/item_action/stealth_mode/Grant(mob/grant_to)
|
||||
. = ..()
|
||||
START_PROCESSING(SSobj, src)
|
||||
build_all_button_icons(UPDATE_BUTTON_STATUS)
|
||||
|
||||
/datum/action/item_action/stealth_mode/Remove(mob/remove_from)
|
||||
if(!isnull(owner) && stealth_engaged)
|
||||
stealth_off()
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
return ..()
|
||||
|
||||
/datum/action/item_action/stealth_mode/Trigger(trigger_flags)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
|
||||
if(stealth_engaged)
|
||||
stealth_off()
|
||||
else
|
||||
stealth_on()
|
||||
|
||||
/datum/action/item_action/stealth_mode/proc/stealth_on()
|
||||
animate(owner, alpha = get_alpha(), time = 0.5 SECONDS)
|
||||
apply_wibbly_filters(owner)
|
||||
stealth_engaged = TRUE
|
||||
build_all_button_icons(UPDATE_BUTTON_STATUS|UPDATE_BUTTON_BACKGROUND)
|
||||
owner.balloon_alert(owner, "stealth mode engaged")
|
||||
|
||||
/datum/action/item_action/stealth_mode/proc/stealth_off()
|
||||
owner.alpha = initial(owner.alpha)
|
||||
remove_wibbly_filters(owner)
|
||||
stealth_engaged = FALSE
|
||||
build_all_button_icons(UPDATE_BUTTON_STATUS|UPDATE_BUTTON_BACKGROUND)
|
||||
owner.balloon_alert(owner, "stealth mode disengaged")
|
||||
|
||||
/datum/action/item_action/stealth_mode/proc/get_alpha()
|
||||
return clamp(255 - (255 * charge / max_charge), min_alpha, 255)
|
||||
|
||||
/datum/action/item_action/stealth_mode/process(seconds_per_tick)
|
||||
if(!stealth_engaged)
|
||||
// Recharge over time
|
||||
charge = min(max_charge, charge + (max_charge * 0.04) * seconds_per_tick)
|
||||
build_all_button_icons(UPDATE_BUTTON_STATUS)
|
||||
return
|
||||
|
||||
if(charge <= 0)
|
||||
stealth_off()
|
||||
return
|
||||
|
||||
var/turf/our_turf = get_turf(owner)
|
||||
var/lumcount = our_turf?.get_lumcount() || 0
|
||||
if(lumcount > 0.3)
|
||||
// Decay charge while invisible+ in the light
|
||||
charge = max(0, charge - (max_charge * 0.05) * seconds_per_tick)
|
||||
build_all_button_icons(UPDATE_BUTTON_STATUS)
|
||||
|
||||
else if(recharge_while_active)
|
||||
// Return charage while invisible + in the darkness + recharge_while_active
|
||||
charge = min(max_charge, charge + (max_charge * 0.1) * seconds_per_tick)
|
||||
build_all_button_icons(UPDATE_BUTTON_STATUS)
|
||||
|
||||
animate(owner, alpha = get_alpha(), time = 1 SECONDS, flags = ANIMATION_PARALLEL)
|
||||
|
||||
/datum/action/item_action/stealth_mode/update_button_status(atom/movable/screen/movable/action_button/current_button, force)
|
||||
. = ..()
|
||||
current_button.maptext_x = 9
|
||||
current_button.maptext = MAPTEXT_TINY_UNICODE("[round(charge / max_charge * 100, 0.01)]%")
|
||||
|
||||
/datum/action/item_action/stealth_mode/weaker
|
||||
charge = 15 SECONDS
|
||||
max_charge = 15 SECONDS
|
||||
min_alpha = 20
|
||||
recharge_while_active = FALSE
|
||||
|
||||
/obj/item/shadowcloak
|
||||
name = "cloaker belt"
|
||||
desc = "Makes you invisible for short periods of time. Recharges in darkness."
|
||||
desc = "Makes you invisible for short periods of time. Recharges in darkness, even while active."
|
||||
icon = 'icons/obj/clothing/belts.dmi'
|
||||
icon_state = "utility"
|
||||
inhand_icon_state = "utility"
|
||||
@@ -214,66 +309,16 @@ effective or pretty fucking useless.
|
||||
slot_flags = ITEM_SLOT_BELT
|
||||
attack_verb_continuous = list("whips", "lashes", "disciplines")
|
||||
attack_verb_simple = list("whip", "lash", "discipline")
|
||||
|
||||
var/mob/living/carbon/human/user = null
|
||||
var/charge = 300
|
||||
var/max_charge = 300
|
||||
var/on = FALSE
|
||||
actions_types = list(/datum/action/item_action/toggle)
|
||||
|
||||
/obj/item/shadowcloak/ui_action_click(mob/user)
|
||||
if(user.get_item_by_slot(ITEM_SLOT_BELT) == src)
|
||||
if(!on)
|
||||
Activate(usr)
|
||||
|
||||
else
|
||||
Deactivate()
|
||||
|
||||
return
|
||||
actions_types = list(/datum/action/item_action/stealth_mode)
|
||||
|
||||
/obj/item/shadowcloak/item_action_slot_check(slot, mob/user)
|
||||
if(slot & ITEM_SLOT_BELT)
|
||||
return 1
|
||||
return slot & slot_flags
|
||||
|
||||
/obj/item/shadowcloak/proc/Activate(mob/living/carbon/human/user)
|
||||
if(!user)
|
||||
return
|
||||
|
||||
to_chat(user, span_notice("You activate [src]."))
|
||||
src.user = user
|
||||
START_PROCESSING(SSobj, src)
|
||||
on = TRUE
|
||||
|
||||
/obj/item/shadowcloak/proc/Deactivate()
|
||||
to_chat(user, span_notice("You deactivate [src]."))
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
if(user)
|
||||
user.alpha = initial(user.alpha)
|
||||
|
||||
on = FALSE
|
||||
user = null
|
||||
|
||||
/obj/item/shadowcloak/dropped(mob/user)
|
||||
..()
|
||||
if(user && user.get_item_by_slot(ITEM_SLOT_BELT) != src)
|
||||
Deactivate()
|
||||
|
||||
/obj/item/shadowcloak/process(seconds_per_tick)
|
||||
if(user.get_item_by_slot(ITEM_SLOT_BELT) != src)
|
||||
Deactivate()
|
||||
return
|
||||
|
||||
var/turf/T = get_turf(src)
|
||||
if(on)
|
||||
var/lumcount = T.get_lumcount()
|
||||
|
||||
if(lumcount > 0.3)
|
||||
charge = max(0, charge - 12.5 * seconds_per_tick)//Quick decrease in light
|
||||
|
||||
else
|
||||
charge = min(max_charge, charge + 25 * seconds_per_tick) //Charge in the dark
|
||||
|
||||
animate(user,alpha = clamp(255 - charge,0,255),time = 10)
|
||||
/obj/item/shadowcloak/weaker
|
||||
name = "stealth belt"
|
||||
desc = "Makes you nigh-invisible to the naked eye for a short period of time. \
|
||||
Lasts indefinitely in darkness, but will not recharge unless inactive."
|
||||
actions_types = list(/datum/action/item_action/stealth_mode/weaker)
|
||||
|
||||
/// Checks if a given atom is in range of a radio jammer, returns TRUE if it is.
|
||||
/proc/is_within_radio_jammer_range(atom/source)
|
||||
|
||||
@@ -174,6 +174,16 @@
|
||||
for(var/i in 1 to 7)
|
||||
new /obj/item/ammo_casing/shotgun/buckshot(src)
|
||||
|
||||
/obj/item/storage/box/slugs
|
||||
name = "box of shotgun shells (Lethal - Slugs)"
|
||||
desc = "A box full of lethal shotgun slugs, designed for shotguns."
|
||||
icon_state = "breacher_box"
|
||||
illustration = null
|
||||
|
||||
/obj/item/storage/box/slugs/PopulateContents()
|
||||
for(var/i in 1 to 7)
|
||||
new /obj/item/ammo_casing/shotgun(src)
|
||||
|
||||
/obj/item/storage/box/beanbag
|
||||
name = "box of shotgun shells (Less Lethal - Beanbag)"
|
||||
desc = "A box full of beanbag shotgun shells, designed for shotguns."
|
||||
|
||||
@@ -271,6 +271,24 @@
|
||||
/obj/item/storage/pill_bottle/penacid = 1)
|
||||
generate_items_inside(items_inside,src)
|
||||
|
||||
/obj/item/storage/medkit/tactical_lite
|
||||
name = "combat first aid kit"
|
||||
icon_state = "medkit_tactical"
|
||||
inhand_icon_state = "medkit-tactical"
|
||||
damagetype_healed = HEAL_ALL_DAMAGE
|
||||
|
||||
/obj/item/storage/medkit/tactical_lite/PopulateContents()
|
||||
if(empty)
|
||||
return
|
||||
var/static/list/items_inside = list(
|
||||
/obj/item/healthanalyzer/advanced = 1,
|
||||
/obj/item/reagent_containers/hypospray/medipen/atropine = 1,
|
||||
/obj/item/stack/medical/gauze = 1,
|
||||
/obj/item/stack/medical/suture/medicated = 2,
|
||||
/obj/item/stack/medical/mesh/advanced = 2,
|
||||
)
|
||||
generate_items_inside(items_inside, src)
|
||||
|
||||
/obj/item/storage/medkit/tactical
|
||||
name = "combat medical kit"
|
||||
desc = "I hope you've got insurance."
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
/obj/structure/closet/gimmick/tacticool/PopulateContents()
|
||||
..()
|
||||
new /obj/item/clothing/glasses/eyepatch(src)
|
||||
new /obj/item/clothing/glasses/sunglasses(src)
|
||||
new /obj/item/clothing/gloves/tackler/combat(src)
|
||||
new /obj/item/clothing/gloves/tackler/combat(src)
|
||||
new /obj/item/clothing/head/helmet/swat(src)
|
||||
@@ -53,6 +52,8 @@
|
||||
new /obj/item/clothing/under/syndicate/tacticool(src)
|
||||
new /obj/item/clothing/under/syndicate/tacticool(src)
|
||||
|
||||
/obj/structure/closet/gimmick/tacticool/populate_contents_immediate()
|
||||
new /obj/item/clothing/glasses/sunglasses(src)
|
||||
|
||||
/obj/structure/closet/thunderdome
|
||||
name = "\improper Thunderdome closet"
|
||||
@@ -69,8 +70,6 @@
|
||||
new /obj/item/clothing/suit/armor/tdome/red(src)
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/melee/energy/sword/saber(src)
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/gun/energy/laser(src)
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/melee/baton/security/loaded(src)
|
||||
for(var/i in 1 to 3)
|
||||
@@ -78,6 +77,10 @@
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/clothing/head/helmet/thunderdome(src)
|
||||
|
||||
/obj/structure/closet/thunderdome/tdred/populate_contents_immediate()
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/gun/energy/laser(src)
|
||||
|
||||
/obj/structure/closet/thunderdome/tdgreen
|
||||
name = "green-team Thunderdome closet"
|
||||
icon_door = "green"
|
||||
@@ -88,8 +91,6 @@
|
||||
new /obj/item/clothing/suit/armor/tdome/green(src)
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/melee/energy/sword/saber(src)
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/gun/energy/laser(src)
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/melee/baton/security/loaded(src)
|
||||
for(var/i in 1 to 3)
|
||||
@@ -97,6 +98,10 @@
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/clothing/head/helmet/thunderdome(src)
|
||||
|
||||
/obj/structure/closet/thunderdome/tdgreen/populate_contents_immediate()
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/gun/energy/laser(src)
|
||||
|
||||
/obj/structure/closet/malf/suits
|
||||
desc = "It's a storage unit for operational gear."
|
||||
icon_state = "syndicate"
|
||||
|
||||
@@ -16,12 +16,14 @@
|
||||
new /obj/item/computer_disk/command/captain(src)
|
||||
new /obj/item/radio/headset/heads/captain/alt(src)
|
||||
new /obj/item/radio/headset/heads/captain(src)
|
||||
new /obj/item/storage/belt/sabre(src)
|
||||
new /obj/item/gun/energy/e_gun(src)
|
||||
new /obj/item/door_remote/captain(src)
|
||||
new /obj/item/storage/photo_album/captain(src)
|
||||
new /obj/item/card/id/departmental_budget(src) //SKYRAT EDIT ADDITION
|
||||
|
||||
/obj/structure/closet/secure_closet/captains/populate_contents_immediate()
|
||||
new /obj/item/gun/energy/e_gun(src)
|
||||
new /obj/item/storage/belt/sabre(src)
|
||||
|
||||
/obj/structure/closet/secure_closet/hop
|
||||
name = "head of personnel's locker"
|
||||
icon_state = "hop"
|
||||
@@ -38,7 +40,6 @@
|
||||
new /obj/item/storage/box/silver_ids(src)
|
||||
new /obj/item/megaphone/command(src)
|
||||
new /obj/item/assembly/flash/handheld(src)
|
||||
new /obj/item/gun/energy/e_gun(src)
|
||||
new /obj/item/clothing/neck/petcollar(src)
|
||||
new /obj/item/pet_carrier(src)
|
||||
new /obj/item/door_remote/civilian(src)
|
||||
@@ -47,6 +48,9 @@
|
||||
new /obj/item/storage/lockbox/medal/hop(src)
|
||||
new /obj/item/card/id/departmental_budget/srv(src) //SKYRAT EDIT ADDITION
|
||||
|
||||
/obj/structure/closet/secure_closet/hop/populate_contents_immediate()
|
||||
new /obj/item/gun/energy/e_gun(src)
|
||||
|
||||
/obj/structure/closet/secure_closet/hos
|
||||
name = "head of security's locker"
|
||||
icon_state = "hos"
|
||||
@@ -308,6 +312,8 @@
|
||||
new /obj/item/storage/box/firingpins(src)
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/storage/box/rubbershot(src)
|
||||
|
||||
/obj/structure/closet/secure_closet/armory2/populate_contents_immediate()
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/gun/ballistic/shotgun/riot(src)
|
||||
|
||||
@@ -321,12 +327,14 @@
|
||||
..()
|
||||
new /obj/item/storage/box/firingpins(src)
|
||||
new /obj/item/gun/energy/ionrifle(src)
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/gun/energy/laser/thermal(src)
|
||||
|
||||
/obj/structure/closet/secure_closet/armory3/populate_contents_immediate()
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/gun/energy/e_gun(src)
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/gun/energy/laser(src)
|
||||
for(var/i in 1 to 3)
|
||||
new /obj/item/gun/energy/laser/thermal(src)
|
||||
|
||||
/obj/structure/closet/secure_closet/tac
|
||||
name = "armory tac locker"
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
/datum/antagonist/spy
|
||||
name = "\improper Spy"
|
||||
roundend_category = "spies"
|
||||
antagpanel_category = "Spy"
|
||||
antag_hud_name = "spy"
|
||||
job_rank = ROLE_SPY
|
||||
antag_moodlet = /datum/mood_event/focused
|
||||
hijack_speed = 1
|
||||
ui_name = "AntagInfoSpy"
|
||||
preview_outfit = /datum/outfit/spy
|
||||
/// Whether an uplink has been created (successfully or at all)
|
||||
var/uplink_created = FALSE
|
||||
/// String displayed in the antag panel pointing the spy to where their uplink is.
|
||||
var/uplink_location
|
||||
/// Whether we give them some random objetives to aim for.
|
||||
var/spawn_with_objectives = TRUE
|
||||
/// Tracks number of bounties claimed, for roundend
|
||||
var/bounties_claimed = 0
|
||||
/// Tracks all loot items the spy has claimed, for roundend
|
||||
var/list/all_loot = list()
|
||||
/// Weakref to our spy uplink
|
||||
/// Only exists for the sole purpose of letting admins see it
|
||||
var/datum/weakref/uplink_weakref
|
||||
|
||||
/datum/antagonist/spy/on_gain()
|
||||
if(!uplink_created)
|
||||
auto_create_spy_uplink(owner.current)
|
||||
if(spawn_with_objectives)
|
||||
give_random_objectives()
|
||||
. = ..()
|
||||
SEND_SOUND(owner.current, sound('sound/ambience/antag/spy.ogg'))
|
||||
|
||||
/datum/antagonist/spy/ui_static_data(mob/user)
|
||||
var/list/data = ..()
|
||||
data["uplink_location"] = uplink_location
|
||||
return data
|
||||
|
||||
/datum/antagonist/spy/get_admin_commands()
|
||||
. = ..()
|
||||
// I wanted to put this in check-antagonists but it's less conducive to that
|
||||
.["See All Bounties (For all spies)"] = CALLBACK(src, PROC_REF(see_bounties))
|
||||
.["Refresh Bounties (For all spies)"] = CALLBACK(src, PROC_REF(refresh_bounties))
|
||||
.["Give Spy Uplink"] = CALLBACK(src, PROC_REF(admin_create_spy_uplink))
|
||||
.["Bounty Handler VV"] = CALLBACK(src, PROC_REF(bounty_handler_vv))
|
||||
|
||||
/datum/antagonist/spy/proc/see_bounties()
|
||||
if(!check_rights(R_ADMIN|R_DEBUG))
|
||||
return
|
||||
|
||||
var/datum/component/spy_uplink/uplink = uplink_weakref?.resolve()
|
||||
if(isnull(uplink))
|
||||
tgui_alert(usr, "No spy uplink!", "Mission Failed")
|
||||
return
|
||||
|
||||
uplink.ui_interact(usr)
|
||||
|
||||
/datum/antagonist/spy/proc/refresh_bounties()
|
||||
if(!check_rights(R_ADMIN|R_DEBUG))
|
||||
return
|
||||
|
||||
var/datum/component/spy_uplink/uplink = uplink_weakref?.resolve()
|
||||
if(isnull(uplink))
|
||||
tgui_alert(usr, "No spy uplink!", "Mission Failed")
|
||||
return
|
||||
|
||||
uplink.handler.force_refresh()
|
||||
tgui_alert(usr, "Bounties refreshed.", "Mission Success")
|
||||
|
||||
/datum/antagonist/spy/proc/admin_create_spy_uplink()
|
||||
if(!check_rights(R_ADMIN|R_DEBUG))
|
||||
return
|
||||
|
||||
if(!auto_create_spy_uplink(owner.current, give_backup = FALSE))
|
||||
tgui_alert(usr, "Failed to give [owner.current] a spy uplink - likely don't have a valid item to host it.", "Mission Failed")
|
||||
|
||||
/datum/antagonist/spy/proc/bounty_handler_vv()
|
||||
if(!check_rights(R_ADMIN|R_DEBUG))
|
||||
return
|
||||
|
||||
var/datum/component/spy_uplink/uplink = uplink_weakref?.resolve()
|
||||
if(isnull(uplink))
|
||||
tgui_alert(usr, "No spy uplink!", "Mission Failed")
|
||||
return
|
||||
|
||||
usr.client?.debug_variables(uplink.handler)
|
||||
|
||||
/datum/antagonist/spy/proc/auto_create_spy_uplink(mob/living/carbon/spy, give_backup = TRUE)
|
||||
if(!iscarbon(spy))
|
||||
return FALSE
|
||||
|
||||
var/spy_uplink_loc = spy.client?.prefs?.read_preference(/datum/preference/choiced/uplink_location)
|
||||
if(isnull(spy_uplink_loc) || spy_uplink_loc == UPLINK_IMPLANT)
|
||||
spy_uplink_loc = pick(UPLINK_PEN, UPLINK_PDA)
|
||||
|
||||
var/obj/item/spy_uplink = spy.get_uplink_location(spy_uplink_loc)
|
||||
if(isnull(spy_uplink) || !create_spy_uplink(spy, spy_uplink))
|
||||
if(give_backup)
|
||||
var/datum/action/backup_uplink/backup = new(src)
|
||||
backup.Grant(spy)
|
||||
to_chat(spy, span_boldnotice("You were unable to be supplied with an uplink, so you have been given the ability to create one yourself."))
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/antagonist/spy/proc/create_spy_uplink(mob/living/carbon/spy, obj/item/spy_uplink)
|
||||
var/datum/component/spy_uplink/uplink = spy_uplink.AddComponent(/datum/component/spy_uplink, src)
|
||||
if(!uplink)
|
||||
return FALSE
|
||||
|
||||
uplink_weakref = WEAKREF(uplink)
|
||||
uplink_created = TRUE
|
||||
|
||||
if(istype(spy_uplink, /obj/item/modular_computer/pda))
|
||||
uplink_location = "your PDA"
|
||||
|
||||
else if(istype(spy_uplink, /obj/item/pen))
|
||||
if(istype(spy_uplink.loc, /obj/item/modular_computer/pda))
|
||||
uplink_location = "your PDA's pen"
|
||||
else
|
||||
uplink_location = "a pen"
|
||||
|
||||
else if(istype(spy_uplink, /obj/item/radio))
|
||||
uplink_location = "your radio headset"
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/antagonist/spy/proc/give_random_objectives()
|
||||
for(var/i in 1 to rand(1, 3))
|
||||
var/datum/objective/custom/your_mission = new()
|
||||
your_mission.owner = owner
|
||||
your_mission.explanation_text = pick_list_replacements(SPY_OBJECTIVE_FILE, "objective_body")
|
||||
objectives += your_mission
|
||||
|
||||
if(prob(10))
|
||||
var/datum/objective/martyr/leave_no_trace = new()
|
||||
leave_no_trace.owner = owner
|
||||
objectives += leave_no_trace
|
||||
|
||||
else if(prob(3)) //3% chance on 90% chance
|
||||
var/datum/objective/hijack/steal_the_shuttle = new()
|
||||
steal_the_shuttle.owner = owner
|
||||
objectives += steal_the_shuttle
|
||||
|
||||
else
|
||||
var/datum/objective/escape/gtfo = new()
|
||||
gtfo.owner = owner
|
||||
objectives += gtfo
|
||||
|
||||
/datum/antagonist/spy/antag_panel_data()
|
||||
return "Bounties Claimed: [bounties_claimed]"
|
||||
|
||||
/datum/antagonist/spy/roundend_report()
|
||||
var/list/report = list()
|
||||
report += printplayer(owner)
|
||||
report += " - They completed <b>[bounties_claimed]</b> bounties."
|
||||
if(bounties_claimed > 0)
|
||||
report += " - They received the following rewards: [english_list(all_loot)]"
|
||||
report += printobjectives(objectives)
|
||||
return report.Join("<br>")
|
||||
|
||||
/datum/antagonist/spy/get_preview_icon()
|
||||
var/mob/living/carbon/human/dummy/consistent/dummy = new()
|
||||
dummy.set_haircolor(COLOR_SILVER, update = FALSE)
|
||||
dummy.set_hairstyle("CIA", update = FALSE)
|
||||
return finish_preview_icon(render_preview_outfit(preview_outfit, dummy))
|
||||
|
||||
/datum/outfit/spy
|
||||
name = "Spy (Preview only)"
|
||||
// Balaclava sprite is ass, otherwise I would use it for this
|
||||
uniform = /obj/item/clothing/under/suit/black
|
||||
gloves = /obj/item/clothing/gloves/color/black
|
||||
shoes = /obj/item/clothing/shoes/jackboots
|
||||
head = /obj/item/clothing/head/fedora
|
||||
suit = /obj/item/clothing/suit/jacket/trenchcoat
|
||||
glasses = /obj/item/clothing/glasses/osi
|
||||
ears = /obj/item/radio/headset
|
||||
|
||||
/datum/action/backup_uplink
|
||||
name = "Create Uplink"
|
||||
desc = "Fashion a PDA, Pen or Radio Headset into a swanky Spy Uplink."
|
||||
var/list/valid_types = list(
|
||||
/obj/item/modular_computer/pda,
|
||||
/obj/item/pen,
|
||||
/obj/item/radio,
|
||||
)
|
||||
|
||||
/datum/action/backup_uplink/New(Target)
|
||||
. = ..()
|
||||
if(!istype(Target, /datum/antagonist/spy))
|
||||
stack_trace("[type] created on invalid target [Target || "null"]")
|
||||
qdel(src)
|
||||
|
||||
/datum/action/backup_uplink/Trigger(trigger_flags)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
|
||||
var/mob/living/spy = usr
|
||||
var/obj/item/held_thing = spy.get_active_held_item()
|
||||
if(isnull(held_thing))
|
||||
spy.balloon_alert(spy, "you need to hold something!")
|
||||
return
|
||||
|
||||
if(!is_type_in_list(held_thing, valid_types))
|
||||
held_thing.balloon_alert(spy, "invalid item!")
|
||||
return
|
||||
|
||||
var/datum/antagonist/spy/spy_datum = target
|
||||
spy_datum.create_spy_uplink(spy, held_thing)
|
||||
held_thing.balloon_alert(spy, "uplink created")
|
||||
|
||||
qdel(src)
|
||||
@@ -0,0 +1,684 @@
|
||||
/**
|
||||
* ## Spy Bounty
|
||||
*
|
||||
* A datum used to track a single spy bounty.
|
||||
* Not a singleton - whenever bounties are re-rolled, the entire list is deleted and new bounty datums are instantiated.
|
||||
*
|
||||
* When bounties are completed, they are also not deleted, but instead marked as claimed.
|
||||
*/
|
||||
/datum/spy_bounty
|
||||
/// The name of the bounty.
|
||||
/// Should be a short description without punctuation.
|
||||
/// IE: "Steal the captain's ID"
|
||||
var/name
|
||||
/// Help text for the bounty.
|
||||
/// Should include additional information about the bounty to assist the spy in figuring out what to do.
|
||||
/// Should be punctuated.
|
||||
/// IE: "Steal the captain's ID. It was last seen in the captain's office."
|
||||
var/help
|
||||
/// Difficult of the bounty, one of [SPY_DIFFICULTY_EASY], [SPY_DIFFICULTY_MEDIUM], [SPY_DIFFICULTY_HARD].
|
||||
/// Must be set to one of the possible bounties to be picked.
|
||||
var/difficulty = "unset"
|
||||
/// How long of a do-after must be completed by the Spy to turn in the bounty.
|
||||
var/theft_time = 2 SECONDS
|
||||
/// Probability that the stolen item will be sent to the black market instead of destroyed.
|
||||
/// Guaranteed if the item is indestructible.
|
||||
var/black_market_prob = 50
|
||||
/// Weight that the bounty will be selected.
|
||||
var/weight = 1
|
||||
|
||||
/// Whether the bounty's been fully initialized. If this is not set, the bounty will be rerolled.
|
||||
VAR_FINAL/initalized = FALSE
|
||||
/// Whether the bounty has been completed.
|
||||
VAR_FINAL/claimed = FALSE
|
||||
/// What uplink item the bounty will reward on completion.
|
||||
VAR_FINAL/datum/uplink_item/reward_item
|
||||
|
||||
/datum/spy_bounty/New(datum/spy_bounty_handler/handler)
|
||||
if(!init_bounty(handler))
|
||||
return
|
||||
|
||||
initalized = TRUE
|
||||
select_reward(handler)
|
||||
|
||||
/// Helper that translates the bounty into UI data for TGUI
|
||||
/datum/spy_bounty/proc/to_ui_data(mob/user)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
return list(
|
||||
"name" = name,
|
||||
"help" = help,
|
||||
"difficulty" = difficulty,
|
||||
"reward" = reward_item.name,
|
||||
"claimed" = claimed,
|
||||
"can_claim" = can_claim(user),
|
||||
)
|
||||
|
||||
/// Check if the passed mob can claim this bounty.
|
||||
/datum/spy_bounty/proc/can_claim(mob/user)
|
||||
SHOULD_BE_PURE(TRUE)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Initializes the bounty, setting up targets and etc.
|
||||
*
|
||||
* * handler - The bounty handler that is creating this bounty.
|
||||
*
|
||||
* Returning FALSE will cancel initialization and force it to reroll the bounty.
|
||||
*/
|
||||
/datum/spy_bounty/proc/init_bounty(datum/spy_bounty_handler/handler)
|
||||
return FALSE
|
||||
|
||||
/// Selects what uplink item the bounty will reward on completion.
|
||||
/datum/spy_bounty/proc/select_reward(datum/spy_bounty_handler/handler)
|
||||
var/list/loot_pool = handler.possible_uplink_items[difficulty]
|
||||
|
||||
if(!length(loot_pool))
|
||||
reward_item = /datum/uplink_item/bundles_tc/telecrystal
|
||||
return // future todo : add some junk items for when we run out of items
|
||||
|
||||
reward_item = pick(loot_pool)
|
||||
if(prob(80))
|
||||
loot_pool -= reward_item
|
||||
|
||||
/**
|
||||
* Checks if the passed movable is a valid target for this bounty.
|
||||
*
|
||||
* * stealing - The movable to check.
|
||||
*
|
||||
* Returning FALSE simply means that the passed movable is not valid for this bounty.
|
||||
*/
|
||||
/datum/spy_bounty/proc/is_stealable(atom/movable/stealing)
|
||||
// SHOULD_BE_PURE(TRUE)
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* What is this bounty's "dupe protection key"?
|
||||
* This is used to determine if a duplicate of this bounty has been rolled before / in the last refresh.
|
||||
* You can check if a bounty has been duped by accessing the handler's claimed_bounties_from_last_pool or all_claimed_bounty_types list.
|
||||
*
|
||||
* * stealing - The item that was stolen.
|
||||
* * handler - The handler that is handling the bounty.
|
||||
*
|
||||
* Return a string key, what this uses for dupe protection.
|
||||
*/
|
||||
/datum/spy_bounty/proc/get_dupe_protection_key(atom/movable/stealing)
|
||||
return stealing.type
|
||||
|
||||
/**
|
||||
* Checks if the passed dupe key is a duplicate of an previously claimed bounty.
|
||||
*
|
||||
* * handler - The handler that is handling the bounty.
|
||||
* * dupe_key - The key to check for dupes
|
||||
* * dupe_prob - The probability of a dupe being allowed when checking all_claimed_bounty_types.
|
||||
* This allows you to have a chance that distant dupes allowed depending on how many times they've been done.
|
||||
*
|
||||
* Returns TRUE if the bounty is a dupe, FALSE if it is not.
|
||||
*/
|
||||
/datum/spy_bounty/proc/check_dupe(datum/spy_bounty_handler/handler, dupe_key, dupe_prob = 0)
|
||||
if(handler.claimed_bounties_from_last_pool[dupe_key])
|
||||
return TRUE
|
||||
if(prob(dupe_prob * handler.all_claimed_bounty_types[dupe_key]))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Called when the bounty is completed, to handle how the stolen item is "stolen".
|
||||
*
|
||||
* By default, stolen items are simply deleted.
|
||||
*
|
||||
* * stealing - The item that was stolen.
|
||||
* * spy - The spy that stole the item.
|
||||
*/
|
||||
/datum/spy_bounty/proc/clean_up_stolen_item(atom/movable/stealing, mob/living/spy)
|
||||
do_sparks(3, FALSE, stealing)
|
||||
|
||||
// Don't mess with it while it's going away
|
||||
stealing.interaction_flags_atom &= ~INTERACT_ATOM_ATTACK_HAND
|
||||
stealing.anchored = TRUE
|
||||
// Add some pizzazz
|
||||
animate(stealing, time = 0.5 SECONDS, transform = matrix(stealing.transform).Scale(0.01), easing = CUBIC_EASING)
|
||||
|
||||
if(isitem(stealing) && ((stealing.resistance_flags & INDESTRUCTIBLE) || prob(black_market_prob)))
|
||||
addtimer(CALLBACK(src, PROC_REF(send_to_black_market), stealing), 0.5 SECONDS)
|
||||
else
|
||||
QDEL_IN(stealing, 0.5 SECONDS)
|
||||
|
||||
/**
|
||||
* Handles putting the passed movable up on the black market.
|
||||
*
|
||||
* By the end of this proc, the item should either be deleted (if failure) or in nullspace (on the black market).
|
||||
*
|
||||
* * thing - The item to put up on the black market.
|
||||
*/
|
||||
/datum/spy_bounty/proc/send_to_black_market(atom/movable/thing)
|
||||
if(QDELETED(thing)) // Just in case anything does anything weird
|
||||
return FALSE
|
||||
|
||||
thing.interaction_flags_atom = initial(thing.interaction_flags_atom)
|
||||
thing.anchored = initial(thing.anchored)
|
||||
thing.moveToNullspace()
|
||||
|
||||
var/datum/market_item/new_item = new()
|
||||
new_item.item = thing
|
||||
new_item.name = "Stolen [thing.name]"
|
||||
new_item.desc = "A [thing.name], stolen from somewhere on the station. Whoever owned it probably wouldn't be happy to see it here."
|
||||
new_item.category = "Fenced Goods"
|
||||
new_item.stock = 1
|
||||
new_item.availability_prob = 100
|
||||
|
||||
switch(difficulty)
|
||||
if(SPY_DIFFICULTY_EASY)
|
||||
new_item.price = PAYCHECK_COMMAND * 2.5
|
||||
if(SPY_DIFFICULTY_MEDIUM)
|
||||
new_item.price = PAYCHECK_COMMAND * 5
|
||||
if(SPY_DIFFICULTY_HARD)
|
||||
new_item.price = PAYCHECK_COMMAND * 10
|
||||
|
||||
new_item.price += rand(0, PAYCHECK_COMMAND * 5)
|
||||
if(thing.resistance_flags & INDESTRUCTIBLE)
|
||||
new_item.price *= 2
|
||||
|
||||
return SSblackmarket.markets[/datum/market/blackmarket].add_item(new_item)
|
||||
|
||||
/// Steal an item
|
||||
/datum/spy_bounty/objective_item
|
||||
/// Reference to an objective item datum that we want stolen.
|
||||
VAR_FINAL/datum/objective_item/desired_item
|
||||
/// Typecache of objective items that should not be selected.
|
||||
var/static/list/blacklisted_item_types = typecacheof(list(
|
||||
/datum/objective_item/steal/functionalai,
|
||||
/datum/objective_item/steal/nukedisc,
|
||||
))
|
||||
|
||||
/datum/spy_bounty/objective_item/can_claim(mob/user)
|
||||
return !(user.mind?.assigned_role.title in desired_item.excludefromjob)
|
||||
|
||||
/datum/spy_bounty/objective_item/get_dupe_protection_key(atom/movable/stealing)
|
||||
return desired_item.targetitem
|
||||
|
||||
/// Determines if the passed objective item is a reasonable, valid theft target.
|
||||
/datum/spy_bounty/objective_item/proc/is_valid_objective_item(datum/objective_item/item)
|
||||
if(length(item.special_equipment) || item.difficulty <= 0 || item.difficulty >= 6)
|
||||
return FALSE
|
||||
if(is_type_in_typecache(item, blacklisted_item_types))
|
||||
return FALSE
|
||||
if(!item.exists_on_map)
|
||||
return TRUE
|
||||
var/list/all_valid_existing_things = list()
|
||||
for(var/obj/item/existing_thing as anything in GLOB.steal_item_handler.objectives_by_path[item.targetitem])
|
||||
var/turf/thing_turf = get_turf(existing_thing)
|
||||
if(isnull(thing_turf)) // nullspaced likely means it was stolen and is in the black market.
|
||||
continue
|
||||
if(!is_station_level(thing_turf.z) && !is_mining_level(thing_turf.z))
|
||||
continue
|
||||
all_valid_existing_things += existing_thing
|
||||
|
||||
if(!length(all_valid_existing_things))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/spy_bounty/objective_item/init_bounty(datum/spy_bounty_handler/handler)
|
||||
var/list/valid_possible_items = list()
|
||||
for(var/datum/objective_item/item as anything in GLOB.possible_items)
|
||||
if(check_dupe(handler, item.targetitem, 33))
|
||||
continue
|
||||
if(!is_valid_objective_item(item))
|
||||
continue
|
||||
// Determine difficulty. Has some overlap between the categories, which is OK
|
||||
switch(difficulty)
|
||||
if(SPY_DIFFICULTY_EASY)
|
||||
if(item.difficulty >= 3)
|
||||
continue
|
||||
if(SPY_DIFFICULTY_MEDIUM)
|
||||
if(item.difficulty <= 2 || item.difficulty >= 5)
|
||||
continue
|
||||
if(SPY_DIFFICULTY_HARD)
|
||||
if(item.difficulty <= 3)
|
||||
continue
|
||||
|
||||
valid_possible_items += item
|
||||
|
||||
for(var/datum/spy_bounty/objective_item/existing_bounty in handler.get_all_bounties())
|
||||
valid_possible_items -= existing_bounty.desired_item
|
||||
|
||||
if(!length(valid_possible_items))
|
||||
return FALSE
|
||||
|
||||
desired_item = pick(valid_possible_items)
|
||||
// We need to do some snowflake for items that do exist vs generic items
|
||||
var/list/obj/item/existing_items = GLOB.steal_item_handler.objectives_by_path[desired_item.targetitem]
|
||||
var/obj/item/the_item = length(existing_items) ? pick(existing_items) : desired_item.targetitem
|
||||
var/the_item_name = istype(the_item) ? the_item.name : initial(the_item.name)
|
||||
name = "[the_item_name] [difficulty == SPY_DIFFICULTY_HARD ? "Grand ":""]Theft"
|
||||
help = "Steal any [the_item_name][desired_item.steal_hint ? ": [desired_item.steal_hint]" : "."]"
|
||||
return TRUE
|
||||
|
||||
/datum/spy_bounty/objective_item/is_stealable(atom/movable/stealing)
|
||||
return istype(stealing, desired_item.targetitem) && desired_item.check_special_completion(stealing)
|
||||
|
||||
/datum/spy_bounty/objective_item/random_easy
|
||||
difficulty = SPY_DIFFICULTY_EASY
|
||||
weight = 4 // Increased due to there being many easy options
|
||||
|
||||
/datum/spy_bounty/objective_item/random_medium
|
||||
difficulty = SPY_DIFFICULTY_MEDIUM
|
||||
weight = 2 // Increased due to there being many medium options
|
||||
|
||||
/datum/spy_bounty/objective_item/random_hard
|
||||
difficulty = SPY_DIFFICULTY_HARD
|
||||
|
||||
/datum/spy_bounty/machine
|
||||
theft_time = 10 SECONDS
|
||||
|
||||
/// What machine (typepath) we want to steal.
|
||||
var/obj/machinery/target_type
|
||||
/// What area (typepath) the desired machine is in.
|
||||
/// Can be pre-set for subtypes. If set, requires the machine to be in the location_type.
|
||||
/// If not set, picks a random machine from all areas it can currently be found in.
|
||||
var/area/location_type
|
||||
/// List of weakrefs to all machines of the target type when the bounty was initialized.
|
||||
var/list/original_options_weakrefs = list()
|
||||
|
||||
/datum/spy_bounty/machine/Destroy()
|
||||
original_options_weakrefs.Cut() // Just in case
|
||||
return ..()
|
||||
|
||||
/datum/spy_bounty/machine/get_dupe_protection_key(atom/movable/stealing)
|
||||
return target_type
|
||||
|
||||
/datum/spy_bounty/machine/send_to_black_market(obj/machinery/thing)
|
||||
if(!istype(thing.circuit, /obj/item/circuitboard))
|
||||
qdel(thing)
|
||||
return FALSE
|
||||
|
||||
var/obj/item/circuitboard/selling = thing.circuit
|
||||
var/turf/machine_turf = get_turf(thing)
|
||||
|
||||
// Sell the circuitboard, take the rest apart
|
||||
// This (should) handle any mobs inside as well
|
||||
thing.deconstruct(FALSE)
|
||||
if(!..(selling))
|
||||
return FALSE
|
||||
|
||||
// Clean up leftover parts from deconstruction
|
||||
for(var/obj/structure/frame/leftover in machine_turf)
|
||||
qdel(leftover)
|
||||
break
|
||||
for(var/obj/item/stock_parts/part in machine_turf)
|
||||
if(prob(part.rating * 20))
|
||||
continue
|
||||
qdel(part)
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/spy_bounty/machine/init_bounty(datum/spy_bounty_handler/handler)
|
||||
if(isnull(target_type))
|
||||
return FALSE
|
||||
|
||||
// Blacklisting maintenance in general, as well as any areas that already have a bounty in them.
|
||||
var/list/blacklisted_areas = typecacheof(/area/station/maintenance)
|
||||
for(var/datum/spy_bounty/machine/existing_bounty in handler.get_all_bounties())
|
||||
blacklisted_areas[existing_bounty.location_type] = TRUE
|
||||
|
||||
var/list/obj/machinery/all_possible = list()
|
||||
for(var/obj/machinery/found_machine as anything in SSmachines.get_machines_by_type_and_subtypes(target_type))
|
||||
if(!is_station_level(found_machine.z) && !is_mining_level(found_machine.z))
|
||||
continue
|
||||
var/area/found_machine_area = get_area(found_machine)
|
||||
if(is_type_in_typecache(found_machine_area, blacklisted_areas))
|
||||
continue
|
||||
if(!isnull(location_type) && !istype(found_machine_area, location_type))
|
||||
continue
|
||||
if(!(found_machine_area.area_flags & VALID_TERRITORY)) // only steal from valid station areas
|
||||
continue
|
||||
all_possible += found_machine
|
||||
|
||||
if(!length(all_possible))
|
||||
return FALSE
|
||||
|
||||
var/obj/machinery/machine = pick_n_take(all_possible)
|
||||
var/area/machine_area = get_area(machine)
|
||||
// Tracks the picked machine, as well as any other machines in the same area
|
||||
// (So they can be removed from the room but still count, for clever Spies)
|
||||
original_options_weakrefs += WEAKREF(machine)
|
||||
for(var/obj/machinery/other_machine as anything in all_possible)
|
||||
if(get_area(other_machine) == machine_area)
|
||||
original_options_weakrefs += WEAKREF(other_machine)
|
||||
|
||||
location_type = machine_area.type
|
||||
name ||= "[machine.name] Burglary"
|
||||
help ||= "Steal \a [machine] found in [machine_area]."
|
||||
return TRUE
|
||||
|
||||
/datum/spy_bounty/machine/is_stealable(atom/movable/stealing)
|
||||
if(!istype(stealing, target_type))
|
||||
return FALSE
|
||||
if(WEAKREF(stealing) in original_options_weakrefs)
|
||||
return TRUE
|
||||
if(istype(get_area(stealing), location_type))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/datum/spy_bounty/machine/random
|
||||
/// List of all machines we can randomly draw from
|
||||
var/list/random_options = list()
|
||||
|
||||
/datum/spy_bounty/machine/random/init_bounty(datum/spy_bounty_handler/handler)
|
||||
var/list/options = random_options.Copy()
|
||||
for(var/datum/spy_bounty/machine/existing_bounty in handler.get_all_bounties())
|
||||
options -= existing_bounty.target_type
|
||||
|
||||
for(var/remaining_option in options)
|
||||
if(check_dupe(handler, remaining_option, 33))
|
||||
options -= remaining_option
|
||||
|
||||
if(!length(options))
|
||||
return FALSE
|
||||
|
||||
target_type = pick(options)
|
||||
return ..()
|
||||
|
||||
/datum/spy_bounty/machine/random/easy
|
||||
difficulty = SPY_DIFFICULTY_EASY
|
||||
weight = 4 // Increased due to there being many easy options
|
||||
random_options = list(
|
||||
/obj/machinery/computer/operating,
|
||||
/obj/machinery/computer/order_console/mining,
|
||||
/obj/machinery/computer/records/medical,
|
||||
/obj/machinery/cryo_cell,
|
||||
/obj/machinery/fax, // Completely random, wild card
|
||||
/obj/machinery/hydroponics/constructable,
|
||||
/obj/machinery/medical_kiosk,
|
||||
/obj/machinery/microwave,
|
||||
/obj/machinery/oven,
|
||||
/obj/machinery/recharge_station,
|
||||
/obj/machinery/vending/boozeomat,
|
||||
/obj/machinery/vending/medical,
|
||||
/obj/machinery/vending/wardrobe,
|
||||
)
|
||||
|
||||
/datum/spy_bounty/machine/random/medium
|
||||
difficulty = SPY_DIFFICULTY_MEDIUM
|
||||
weight = 4 // Increased due to there being many medium options
|
||||
random_options = list(
|
||||
/obj/machinery/chem_dispenser,
|
||||
/obj/machinery/computer/bank_machine,
|
||||
/obj/machinery/computer/camera_advanced/xenobio,
|
||||
/obj/machinery/computer/cargo, // This includes request-only ones in the public lobby
|
||||
/obj/machinery/computer/crew,
|
||||
/obj/machinery/computer/prisoner/management,
|
||||
/obj/machinery/computer/rdconsole,
|
||||
/obj/machinery/computer/records/security,
|
||||
/obj/machinery/computer/scan_consolenew,
|
||||
/obj/machinery/computer/security, // Requires breaking into a sec checkpoint, but not too hard, many are never visited
|
||||
/obj/machinery/dna_scannernew,
|
||||
/obj/machinery/mecha_part_fabricator,
|
||||
)
|
||||
|
||||
/datum/spy_bounty/machine/engineering_emitter
|
||||
difficulty = SPY_DIFFICULTY_MEDIUM
|
||||
target_type = /obj/machinery/power/emitter
|
||||
location_type = /area/station/engineering/supermatter/
|
||||
|
||||
/datum/spy_bounty/machine/engineering_emitter/can_claim(mob/user)
|
||||
return !(user.mind?.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_ENGINEERING)
|
||||
|
||||
/datum/spy_bounty/machine/random/hard
|
||||
difficulty = SPY_DIFFICULTY_HARD
|
||||
random_options = list(
|
||||
/obj/machinery/computer/accounting,
|
||||
/obj/machinery/computer/communications,
|
||||
/obj/machinery/computer/upload,
|
||||
/obj/machinery/modular_computer/preset/id,
|
||||
)
|
||||
|
||||
/datum/spy_bounty/machine/random/hard/can_claim(mob/user) // These would all be too easy with command level access
|
||||
return !(user.mind?.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND)
|
||||
|
||||
/datum/spy_bounty/machine/random/hard/ai_sat_teleporter
|
||||
random_options = list(
|
||||
/obj/machinery/teleport,
|
||||
/obj/machinery/computer/teleporter.
|
||||
)
|
||||
location_type = /area/station/ai_monitored/aisat
|
||||
|
||||
/// Subtype for a bounty that targets a specific crew member
|
||||
/datum/spy_bounty/targets_person
|
||||
difficulty = SPY_DIFFICULTY_HARD
|
||||
theft_time = 12 SECONDS
|
||||
/// Weakref to the mob target of the bounty
|
||||
VAR_FINAL/datum/weakref/target_ref
|
||||
|
||||
/datum/spy_bounty/targets_person/get_dupe_protection_key(atom/movable/stealing)
|
||||
// Prevents the same player from being selected twice, but if they're straight up gone, whatever
|
||||
return REF(target_ref.resolve() || stealing)
|
||||
|
||||
/datum/spy_bounty/targets_person/can_claim(mob/user)
|
||||
return !IS_WEAKREF_OF(user, target_ref)
|
||||
|
||||
/datum/spy_bounty/targets_person/init_bounty(datum/spy_bounty_handler/handler)
|
||||
var/list/mob/possible_targets = list()
|
||||
for(var/datum/mind/crew_mind as anything in get_crewmember_minds())
|
||||
var/mob/living/real_target = crew_mind.current
|
||||
// Ideally we want it to be a player, but we don't care if they DC after being selected
|
||||
if(!istype(real_target) || isnull(GET_CLIENT(real_target)))
|
||||
continue
|
||||
if(check_dupe(handler, REF(real_target), 50))
|
||||
continue
|
||||
if(!is_valid_crewmember(real_target))
|
||||
continue
|
||||
possible_targets += real_target
|
||||
|
||||
for(var/datum/spy_bounty/targets_person/existing_bounty in handler.get_all_bounties())
|
||||
possible_targets -= existing_bounty.target_ref.resolve()
|
||||
|
||||
if(!length(possible_targets))
|
||||
return FALSE
|
||||
|
||||
var/mob/picked = pick(possible_targets)
|
||||
if(target_found(picked))
|
||||
target_ref = WEAKREF(picked)
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Ran on every single member of the crew to determine if they are a valid target.
|
||||
*
|
||||
* * crewmember - The person to check.
|
||||
*
|
||||
* Returning FALSE will exclude them from the list of possible targets.
|
||||
*/
|
||||
/datum/spy_bounty/targets_person/proc/is_valid_crewmember(mob/crewmember)
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Ran when a valid target is selected for the bounty.
|
||||
*
|
||||
* * crewmember - The person that was selected as the target.
|
||||
*
|
||||
* Returning FALSE will stop the bounty from being finalized, this can be used for last minute checks.
|
||||
*/
|
||||
/datum/spy_bounty/targets_person/proc/target_found(mob/crewmember)
|
||||
return FALSE
|
||||
|
||||
/// Subtype for a bounty that targets a specific crew member and a specific item on them
|
||||
/datum/spy_bounty/targets_person/some_item
|
||||
/// Typepath of the item we want from the target
|
||||
var/obj/item/desired_type
|
||||
/// Weakref to the item that matches our desired type within the target at the time of bounty creation
|
||||
VAR_FINAL/datum/weakref/target_original_desired_ref
|
||||
|
||||
/datum/spy_bounty/targets_person/some_item/is_valid_crewmember(mob/living/carbon/human/crewmember)
|
||||
return istype(crewmember) && find_desired_thing(crewmember)
|
||||
|
||||
/datum/spy_bounty/targets_person/some_item/is_stealable(atom/movable/stealing)
|
||||
if(IS_WEAKREF_OF(stealing, target_original_desired_ref))
|
||||
return TRUE
|
||||
if(IS_WEAKREF_OF(stealing, target_ref))
|
||||
var/mob/living/carbon/human/target = stealing
|
||||
if(!target.incapacitated(IGNORE_RESTRAINTS|IGNORE_STASIS))
|
||||
return FALSE
|
||||
if(find_desired_thing(target))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/datum/spy_bounty/targets_person/some_item/clean_up_stolen_item(atom/movable/stealing, mob/living/spy)
|
||||
if(IS_WEAKREF_OF(stealing, target_original_desired_ref))
|
||||
return ..()
|
||||
|
||||
ASSERT(ishuman(stealing), "[type] called clean_up_stolen_item with something that isn't a human and isn't the original item.")
|
||||
|
||||
do_sparks(2, FALSE, stealing)
|
||||
var/mob/living/carbon/human/stolen_from = stealing
|
||||
var/obj/item/real_stolen_item = find_desired_thing(stealing)
|
||||
stolen_from.Unconscious(10 SECONDS)
|
||||
to_chat(stolen_from, span_warning("You feel something missing where your [real_stolen_item.name] once was."))
|
||||
return ..(real_stolen_item, spy)
|
||||
|
||||
/datum/spy_bounty/targets_person/some_item/target_found(mob/crewmember)
|
||||
var/obj/item/desired_thing = find_desired_thing(crewmember)
|
||||
target_original_desired_ref = WEAKREF(desired_thing)
|
||||
name = "[crewmember.real_name]'s [desired_thing.name]"
|
||||
help = "Steal [desired_thing] from [crewmember.real_name]. \
|
||||
You can accomplish this via brute force, or by scanning them with your uplink while they are incapacitated."
|
||||
return TRUE
|
||||
|
||||
/// Finds the desired item type in the target crewmember.
|
||||
/datum/spy_bounty/targets_person/some_item/proc/find_desired_thing(mob/living/carbon/human/crewmember)
|
||||
return locate(desired_type) in crewmember.get_all_gear()
|
||||
|
||||
// Steal someone's ID card
|
||||
/datum/spy_bounty/targets_person/some_item/id
|
||||
desired_type = /obj/item/card/id/advanced
|
||||
|
||||
/datum/spy_bounty/targets_person/some_item/id/find_desired_thing(mob/living/carbon/human/crewmember)
|
||||
for(var/obj/item/card/id/advanced/id in crewmember.get_all_gear())
|
||||
if(id.registered_account?.account_id == crewmember.account_id)
|
||||
return id
|
||||
|
||||
return null
|
||||
|
||||
/datum/spy_bounty/targets_person/some_item/id/target_found(mob/crewmember)
|
||||
. = ..()
|
||||
name = "[crewmember.real_name]'s ID Card"
|
||||
|
||||
// Steal someone's PDA
|
||||
/datum/spy_bounty/targets_person/some_item/pda
|
||||
desired_type = /obj/item/modular_computer/pda
|
||||
|
||||
/datum/spy_bounty/targets_person/some_item/pda/find_desired_thing(mob/living/carbon/human/crewmember)
|
||||
for(var/obj/item/modular_computer/pda/pda in crewmember.get_all_gear())
|
||||
if(pda.saved_identification == crewmember.real_name)
|
||||
return pda
|
||||
|
||||
return null
|
||||
|
||||
/datum/spy_bounty/targets_person/some_item/pda/target_found(mob/crewmember)
|
||||
. = ..()
|
||||
name = "[crewmember.real_name]'s PDA"
|
||||
|
||||
// Steal someone's heirloom
|
||||
/datum/spy_bounty/targets_person/some_item/heirloom
|
||||
desired_type = /obj/item
|
||||
black_market_prob = 100
|
||||
|
||||
/datum/spy_bounty/targets_person/some_item/heirloom/find_desired_thing(mob/living/crewmember)
|
||||
var/datum/quirk/item_quirk/family_heirloom/quirk = crewmember.get_quirk(/datum/quirk/item_quirk/family_heirloom)
|
||||
return quirk?.heirloom?.resolve()
|
||||
|
||||
/datum/spy_bounty/targets_person/some_item/heirloom/target_found(mob/crewmember)
|
||||
. = ..()
|
||||
name = "[crewmember.real_name]'s heirloom"
|
||||
|
||||
// Steal a limb or organ off someone
|
||||
/datum/spy_bounty/targets_person/some_item/limb_or_organ
|
||||
weight = 4 // lots to pick from here
|
||||
|
||||
/datum/spy_bounty/targets_person/some_item/limb_or_organ/init_bounty(datum/spy_bounty_handler/handler)
|
||||
desired_type = pick(
|
||||
/obj/item/bodypart/arm/left,
|
||||
/obj/item/bodypart/arm/right,
|
||||
/obj/item/bodypart/leg/left,
|
||||
/obj/item/bodypart/leg/right,
|
||||
/obj/item/organ/internal/stomach,
|
||||
/obj/item/organ/internal/appendix,
|
||||
/obj/item/organ/internal/liver,
|
||||
/obj/item/organ/internal/eyes,
|
||||
)
|
||||
return ..()
|
||||
|
||||
/datum/spy_bounty/targets_person/some_item/limb_or_organ/find_desired_thing(mob/living/carbon/human/crewmember)
|
||||
if(ispath(desired_type, /obj/item/bodypart))
|
||||
return locate(desired_type) in crewmember.bodyparts
|
||||
if(ispath(desired_type, /obj/item/organ))
|
||||
return locate(desired_type) in crewmember.organs
|
||||
return null
|
||||
|
||||
/datum/spy_bounty/some_bot
|
||||
theft_time = 10 SECONDS
|
||||
black_market_prob = 0
|
||||
/// What typepath of bot we want to steal.
|
||||
var/mob/living/simple_animal/bot/bot_type
|
||||
/// Weakref to the bot we want to steal.
|
||||
VAR_FINAL/datum/weakref/target_bot_ref
|
||||
|
||||
/datum/spy_bounty/some_bot/get_dupe_protection_key(atom/movable/stealing)
|
||||
return bot_type
|
||||
|
||||
/datum/spy_bounty/some_bot/init_bounty(datum/spy_bounty_handler/handler)
|
||||
for(var/datum/spy_bounty/some_bot/existing_bounty in handler.get_all_bounties())
|
||||
var/mob/living/simple_animal/bot/existing_bot_type = existing_bounty.bot_type
|
||||
// ensures we don't get two similar bounties.
|
||||
// may occasionally cast a wider net than we'd desire, but it's not that bad.
|
||||
if(ispath(bot_type, initial(existing_bot_type.parent_type)))
|
||||
return FALSE
|
||||
|
||||
var/list/mob/living/possible_bots = list()
|
||||
for(var/mob/living/bot as anything in GLOB.bots_list)
|
||||
if(!is_station_level(bot.z) && !is_mining_level(bot.z))
|
||||
continue
|
||||
if(!istype(bot, bot_type))
|
||||
continue
|
||||
possible_bots += bot
|
||||
|
||||
if(!length(possible_bots))
|
||||
return FALSE
|
||||
|
||||
var/mob/living/picked = pick(possible_bots)
|
||||
target_bot_ref = WEAKREF(picked)
|
||||
name ||= "[picked.name] Abduction"
|
||||
help ||= "Abduct the station's robot assistant [picked.name]."
|
||||
return TRUE
|
||||
|
||||
/datum/spy_bounty/some_bot/is_stealable(atom/movable/stealing)
|
||||
return IS_WEAKREF_OF(stealing, target_bot_ref)
|
||||
|
||||
/datum/spy_bounty/some_bot/beepsky
|
||||
difficulty = SPY_DIFFICULTY_MEDIUM // gotta get him to stand still
|
||||
bot_type = /mob/living/simple_animal/bot/secbot/beepsky/officer
|
||||
help = "Abduct Officer Beepsky - commonly found patrolling the station. \
|
||||
Watch out, they may not take kindly to being scanned."
|
||||
|
||||
/datum/spy_bounty/some_bot/ofitser
|
||||
difficulty = SPY_DIFFICULTY_EASY
|
||||
bot_type = /mob/living/simple_animal/bot/secbot/beepsky/ofitser
|
||||
help = "Abduct Prison Ofitser - commonly found guarding the Gulag."
|
||||
|
||||
/datum/spy_bounty/some_bot/armsky
|
||||
difficulty = SPY_DIFFICULTY_HARD
|
||||
bot_type = /mob/living/simple_animal/bot/secbot/beepsky/armsky
|
||||
help = "Abduct Sergeant-At-Armsky - commonly found guarding the station's Armory."
|
||||
|
||||
/datum/spy_bounty/some_bot/pingsky
|
||||
difficulty = SPY_DIFFICULTY_HARD
|
||||
bot_type = /mob/living/simple_animal/bot/secbot/pingsky
|
||||
help = "Abduct Officer Pingsky - commonly found protecting the station's AI."
|
||||
|
||||
/datum/spy_bounty/some_bot/scrubbs
|
||||
difficulty = SPY_DIFFICULTY_EASY
|
||||
bot_type = /mob/living/basic/bot/cleanbot/medbay
|
||||
help = "Abduct Scrubbs, MD - commonly found mopping up blood in Medbay."
|
||||
|
||||
/datum/spy_bounty/some_bot/scrubbs/can_claim(mob/user)
|
||||
return !(user.mind?.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_MEDICAL)
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* ## Spy bounty handler
|
||||
*
|
||||
* Singleton datum that handles determining active bounties for spies.
|
||||
*/
|
||||
/datum/spy_bounty_handler
|
||||
/// Timer between when all bounties are refreshed.
|
||||
var/refresh_time = 12 MINUTES
|
||||
/// timerID of the active refresh timer.
|
||||
var/refresh_timer
|
||||
/// Number of times we have refreshed bounties
|
||||
var/num_refreshes = 0
|
||||
/// Assoc list of items stolen in the past to how many times they have been stolen
|
||||
/// Sometimes item typepaths, sometimes REFs, in general just strings that represent stolen items
|
||||
var/list/all_claimed_bounty_types = list()
|
||||
/// List of all items stolen in the last pool of bounties.
|
||||
/// Same as above - strings that represent stolen items.
|
||||
var/list/claimed_bounties_from_last_pool = list()
|
||||
/// Override for the number of attempts to make a bounty.
|
||||
var/num_attempts_override = 0
|
||||
|
||||
/// Assoc list that dictates how much of each bounty difficulty to give out at once.
|
||||
/// Modified by the number of times we have refreshed bounties.
|
||||
VAR_PRIVATE/list/base_bounties_to_give = list(
|
||||
SPY_DIFFICULTY_EASY = 4,
|
||||
SPY_DIFFICULTY_MEDIUM = 2,
|
||||
SPY_DIFFICULTY_HARD = 2,
|
||||
)
|
||||
|
||||
/// Assoc list of all active bounties.
|
||||
VAR_PRIVATE/list/list/bounties = list(
|
||||
SPY_DIFFICULTY_EASY = list(),
|
||||
SPY_DIFFICULTY_MEDIUM = list(),
|
||||
SPY_DIFFICULTY_HARD = list(),
|
||||
)
|
||||
|
||||
/// Assoc list of all possible bounties for each difficulty, weighted.
|
||||
/// This is static, no bounty types are removed from this list.
|
||||
VAR_PRIVATE/list/list/bounty_types = list(
|
||||
SPY_DIFFICULTY_EASY = list(),
|
||||
SPY_DIFFICULTY_MEDIUM = list(),
|
||||
SPY_DIFFICULTY_HARD = list(),
|
||||
)
|
||||
|
||||
/// Assoc list of all uplink items possible to be given as bounties for each difficulty.
|
||||
/// This is not static, as bounties are complete uplink items will be removed from this list.
|
||||
var/list/list/possible_uplink_items = list(
|
||||
SPY_DIFFICULTY_EASY = list(),
|
||||
SPY_DIFFICULTY_MEDIUM = list(),
|
||||
SPY_DIFFICULTY_HARD = list(),
|
||||
)
|
||||
|
||||
/datum/spy_bounty_handler/New()
|
||||
for(var/datum/spy_bounty/bounty as anything in subtypesof(/datum/spy_bounty))
|
||||
var/weight = initial(bounty.weight)
|
||||
var/difficulty = initial(bounty.difficulty)
|
||||
if(weight <= 0 || !islist(bounty_types[difficulty]))
|
||||
continue
|
||||
bounty_types[difficulty][bounty] = weight
|
||||
|
||||
for(var/datum/uplink_item/item as anything in SStraitor.uplink_items)
|
||||
if(isnull(item.item) || item.item == ABSTRACT_UPLINK_ITEM)
|
||||
continue
|
||||
if(!(item.purchasable_from & UPLINK_SPY))
|
||||
continue
|
||||
// This will have some overlap, and that's intentional -
|
||||
// Adds some variety, rare moments where you can get a hard reward for an easier bounty (or visa versa)
|
||||
if(item.cost <= SPY_LOWER_COST_THRESHOLD)
|
||||
possible_uplink_items[SPY_DIFFICULTY_EASY] += item
|
||||
if(item.cost >= SPY_LOWER_COST_THRESHOLD && item.cost <= SPY_UPPER_COST_THRESHOLD)
|
||||
possible_uplink_items[SPY_DIFFICULTY_MEDIUM] += item
|
||||
if(item.cost >= SPY_UPPER_COST_THRESHOLD)
|
||||
possible_uplink_items[SPY_DIFFICULTY_HARD] += item
|
||||
|
||||
refresh_bounty_list()
|
||||
|
||||
/// Helper that returns a list of all active bounties in a single list, regardless of difficulty.
|
||||
/datum/spy_bounty_handler/proc/get_all_bounties() as /list
|
||||
var/list/all_bounties = list()
|
||||
for(var/difficulty in bounties)
|
||||
all_bounties += bounties[difficulty]
|
||||
|
||||
return all_bounties
|
||||
|
||||
/// Refreshes all active bounties for each difficulty, no matter if they were complete or not.
|
||||
/// Then recursively calls itself via a timer.
|
||||
/datum/spy_bounty_handler/proc/refresh_bounty_list()
|
||||
PRIVATE_PROC(TRUE)
|
||||
|
||||
var/list/bounties_to_give = base_bounties_to_give.Copy()
|
||||
|
||||
if(num_refreshes < base_bounties_to_give[SPY_DIFFICULTY_HARD])
|
||||
bounties_to_give[SPY_DIFFICULTY_HARD] = num_refreshes
|
||||
bounties_to_give[SPY_DIFFICULTY_MEDIUM] += (base_bounties_to_give[SPY_DIFFICULTY_HARD] - num_refreshes)
|
||||
|
||||
for(var/difficulty in bounties)
|
||||
QDEL_LIST(bounties[difficulty])
|
||||
|
||||
var/list/pool = bounty_types[difficulty]
|
||||
var/amount_to_give = bounties_to_give[difficulty]
|
||||
var/failed_attempts = num_attempts_override || clamp(amount_to_give * 4, 10, 25) // more potential bounties = more attempts to make one
|
||||
while(amount_to_give > 0 && failed_attempts > 0)
|
||||
var/picked_bounty = pick_weight(pool)
|
||||
var/datum/spy_bounty/bounty = new picked_bounty(src)
|
||||
if(bounty.initalized)
|
||||
amount_to_give -= 1
|
||||
bounties[difficulty] += bounty
|
||||
|
||||
else
|
||||
failed_attempts -= 1
|
||||
qdel(bounty)
|
||||
|
||||
claimed_bounties_from_last_pool.Cut()
|
||||
num_refreshes += 1
|
||||
refresh_timer = addtimer(CALLBACK(src, PROC_REF(refresh_bounty_list)), refresh_time, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE)
|
||||
|
||||
/// Forces a refresh of the bounty list.
|
||||
/// Counts towards [num_refreshes].
|
||||
/datum/spy_bounty_handler/proc/force_refresh()
|
||||
if(refresh_timer)
|
||||
deltimer(refresh_timer)
|
||||
|
||||
refresh_bounty_list()
|
||||
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* ## Spy uplink
|
||||
*
|
||||
* Applied to items similar to traitor uplinks.
|
||||
*
|
||||
* Used for spies to complete bounties.
|
||||
*/
|
||||
/datum/component/spy_uplink
|
||||
/// Weakref to the spy antag datum which owns this uplink
|
||||
var/datum/weakref/spy_ref
|
||||
/// The handler which manages all bounties across all spies.
|
||||
var/static/datum/spy_bounty_handler/handler
|
||||
|
||||
/datum/component/spy_uplink/Initialize(datum/antagonist/spy/spy)
|
||||
if(!isitem(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
spy_ref = WEAKREF(spy)
|
||||
|
||||
if(isnull(handler))
|
||||
handler = new()
|
||||
|
||||
/datum/component/spy_uplink/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
|
||||
RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, PROC_REF(on_attack_self))
|
||||
RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK_SECONDARY, PROC_REF(on_pre_attack_secondary))
|
||||
RegisterSignal(parent, COMSIG_TABLET_CHECK_DETONATE, PROC_REF(block_pda_bombs))
|
||||
|
||||
/datum/component/spy_uplink/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(
|
||||
COMSIG_ATOM_EXAMINE,
|
||||
COMSIG_ITEM_ATTACK_SELF,
|
||||
COMSIG_ITEM_PRE_ATTACK_SECONDARY,
|
||||
COMSIG_TABLET_CHECK_DETONATE,
|
||||
))
|
||||
|
||||
/// Checks that the passed mob is the owner of this uplink.
|
||||
/datum/component/spy_uplink/proc/is_our_spy(mob/whoever)
|
||||
var/datum/antagonist/spy/spy_datum = spy_ref?.resolve()
|
||||
return spy_datum?.owner.current == whoever
|
||||
|
||||
/datum/component/spy_uplink/proc/on_examine(obj/item/source, mob/user, list/examine_list)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!is_our_spy(user))
|
||||
return
|
||||
examine_list += span_notice("You recognize this as your <i>spy uplink</i>.")
|
||||
examine_list += span_notice("- [EXAMINE_HINT("Use it in hand")] to view your bounty list.")
|
||||
examine_list += span_notice("- [EXAMINE_HINT("Right click")] with it on a bounty target to claim it.")
|
||||
|
||||
/datum/component/spy_uplink/proc/block_pda_bombs(obj/item/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
return COMPONENT_TABLET_NO_DETONATE
|
||||
|
||||
/datum/component/spy_uplink/proc/on_attack_self(obj/item/source, mob/user)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(is_our_spy(user))
|
||||
INVOKE_ASYNC(src, TYPE_PROC_REF(/datum, ui_interact), user)
|
||||
return NONE
|
||||
|
||||
/datum/component/spy_uplink/proc/on_pre_attack_secondary(obj/item/source, atom/target, mob/living/user, params)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!ismovable(target))
|
||||
return NONE
|
||||
if(!is_our_spy(user))
|
||||
return NONE
|
||||
if(!try_steal(target, user))
|
||||
return NONE
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/// Checks if the passed atom is something that can be stolen according to one of the active bounties.
|
||||
/// If so, starts the stealing process.
|
||||
/datum/component/spy_uplink/proc/try_steal(atom/movable/stealing, mob/living/spy)
|
||||
for(var/datum/spy_bounty/bounty as anything in handler.get_all_bounties())
|
||||
if(!bounty.can_claim(spy))
|
||||
continue
|
||||
if(!bounty.is_stealable(stealing))
|
||||
continue
|
||||
if(bounty.claimed)
|
||||
stealing.balloon_alert(spy, "bounty already claimed!")
|
||||
return TRUE
|
||||
if(DOING_INTERACTION(spy, REF(src)))
|
||||
spy.balloon_alert(spy, "already scanning!") // Only shown if they're trying to scan two valid targets
|
||||
return TRUE
|
||||
SEND_SIGNAL(stealing, COMSIG_MOVABLE_SPY_STEALING, spy, bounty)
|
||||
INVOKE_ASYNC(src, PROC_REF(start_stealing), stealing, spy, bounty)
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
|
||||
/// Wraps the stealing process in a scanning effect.
|
||||
/datum/component/spy_uplink/proc/start_stealing(atom/movable/stealing, mob/living/spy, datum/spy_bounty/bounty)
|
||||
if(!isturf(stealing.loc) && stealing.loc != spy)
|
||||
to_chat(spy, span_warning("Your uplinks blinks red: [stealing] cannot be extracted from there."))
|
||||
return FALSE
|
||||
|
||||
playsound(stealing, 'sound/items/pshoom.ogg', 33, vary = TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, frequency = 0.33, ignore_walls = FALSE)
|
||||
|
||||
var/obj/effect/scan_effect/active_scan_effect = new(stealing.loc)
|
||||
active_scan_effect.appearance = stealing.appearance
|
||||
active_scan_effect.dir = stealing.dir
|
||||
active_scan_effect.makeHologram()
|
||||
SET_PLANE_EXPLICIT(active_scan_effect, stealing.plane, stealing)
|
||||
active_scan_effect.layer = stealing.layer + 0.1
|
||||
|
||||
var/obj/effect/scan_effect/cone/active_scan_cone
|
||||
if(isturf(stealing.loc) && isturf(spy.loc)) // Cone doesn't make sense if its being held or something
|
||||
active_scan_cone = new(spy.loc)
|
||||
var/angle = round(get_angle(spy, stealing), 10)
|
||||
if(angle > 180 && angle < 360)
|
||||
active_scan_cone.pixel_x -= 16
|
||||
else if(angle < 180 && angle > 0)
|
||||
active_scan_cone.pixel_x += 16
|
||||
if(angle > 90 && angle < 270)
|
||||
active_scan_cone.pixel_y -= 16
|
||||
else if(angle < 90 || angle > 270)
|
||||
active_scan_cone.pixel_y += 16
|
||||
active_scan_cone.transform = active_scan_cone.transform.Turn(angle)
|
||||
active_scan_cone.alpha = 0
|
||||
animate(active_scan_cone, time = 0.5 SECONDS, alpha = initial(active_scan_cone.alpha))
|
||||
|
||||
. = steal_process(stealing, spy, bounty)
|
||||
qdel(active_scan_effect)
|
||||
qdel(active_scan_cone)
|
||||
return .
|
||||
|
||||
/// Attempts to steal the passed atom in accordance with the passed bounty.
|
||||
/// If successful, proceeds to complete the bounty.
|
||||
/datum/component/spy_uplink/proc/steal_process(atom/movable/stealing, mob/living/spy, datum/spy_bounty/bounty)
|
||||
spy.visible_message(
|
||||
span_warning("[spy] starts scanning [stealing] with a strange device..."),
|
||||
span_notice("You start scanning [stealing], preparing it for extraction."),
|
||||
)
|
||||
|
||||
if(!do_after(spy, bounty.theft_time, stealing, interaction_key = REF(src)))
|
||||
return FALSE
|
||||
if(bounty.claimed)
|
||||
to_chat(spy, span_warning("Your uplinks blinks red: The bounty for [stealing] has been claimed by another spy!"))
|
||||
return FALSE
|
||||
if(spy.is_holding(stealing) && !spy.dropItemToGround(stealing))
|
||||
to_chat(spy, span_warning("Your uplinks blinks red: [stealing] seems stuck to your hand!"))
|
||||
return FALSE
|
||||
|
||||
var/bounty_key = bounty.get_dupe_protection_key(stealing)
|
||||
handler.all_claimed_bounty_types[bounty_key] += 1
|
||||
handler.claimed_bounties_from_last_pool[bounty_key] = TRUE
|
||||
|
||||
bounty.clean_up_stolen_item(stealing, spy, handler)
|
||||
bounty.claimed = TRUE
|
||||
|
||||
var/atom/movable/reward = bounty.reward_item.spawn_item_for_generic_use(spy)
|
||||
if(isitem(reward))
|
||||
spy.put_in_hands(reward)
|
||||
|
||||
to_chat(spy, span_notice("Bounty complete! You have been rewarded with \a [reward].\
|
||||
[reward.loc == spy ? "" : " <i>Find it at your feet.</i>"]"))
|
||||
|
||||
playsound(parent, 'sound/machines/wewewew.ogg', 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
|
||||
|
||||
log_spy("[key_name(spy)] completed the bounty [bounty.name] of difficulty [bounty.difficulty] for \a [reward].")
|
||||
SSblackbox.record_feedback("nested tally", "spy_bounty", 1, list("[stealing.type]", "[bounty.type]", "[bounty.difficulty]", "[bounty.reward_item.type]"))
|
||||
|
||||
var/datum/antagonist/spy/spy_datum = spy_ref?.resolve()
|
||||
if(!isnull(spy_datum))
|
||||
// "When" TGUI roundend is finished, a list of all bounties complete and their rewards should be put in a collapsible,
|
||||
// otherwise it's just too much information to display cleanly. (That's why we're only displaying number and rewards)
|
||||
spy_datum.bounties_claimed += 1
|
||||
spy_datum.all_loot += bounty.reward_item.name
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/component/spy_uplink/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "SpyUplink")
|
||||
ui.open()
|
||||
|
||||
/datum/component/spy_uplink/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["bounties"] = list()
|
||||
for(var/datum/spy_bounty/bounty as anything in handler.get_all_bounties())
|
||||
UNTYPED_LIST_ADD(data["bounties"], bounty.to_ui_data(user))
|
||||
data["time_left"] = timeleft(handler.refresh_timer)
|
||||
|
||||
return data
|
||||
|
||||
/datum/component/spy_uplink/ui_status(mob/user, datum/ui_state/state)
|
||||
if(isobserver(user) && user.client?.holder)
|
||||
return UI_UPDATE
|
||||
return ..()
|
||||
|
||||
/obj/effect/scan_effect
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
anchored = TRUE
|
||||
layer = ABOVE_ALL_MOB_LAYER
|
||||
|
||||
/obj/effect/scan_effect/cone
|
||||
name = "holoray"
|
||||
icon = 'icons/effects/effects.dmi'
|
||||
icon_state = "scan_beam"
|
||||
color = "#3ba0ff"
|
||||
alpha = 200
|
||||
@@ -13,10 +13,7 @@
|
||||
|
||||
/// Adds item to the available items and add it's category if it is not in categories yet.
|
||||
/datum/market/proc/add_item(datum/market_item/item)
|
||||
if(!prob(initial(item.availability_prob)))
|
||||
return FALSE
|
||||
|
||||
if(ispath(item))
|
||||
if(ispath(item, /datum/market_item))
|
||||
item = new item()
|
||||
|
||||
if(!(item.category in categories))
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
var/stock
|
||||
|
||||
/// Path to or the item itself what this entry is for, this should be set even if you override spawn_item to spawn your item.
|
||||
var/item
|
||||
var/obj/item/item
|
||||
|
||||
/// Minimum price for the item if generated randomly.
|
||||
var/price_min = 0
|
||||
@@ -33,9 +33,18 @@
|
||||
if(isnull(stock))
|
||||
stock = rand(stock_min, stock_max)
|
||||
|
||||
/datum/market_item/Destroy()
|
||||
item = null
|
||||
return ..()
|
||||
|
||||
/// Used for spawning the wanted item, override if you need to do something special with the item.
|
||||
/datum/market_item/proc/spawn_item(loc)
|
||||
return new item(loc)
|
||||
if(ismovable(item))
|
||||
item.forceMove(loc)
|
||||
return item
|
||||
if(ispath(item))
|
||||
return new item(loc)
|
||||
CRASH("Invalid item type for market item [item || "null"]")
|
||||
|
||||
/// Buys the item and makes SSblackmarket handle it.
|
||||
/datum/market_item/proc/buy(obj/item/market_uplink/uplink, mob/buyer, shipping_method)
|
||||
|
||||
@@ -82,11 +82,7 @@
|
||||
if(receiving)
|
||||
var/datum/market_purchase/P = receiving
|
||||
|
||||
if(!P.item || ispath(P.item))
|
||||
P.item = P.entry.spawn_item(T)
|
||||
else
|
||||
var/atom/movable/M = P.item
|
||||
M.forceMove(T)
|
||||
P.item = P.entry.spawn_item(T)
|
||||
|
||||
use_power(power_usage_per_teleport / power_efficiency)
|
||||
var/datum/effect_system/spark_spread/sparks = new
|
||||
|
||||
@@ -173,6 +173,7 @@
|
||||
'sound/ambience/antag/ling_alert.ogg',
|
||||
'sound/ambience/antag/malf.ogg',
|
||||
'sound/ambience/antag/ops.ogg',
|
||||
'sound/ambience/antag/spy.ogg',
|
||||
'sound/ambience/antag/tatoralert.ogg',
|
||||
)
|
||||
|
||||
|
||||
@@ -21,3 +21,8 @@
|
||||
category = LOG_CATEGORY_UPLINK_SPELL
|
||||
config_flag = /datum/config_entry/flag/log_uplink
|
||||
master_category = /datum/log_category/uplink
|
||||
|
||||
/datum/log_category/uplink_spy
|
||||
category = LOG_CATEGORY_UPLINK_SPY
|
||||
config_flag = /datum/config_entry/flag/log_uplink
|
||||
master_category = /datum/log_category/uplink
|
||||
|
||||
@@ -203,7 +203,7 @@
|
||||
new /obj/item/reagent_containers/cup/beaker(src)
|
||||
new /obj/item/clothing/glasses/science(src)
|
||||
if(7)
|
||||
new /obj/item/clothing/glasses/sunglasses(src)
|
||||
new /obj/item/clothing/glasses/sunglasses/big(src)
|
||||
new /obj/item/clothing/mask/cigarette/rollie(src)
|
||||
else
|
||||
//empty grave
|
||||
|
||||
@@ -73,6 +73,11 @@
|
||||
desc = "It's Officer Beepsky! Powered by a potato and a shot of whiskey, and with a sturdier reinforced chassis, too."
|
||||
health = 45
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/beepsky/officer/Initialize(mapload)
|
||||
. = ..()
|
||||
// Beepsky hates people scanning them
|
||||
RegisterSignal(src, COMSIG_MOVABLE_SPY_STEALING, PROC_REF(retaliate_async))
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/beepsky/ofitser
|
||||
name = "Prison Ofitser"
|
||||
desc = "Powered by the tears and sweat of laborers."
|
||||
@@ -208,6 +213,11 @@
|
||||
if("arrest_alert")
|
||||
security_mode_flags ^= SECBOT_DECLARE_ARRESTS
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/proc/retaliate_async(datum/source, mob/user, ...)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
INVOKE_ASYNC(src, PROC_REF(retaliate), user)
|
||||
|
||||
/mob/living/simple_animal/bot/secbot/proc/retaliate(mob/living/carbon/human/attacking_human)
|
||||
var/judgement_criteria = judgement_criteria()
|
||||
threatlevel = attacking_human.assess_threat(judgement_criteria)
|
||||
|
||||
@@ -13,6 +13,12 @@
|
||||
/obj/item/ammo_box/magazine/internal/shot/tube/fire
|
||||
ammo_type = /obj/projectile/bullet/incendiary/shotgun/no_trail
|
||||
|
||||
/obj/item/ammo_box/magazine/internal/shot/tube/buckshot
|
||||
ammo_type = /obj/item/ammo_casing/shotgun/buckshot
|
||||
|
||||
/obj/item/ammo_box/magazine/internal/shot/tube/slug
|
||||
ammo_type = /obj/item/ammo_casing/shotgun
|
||||
|
||||
/obj/item/ammo_box/magazine/internal/shot/lethal
|
||||
ammo_type = /obj/item/ammo_casing/shotgun/buckshot
|
||||
|
||||
|
||||
@@ -97,6 +97,10 @@
|
||||
desc = "An advanced shotgun with two separate magazine tubes. This one shows signs of bounty hunting customization, meaning it likely has a dual rubber shot/fire slug load."
|
||||
alt_mag_type = /obj/item/ammo_box/magazine/internal/shot/tube/fire
|
||||
|
||||
/obj/item/gun/ballistic/shotgun/automatic/dual_tube/deadly
|
||||
spawn_magazine_type = /obj/item/ammo_box/magazine/internal/shot/tube/buckshot
|
||||
alt_mag_type = /obj/item/ammo_box/magazine/internal/shot/tube/slug
|
||||
|
||||
/obj/item/gun/ballistic/shotgun/automatic/dual_tube/examine(mob/user)
|
||||
. = ..()
|
||||
. += span_notice("Alt-click to pump it.")
|
||||
|
||||
@@ -387,4 +387,5 @@
|
||||
/obj/item/firing_pin/Destroy()
|
||||
if(gun)
|
||||
gun.pin = null
|
||||
gun = null
|
||||
return ..()
|
||||
|
||||
@@ -177,3 +177,8 @@
|
||||
|
||||
/obj/item/autosurgeon/syndicate/emaggedsurgerytoolset
|
||||
starting_organ = /obj/item/organ/internal/cyberimp/arm/surgery/emagged
|
||||
|
||||
/obj/item/autosurgeon/syndicate/contraband_sechud
|
||||
desc = "Contains a contraband SecHUD implant, undetectable by health scanners."
|
||||
uses = 1
|
||||
starting_organ = /obj/item/organ/internal/cyberimp/eyes/hud/security/syndicate
|
||||
|
||||
@@ -247,6 +247,7 @@
|
||||
#include "spell_mindswap.dm"
|
||||
#include "spell_names.dm"
|
||||
#include "spell_shapeshift.dm"
|
||||
#include "spies.dm"
|
||||
#include "spritesheets.dm"
|
||||
#include "stack_singular_name.dm"
|
||||
#include "station_trait_tests.dm"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 751 B |
@@ -0,0 +1,41 @@
|
||||
/// Tests spy bounty setup
|
||||
/datum/unit_test/spy_bounty
|
||||
|
||||
/datum/unit_test/spy_bounty/Run()
|
||||
var/mob/living/carbon/human/james_bond = allocate(/mob/living/carbon/human/consistent)
|
||||
james_bond.mind_initialize()
|
||||
james_bond.equipOutfit(/datum/outfit/job/assistant/consistent)
|
||||
var/datum/antagonist/spy/spy = james_bond.mind.add_antag_datum(/datum/antagonist/spy)
|
||||
|
||||
var/datum/component/spy_uplink/uplink = spy.uplink_weakref?.resolve()
|
||||
TEST_ASSERT_NOTNULL(uplink, "Spy failed to be given an uplink!")
|
||||
|
||||
var/datum/spy_bounty_handler/handler = uplink.handler
|
||||
handler.num_attempts_override = 100
|
||||
|
||||
for(var/difficulty in handler.possible_uplink_items)
|
||||
var/list/loot_pool = handler.possible_uplink_items[difficulty]
|
||||
if(!length(loot_pool))
|
||||
TEST_FAIL("No rewards generated for spy bounty difficulty [difficulty]")
|
||||
|
||||
for(var/difficulty in UNLINT(handler.bounty_types))
|
||||
var/list/bounty_type_pool = UNLINT(handler.bounty_types[difficulty])
|
||||
if(!length(bounty_type_pool))
|
||||
TEST_FAIL("No bounty types for spy bounty difficulty [difficulty] found")
|
||||
|
||||
for(var/difficulty in UNLINT(handler.bounties))
|
||||
var/list/generated_bounties = UNLINT(handler.bounties[difficulty])
|
||||
if(difficulty == SPY_DIFFICULTY_HARD)
|
||||
if(length(generated_bounties))
|
||||
TEST_FAIL("No [difficulty] bounties should not be generated on initial refresh!")
|
||||
|
||||
else
|
||||
if(!length(generated_bounties))
|
||||
TEST_FAIL("No bounties were generated on initial refresh for difficulty [difficulty]")
|
||||
|
||||
handler.force_refresh()
|
||||
|
||||
for(var/difficulty in UNLINT(handler.bounties))
|
||||
var/list/generated_bounties = UNLINT(handler.bounties[difficulty])
|
||||
if(!length(generated_bounties))
|
||||
TEST_FAIL("No bounties were generated on first refresh for difficulty [difficulty]")
|
||||
@@ -149,6 +149,34 @@
|
||||
SEND_SIGNAL(uplink_handler, COMSIG_ON_UPLINK_PURCHASE, spawned_item, user)
|
||||
return spawned_item
|
||||
|
||||
/// Used to create the uplink's item for generic use, rather than use by a Syndie specifically
|
||||
/// Can be used to "de-restrict" some items, such as Nukie guns spawning with Syndicate pins
|
||||
/datum/uplink_item/proc/spawn_item_for_generic_use(mob/user)
|
||||
var/atom/movable/created = new item(user.loc)
|
||||
|
||||
if(isgun(created))
|
||||
replace_pin(created)
|
||||
else if(istype(created, /obj/item/storage/toolbox/guncase))
|
||||
for(var/obj/item/gun/gun in created)
|
||||
replace_pin(gun)
|
||||
|
||||
if(isobj(created))
|
||||
var/obj/created_obj = created
|
||||
LAZYREMOVE(created_obj.req_access, ACCESS_SYNDICATE)
|
||||
LAZYREMOVE(created_obj.req_one_access, ACCESS_SYNDICATE)
|
||||
|
||||
return created
|
||||
|
||||
/// Used by spawn_item_for_generic_use to replace the pin of a gun with a normal one
|
||||
/datum/uplink_item/proc/replace_pin(obj/item/gun/gun_reward)
|
||||
PRIVATE_PROC(TRUE)
|
||||
|
||||
if(!istype(gun_reward.pin, /obj/item/firing_pin/implant/pindicate))
|
||||
return
|
||||
|
||||
QDEL_NULL(gun_reward.pin)
|
||||
gun_reward.pin = new /obj/item/firing_pin(gun_reward)
|
||||
|
||||
///For special overrides if an item can be bought or not.
|
||||
/datum/uplink_item/proc/can_be_bought(datum/uplink_handler/source)
|
||||
return TRUE
|
||||
@@ -168,6 +196,7 @@
|
||||
//Discounts (dynamically filled above)
|
||||
/datum/uplink_item/discounts
|
||||
category = /datum/uplink_category/discounts
|
||||
purchasable_from = parent_type::purchasable_from & ~UPLINK_SPY // Probably not necessary but just in case
|
||||
|
||||
// Special equipment (Dynamically fills in uplink component)
|
||||
/datum/uplink_item/special_equipment
|
||||
@@ -176,6 +205,7 @@
|
||||
desc = "Equipment necessary for accomplishing specific objectives. If you are seeing this, something has gone wrong."
|
||||
limited_stock = 1
|
||||
illegal_tech = FALSE
|
||||
purchasable_from = parent_type::purchasable_from & ~UPLINK_SPY // Ditto
|
||||
|
||||
/datum/uplink_item/special_equipment/purchase(mob/user, datum/component/uplink/U)
|
||||
..()
|
||||
|
||||
@@ -53,5 +53,5 @@
|
||||
For when you really need a lot of things dead."
|
||||
item = /obj/item/ammo_box/a357
|
||||
cost = 4
|
||||
purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) //nukies get their own version
|
||||
purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY) //nukies get their own version
|
||||
illegal_tech = FALSE
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
category = /datum/uplink_category/bundle
|
||||
surplus = 0
|
||||
cant_discount = TRUE
|
||||
purchasable_from = parent_type::purchasable_from & ~UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/bundles_tc/random
|
||||
name = "Random Item"
|
||||
desc = "Picking this will purchase a random item. Useful if you have some TC to spare or if you haven't decided on a strategy yet."
|
||||
item = /obj/effect/gibspawner/generic // non-tangible item because techwebs use this path to determine illegal tech
|
||||
item = ABSTRACT_UPLINK_ITEM
|
||||
cost = 0
|
||||
cost_override_string = "Varies"
|
||||
|
||||
@@ -61,7 +62,7 @@
|
||||
item = /obj/item/storage/box/syndicate/bundle_a
|
||||
cost = 20
|
||||
stock_key = UPLINK_SHARED_STOCK_KITS
|
||||
purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
|
||||
purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY)
|
||||
|
||||
/datum/uplink_item/bundles_tc/bundle_b
|
||||
name = "Syndi-kit Special"
|
||||
@@ -72,7 +73,7 @@
|
||||
item = /obj/item/storage/box/syndicate/bundle_b
|
||||
cost = 20
|
||||
stock_key = UPLINK_SHARED_STOCK_KITS
|
||||
purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
|
||||
purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY)
|
||||
|
||||
/datum/uplink_item/bundles_tc/surplus
|
||||
name = "Syndicate Surplus Crate"
|
||||
@@ -81,7 +82,7 @@
|
||||
Contents are sorted to always be worth 30 TC. The Syndicate will only provide one surplus item per agent."
|
||||
item = /obj/structure/closet/crate // will be replaced in purchase()
|
||||
cost = 20
|
||||
purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
|
||||
purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY)
|
||||
stock_key = UPLINK_SHARED_STOCK_SURPLUS
|
||||
/// Value of items inside the crate in TC
|
||||
var/crate_tc_value = 30
|
||||
@@ -170,5 +171,5 @@
|
||||
The Syndicate will only provide one surplus item per agent."
|
||||
cost = 20
|
||||
item = /obj/item/syndicrate_key
|
||||
purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
|
||||
purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY)
|
||||
stock_key = UPLINK_SHARED_STOCK_SURPLUS
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
cost = 10
|
||||
item = /obj/item/pneumatic_cannon/pie/selfcharge
|
||||
surplus = 0
|
||||
purchasable_from = UPLINK_CLOWN_OPS
|
||||
purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/weapon_kits/bananashield
|
||||
name = "Bananium Energy Shield"
|
||||
@@ -18,7 +18,7 @@
|
||||
item = /obj/item/shield/energy/bananium
|
||||
cost = 16
|
||||
surplus = 0
|
||||
purchasable_from = UPLINK_CLOWN_OPS
|
||||
purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/weapon_kits/clownsword
|
||||
name = "Bananium Energy Sword"
|
||||
@@ -27,7 +27,7 @@
|
||||
item = /obj/item/melee/energy/sword/bananium
|
||||
cost = 3
|
||||
surplus = 0
|
||||
purchasable_from = UPLINK_CLOWN_OPS
|
||||
purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/weapon_kits/clownoppin
|
||||
name = "Ultra Hilarious Firing Pin"
|
||||
@@ -51,7 +51,7 @@
|
||||
item = /obj/item/gun/ballistic/automatic/c20r/toy
|
||||
cost = 5
|
||||
surplus = 0
|
||||
purchasable_from = UPLINK_CLOWN_OPS
|
||||
purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/weapon_kits/foammachinegun
|
||||
name = "Toy Machine Gun"
|
||||
@@ -60,7 +60,7 @@
|
||||
item = /obj/item/gun/ballistic/automatic/l6_saw/toy
|
||||
cost = 10
|
||||
surplus = 0
|
||||
purchasable_from = UPLINK_CLOWN_OPS
|
||||
purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/explosives/bombanana
|
||||
name = "Bombanana"
|
||||
@@ -69,7 +69,7 @@
|
||||
item = /obj/item/food/grown/banana/bombanana
|
||||
cost = 4 //it is a bit cheaper than a minibomb because you have to take off your helmet to eat it, which is how you arm it
|
||||
surplus = 0
|
||||
purchasable_from = UPLINK_CLOWN_OPS
|
||||
purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/explosives/clown_bomb_clownops
|
||||
name = "Clown Bomb"
|
||||
@@ -81,7 +81,7 @@
|
||||
item = /obj/item/sbeacondrop/clownbomb
|
||||
cost = 15
|
||||
surplus = 0
|
||||
purchasable_from = UPLINK_CLOWN_OPS
|
||||
purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/explosives/clown_bomb_clownops/New()
|
||||
. = ..()
|
||||
@@ -94,7 +94,7 @@
|
||||
item = /obj/item/grenade/chem_grenade/teargas/moustache
|
||||
cost = 3
|
||||
surplus = 0
|
||||
purchasable_from = UPLINK_CLOWN_OPS
|
||||
purchasable_from = UPLINK_CLOWN_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/explosives/pinata
|
||||
name = "Weapons Grade Pinata Kit"
|
||||
@@ -160,4 +160,3 @@
|
||||
cost = 1
|
||||
purchasable_from = UPLINK_CLOWN_OPS
|
||||
illegal_tech = FALSE
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
item = /obj/item/storage/box/syndicate/contract_kit
|
||||
category = /datum/uplink_category/contractor
|
||||
cost = 20
|
||||
purchasable_from = ~(UPLINK_CLOWN_OPS | UPLINK_NUKE_OPS | UPLINK_TRAITORS)
|
||||
purchasable_from = UPLINK_INFILTRATORS
|
||||
|
||||
/datum/uplink_item/bundles_tc/contract_kit/purchase(mob/user, datum/uplink_handler/uplink_handler, atom/movable/source)
|
||||
. = ..()
|
||||
@@ -36,7 +36,7 @@
|
||||
name = "Contract Reroll"
|
||||
desc = "Request a reroll of your current contract list. Will generate a new target, \
|
||||
payment, and dropoff for the contracts you currently have available."
|
||||
item = /obj/effect/gibspawner/generic
|
||||
item = ABSTRACT_UPLINK_ITEM
|
||||
limited_stock = 2
|
||||
cost = 0
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
/datum/uplink_item/device_tools/failsafe
|
||||
name = "Failsafe Uplink Code"
|
||||
desc = "When entered the uplink will self-destruct immediately."
|
||||
item = /obj/effect/gibspawner/generic
|
||||
item = ABSTRACT_UPLINK_ITEM
|
||||
cost = 1
|
||||
surplus = 0
|
||||
restricted = TRUE
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
// An empty uplink is kinda useless.
|
||||
surplus = 0
|
||||
restricted = TRUE
|
||||
purchasable_from = parent_type::purchasable_from & ~UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/implants/uplink/spawn_item(spawn_path, mob/user, datum/uplink_handler/uplink_handler, atom/movable/source)
|
||||
var/obj/item/storage/box/syndie_kit/uplink_box = ..()
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
/datum/uplink_item/role_restricted/bureaucratic_error
|
||||
name = "Organic Capital Disturbance Virus"
|
||||
desc = "Randomizes job positions presented to new hires. May lead to too many/too few security officers and/or clowns. Single use."
|
||||
item = /obj/effect/gibspawner/generic
|
||||
item = ABSTRACT_UPLINK_ITEM
|
||||
surplus = 0
|
||||
limited_stock = 1
|
||||
cost = 2
|
||||
@@ -286,6 +286,13 @@
|
||||
restricted_roles = list(JOB_CLOWN)
|
||||
surplus = 10
|
||||
|
||||
/datum/uplink_item/role_restricted/clowncar/spawn_item_for_generic_use(mob/user)
|
||||
var/obj/vehicle/sealed/car/clowncar/car = ..()
|
||||
car.enforce_clown_role = FALSE
|
||||
var/obj/item/key = new car.key_type(user.loc)
|
||||
car.visible_message(span_notice("[key] drops out of [car] onto the floor."))
|
||||
return car
|
||||
|
||||
/datum/uplink_item/role_restricted/his_grace
|
||||
name = "His Grace"
|
||||
desc = "An incredibly dangerous weapon recovered from a station overcome by the grey tide. Once activated, He will thirst for blood and must be used to kill to sate that thirst. \
|
||||
@@ -298,6 +305,7 @@
|
||||
cost = 20
|
||||
surplus = 0
|
||||
restricted_roles = list(JOB_CHAPLAIN)
|
||||
purchasable_from = ~UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/role_restricted/concealed_weapon_bay
|
||||
name = "Concealed Weapon Bay"
|
||||
@@ -384,3 +392,4 @@
|
||||
restricted_roles = list(JOB_MIME)
|
||||
restricted = TRUE
|
||||
refundable = FALSE
|
||||
purchasable_from = parent_type::purchasable_from & ~UPLINK_SPY
|
||||
|
||||
@@ -76,26 +76,28 @@
|
||||
name = "12g Buckshot Drum (Bulldog)"
|
||||
desc = "An additional 8-round buckshot magazine for use with the Bulldog shotgun. Front towards enemy."
|
||||
item = /obj/item/ammo_box/magazine/m12g
|
||||
purchasable_from = parent_type::purchasable_from | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/ammo_nuclear/basic/slug
|
||||
name = "12g Slug Drum (Bulldog)"
|
||||
desc = "An additional 8-round slug magazine for use with the Bulldog shotgun. \
|
||||
Now 8 times less likely to shoot your pals."
|
||||
item = /obj/item/ammo_box/magazine/m12g/slug
|
||||
purchasable_from = parent_type::purchasable_from | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/ammo_nuclear/incendiary/dragon
|
||||
name = "12g Dragon's Breath Drum (Bulldog)"
|
||||
desc = "An alternative 8-round dragon's breath magazine for use in the Bulldog shotgun. \
|
||||
'I'm a fire starter, twisted fire starter!'"
|
||||
item = /obj/item/ammo_box/magazine/m12g/dragon
|
||||
purchasable_from = UPLINK_NUKE_OPS
|
||||
purchasable_from = parent_type::purchasable_from | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/ammo_nuclear/special/meteor
|
||||
name = "12g Meteorslug Shells (Bulldog)"
|
||||
desc = "An alternative 8-round meteorslug magazine for use in the Bulldog shotgun. \
|
||||
Great for blasting holes into the hull and knocking down enemies."
|
||||
item = /obj/item/ammo_box/magazine/m12g/meteor
|
||||
purchasable_from = UPLINK_NUKE_OPS
|
||||
purchasable_from = parent_type::purchasable_from | UPLINK_SPY
|
||||
|
||||
// ~~ Ansem Pistol ~~
|
||||
|
||||
@@ -109,24 +111,28 @@
|
||||
name = "10mm Handgun Magazine (Ansem)"
|
||||
desc = "An additional 8-round 10mm magazine, compatible with the Ansem pistol."
|
||||
item = /obj/item/ammo_box/magazine/m10mm
|
||||
purchasable_from = parent_type::purchasable_from | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/ammo_nuclear/ap/m10mm
|
||||
name = "10mm Armour Piercing Magazine (Ansem)"
|
||||
desc = "An additional 8-round 10mm magazine, compatible with the Ansem pistol. \
|
||||
These rounds are less effective at injuring the target but penetrate protective gear."
|
||||
item = /obj/item/ammo_box/magazine/m10mm/ap
|
||||
purchasable_from = parent_type::purchasable_from | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/ammo_nuclear/hp/m10mm
|
||||
name = "10mm Hollow Point Magazine (Ansem)"
|
||||
desc = "An additional 8-round 10mm magazine, compatible with the Ansem pistol. \
|
||||
These rounds are more damaging but ineffective against armour."
|
||||
item = /obj/item/ammo_box/magazine/m10mm/hp
|
||||
purchasable_from = parent_type::purchasable_from | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/ammo_nuclear/incendiary/m10mm
|
||||
name = "10mm Incendiary Magazine (Ansem)"
|
||||
desc = "An additional 8-round 10mm magazine, compatible with the Ansem pistol. \
|
||||
Loaded with incendiary rounds which inflict less damage, but ignite the target."
|
||||
item = /obj/item/ammo_box/magazine/m10mm/fire
|
||||
purchasable_from = parent_type::purchasable_from | UPLINK_SPY
|
||||
|
||||
//Medium-cost: 14 TC each. Meant for more expensive purchases with a goal in mind.
|
||||
|
||||
@@ -197,6 +203,7 @@
|
||||
desc = "A speed loader that contains seven additional .357 Magnum rounds; usable with the Syndicate revolver. \
|
||||
For when you really need a lot of things dead. Operatives get a discount from most of our agents!"
|
||||
item = /obj/item/ammo_box/a357
|
||||
purchasable_from = parent_type::purchasable_from | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/ammo_nuclear/special/revolver/phasic
|
||||
name = ".357 Phasic Speed Loader (Revolver)"
|
||||
@@ -204,6 +211,7 @@
|
||||
These bullets are made from an experimental alloy, 'Ghost Lead', that allows it to pass through almost any non-organic material. \
|
||||
The name is a misnomer. It doesn't contain any lead whatsoever!"
|
||||
item = /obj/item/ammo_box/a357/phasic
|
||||
purchasable_from = parent_type::purchasable_from | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/ammo_nuclear/special/revolver/heartseeker
|
||||
name = ".357 Heartseeker Speed Loader (Revolver)"
|
||||
@@ -212,6 +220,7 @@
|
||||
Brought to you by Roseus Galactic!"
|
||||
item = /obj/item/ammo_box/a357/heartseeker
|
||||
cost = 3
|
||||
purchasable_from = parent_type::purchasable_from | UPLINK_SPY
|
||||
|
||||
// ~~ Grenade Launcher ~~
|
||||
// 'If god had wanted you to live, he would not have created ME!'
|
||||
@@ -591,7 +600,7 @@
|
||||
desc = "An upgraded, elite version of the Syndicate MODsuit. It features fireproofing, and also \
|
||||
provides the user with superior armor and mobility compared to the standard Syndicate MODsuit."
|
||||
item = /obj/item/mod/control/pre_equipped/elite
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/suits/energy_shield
|
||||
name = "MODsuit Energy Shield Module"
|
||||
@@ -599,28 +608,28 @@
|
||||
before needing to recharge. Used wisely, this module will keep you alive for a lot longer."
|
||||
item = /obj/item/mod/module/energy_shield
|
||||
cost = 8
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/suits/emp_shield
|
||||
name = "MODsuit Advanced EMP Shield Module"
|
||||
desc = "An advanced EMP shield module for a MODsuit. It protects your entire body from electromagnetic pulses."
|
||||
item = /obj/item/mod/module/emp_shield/advanced
|
||||
cost = 5
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/suits/injector
|
||||
name = "MODsuit Injector Module"
|
||||
desc = "An injector module for a MODsuit. It is an extendable piercing injector with 30u capacity."
|
||||
item = /obj/item/mod/module/injector
|
||||
cost = 2
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/suits/holster
|
||||
name = "MODsuit Holster Module"
|
||||
desc = "A holster module for a MODsuit. It can stealthily store any not too heavy gun inside it."
|
||||
item = /obj/item/mod/module/holster
|
||||
cost = 2
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/device_tools/medgun_mod
|
||||
name = "Medbeam Gun Module"
|
||||
@@ -665,7 +674,7 @@
|
||||
In its crowbar configuration, it can be used to force open airlocks. Very useful for entering the station or its departments."
|
||||
item = /obj/item/crowbar/power/syndicate
|
||||
cost = 4
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/device_tools/medkit
|
||||
name = "Syndicate Combat Medic Kit"
|
||||
@@ -692,7 +701,7 @@
|
||||
desc = "A potion recovered at great risk by undercover Syndicate operatives and then subsequently modified with Syndicate technology. \
|
||||
Using it will make any animal sentient, and bound to serve you, as well as implanting an internal radio for communication and an internal ID card for opening doors."
|
||||
cost = 4
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY
|
||||
restricted = TRUE
|
||||
|
||||
// Implants
|
||||
@@ -717,6 +726,7 @@
|
||||
This will permanently destroy your body, however."
|
||||
item = /obj/item/storage/box/syndie_kit/imp_microbomb
|
||||
cost = 2
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/implants/nuclear/macrobomb
|
||||
name = "Macrobomb Implant"
|
||||
@@ -732,18 +742,21 @@
|
||||
Prevents collapsing from critical condition, but explodes after a while."
|
||||
item = /obj/item/storage/box/syndie_kit/imp_deniability
|
||||
cost = 6
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/implants/nuclear/reviverplus
|
||||
name = "Reviver Implant"
|
||||
desc = "This implant will attempt to revive and heal you if you lose consciousness. Comes with an autosurgeon."
|
||||
item = /obj/item/autosurgeon/syndicate/reviver
|
||||
cost = 8
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/implants/nuclear/thermals
|
||||
name = "Thermal Eyes"
|
||||
desc = "These cybernetic eyes will give you thermal vision. Comes with a free autosurgeon."
|
||||
item = /obj/item/autosurgeon/syndicate/thermal_eyes
|
||||
cost = 8
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_SPY
|
||||
|
||||
/datum/uplink_item/implants/nuclear/implants/xray
|
||||
name = "X-ray Vision Implant"
|
||||
@@ -756,6 +769,7 @@
|
||||
desc = "This implant will help you get back up on your feet faster after being stunned. Comes with an autosurgeon."
|
||||
item = /obj/item/autosurgeon/syndicate/anti_stun
|
||||
cost = 8
|
||||
purchasable_from = UPLINK_NUKE_OPS | UPLINK_SPY
|
||||
|
||||
// Badass (meme items)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/datum/uplink_item/species_restricted
|
||||
category = /datum/uplink_category/species
|
||||
purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS)
|
||||
purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_SPY)
|
||||
|
||||
/datum/uplink_item/species_restricted/moth_lantern
|
||||
name = "Extra-Bright Lantern"
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/datum/uplink_category/spy_unique
|
||||
name = "Spy Unique"
|
||||
|
||||
// This is solely for uplink items that the spy can randomly obtain via bounties.
|
||||
/datum/uplink_item/spy_unique
|
||||
category = /datum/uplink_category/spy_unique
|
||||
cant_discount = TRUE
|
||||
surplus = FALSE
|
||||
purchasable_from = UPLINK_SPY
|
||||
// Cost doesn't really matter since it's free, but it determines which loot pool it falls into.
|
||||
// By default, these fall into easy-medium spy bounty loot pool
|
||||
cost = SPY_LOWER_COST_THRESHOLD
|
||||
|
||||
/datum/uplink_item/spy_unique/syndie_bowman
|
||||
name = "Syndicate Bowman"
|
||||
desc = "A bowman headset for members of the Syndicate. Not very conspicuous."
|
||||
item = /obj/item/radio/headset/syndicate/alt
|
||||
cost = 1
|
||||
|
||||
/datum/uplink_item/spy_unique/megaphone
|
||||
name = "Megaphone"
|
||||
desc = "A megaphone. It's loud."
|
||||
item = /obj/item/megaphone
|
||||
cost = 1
|
||||
|
||||
/datum/uplink_item/spy_unique/combat_gloves
|
||||
name = "Combat Gloves"
|
||||
desc = "A pair of combat gloves. They're insulated!"
|
||||
item = /obj/item/clothing/gloves/combat
|
||||
cost = 1
|
||||
|
||||
/datum/uplink_item/spy_unique/krav_maga
|
||||
name = "Combat Gloves Plus"
|
||||
desc = "A pair of combat gloves plus. They're insulated AND you can do martial arts with it!"
|
||||
item = /obj/item/clothing/gloves/krav_maga/combatglovesplus
|
||||
|
||||
/datum/uplink_item/spy_unique/tackle_gloves
|
||||
name = "Guerrilla Gloves"
|
||||
desc = "A pair of Guerrilla gloves. They're insulated AND you can tackle people with it!"
|
||||
item = /obj/item/clothing/gloves/tackler/combat/insulated
|
||||
|
||||
/datum/uplink_item/spy_unique/kudzu
|
||||
name = "Kudzu"
|
||||
desc = "A packet of Kudzu - plant and forget, a great distraction."
|
||||
item = /obj/item/seeds/kudzu
|
||||
|
||||
/datum/uplink_item/spy_unique/big_knife
|
||||
name = "Combat Knife"
|
||||
desc = "A big knife. It's sharp."
|
||||
item = /obj/item/knife/combat
|
||||
|
||||
/datum/uplink_item/spy_unique/switchblade
|
||||
name = "Switchblade"
|
||||
desc = "A switchblade. Switches between not sharp and sharp."
|
||||
item = /obj/item/switchblade
|
||||
|
||||
/datum/uplink_item/spy_unique/sechud_implant
|
||||
name = "SecHUD Implant"
|
||||
desc = "A SecHUD implant. Shows you the ID of people you're looking at. It's also stealthy!"
|
||||
item = /obj/item/autosurgeon/syndicate/contraband_sechud
|
||||
|
||||
/datum/uplink_item/spy_unique/rifle_prime
|
||||
name = "Bolt-Action Rifle"
|
||||
desc = "A bolt-action rifle, with a scope. Won't jam, either."
|
||||
item = /obj/item/gun/ballistic/rifle/boltaction/prime
|
||||
cost = SPY_UPPER_COST_THRESHOLD
|
||||
|
||||
/datum/uplink_item/spy_unique/cycler_shotgun
|
||||
name = "Cycler Shotgun"
|
||||
desc = "A cycler shotgun. It's a shotgun that cycles between two barrels."
|
||||
item = /obj/item/gun/ballistic/shotgun/automatic/dual_tube/deadly
|
||||
cost = SPY_UPPER_COST_THRESHOLD
|
||||
|
||||
/datum/uplink_item/spy_unique/bulldog_shotgun
|
||||
name = "Bulldog Shotgun"
|
||||
desc = "A bulldog shotgun. It's a shotgun that shoots bulldogs."
|
||||
item = /obj/item/gun/ballistic/shotgun/bulldog/unrestricted
|
||||
cost = SPY_UPPER_COST_THRESHOLD
|
||||
|
||||
/datum/uplink_item/spy_unique/ansem_pistol
|
||||
name = "Ansem Pistol"
|
||||
desc = "A pistol that's really good at making people sleep."
|
||||
item = /obj/item/gun/ballistic/automatic/pistol/clandestine
|
||||
cost = SPY_UPPER_COST_THRESHOLD
|
||||
|
||||
/datum/uplink_item/spy_unique/rocket_launcher
|
||||
name = "Rocket Launcher"
|
||||
desc = "A rocket launcher. I would recommend against jumping with it."
|
||||
item = /obj/item/gun/ballistic/rocketlauncher
|
||||
cost = SPY_UPPER_COST_THRESHOLD - 1 // It's a meme item
|
||||
|
||||
/datum/uplink_item/spy_unique/shotgun_ammo
|
||||
name = "Box of Buckshot"
|
||||
desc = "A box of buckshot rounds for a shotgun. For when you don't want to miss."
|
||||
item = /obj/item/storage/box/lethalshot
|
||||
cost = 1
|
||||
|
||||
/datum/uplink_item/spy_unique/shotgun_ammo/breacher_slug
|
||||
name = "Box of Breacher Slugs"
|
||||
desc = "A box of breacher slugs for a shotgun. For making a good first impression."
|
||||
item = /obj/item/storage/box/breacherslug
|
||||
|
||||
/datum/uplink_item/spy_unique/shotgun_ammo/slugs
|
||||
name = "Box of Slugs"
|
||||
desc = "A box of slugs for a shotgun. For big game hunting."
|
||||
item = /obj/item/storage/box/slugs
|
||||
|
||||
/datum/uplink_item/spy_unique/stealth_belt
|
||||
name = "Stealth Belt"
|
||||
desc = "A stealth belt that lets you sneak behind enemy lines."
|
||||
item = /obj/item/shadowcloak/weaker
|
||||
cost = SPY_UPPER_COST_THRESHOLD
|
||||
|
||||
/datum/uplink_item/spy_unique/katana
|
||||
name = "Katana"
|
||||
desc = "A really sharp Katana. Did I mention it's sharp?"
|
||||
item = /obj/item/katana
|
||||
cost = /datum/uplink_item/dangerous/doublesword::cost // Puts it in the same pool as Desword
|
||||
|
||||
/datum/uplink_item/spy_unique/medkit_lite
|
||||
name = "Syndicate First Medic Kit"
|
||||
desc = "A syndicate tactical combat medkit, but only stocked enough to do basic first aid."
|
||||
item = /obj/item/storage/medkit/tactical_lite
|
||||
@@ -102,4 +102,4 @@
|
||||
cost = 7
|
||||
surplus = 50
|
||||
limited_stock = 1
|
||||
purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS | UPLINK_INFILTRATORS)
|
||||
purchasable_from = UPLINK_TRAITORS | UPLINK_SPY
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
/datum/uplink_item/stealthy_tools/telecomm_blackout
|
||||
name = "Disable Telecomms"
|
||||
desc = "When purchased, a virus will be uploaded to the telecommunication processing servers to temporarily disable themselves."
|
||||
item = /obj/effect/gibspawner/generic
|
||||
item = ABSTRACT_UPLINK_ITEM
|
||||
surplus = 0
|
||||
progression_minimum = 15 MINUTES
|
||||
limited_stock = 1
|
||||
@@ -117,7 +117,7 @@
|
||||
/datum/uplink_item/stealthy_tools/blackout
|
||||
name = "Trigger Stationwide Blackout"
|
||||
desc = "When purchased, a virus will be uploaded to the engineering processing servers to force a routine power grid check, forcing all APCs on the station to be temporarily disabled."
|
||||
item = /obj/effect/gibspawner/generic
|
||||
item = ABSTRACT_UPLINK_ITEM
|
||||
surplus = 0
|
||||
progression_minimum = 20 MINUTES
|
||||
limited_stock = 1
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 913 KiB After Width: | Height: | Size: 923 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.2 KiB |
@@ -116,6 +116,10 @@
|
||||
icon_state = "secbox_xl"
|
||||
illustration = "breacherslug"
|
||||
|
||||
/obj/item/storage/box/slugs
|
||||
icon_state = "secbox_xl"
|
||||
illustration = "breacherslug"
|
||||
|
||||
/obj/item/storage/box/evidence
|
||||
icon_state = "secbox"
|
||||
illustration = "evidence"
|
||||
|
||||
@@ -169,11 +169,10 @@
|
||||
new /obj/item/pen/sleepy(src)
|
||||
new /obj/item/storage/fancy/cigarettes/cigpack_syndicate(src)
|
||||
if(2) //Energy weapons + energy knives
|
||||
new /obj/item/gun/energy/e_gun(src)
|
||||
new /obj/item/gun/energy/e_gun(src)
|
||||
new /obj/item/gun/energy/e_gun(src)
|
||||
new /obj/item/gun/energy/e_gun/mini(src)
|
||||
new /obj/item/gun/energy/recharge/ebow(src)
|
||||
new /obj/item/gun/energy/recharge/ebow(src)
|
||||
new /obj/item/melee/energy/sword(src)
|
||||
new /obj/item/melee/energy/sword(src)
|
||||
new /obj/item/melee/energy/sword(src)
|
||||
new /obj/item/melee/energy/sword(src)
|
||||
if(3) //Ballistics + knives
|
||||
@@ -197,7 +196,6 @@
|
||||
new /obj/item/mod/control/pre_equipped/mining(src)
|
||||
new /obj/item/mod/control/pre_equipped/engineering(src)
|
||||
new /obj/item/mod/control/pre_equipped/atmospheric(src)
|
||||
new /obj/item/mod/control/pre_equipped/research(src)
|
||||
new /obj/item/mod/control/pre_equipped/traitor(src)
|
||||
new /obj/item/mod/control/pre_equipped/elite(src)
|
||||
if(5) //Implants
|
||||
|
||||
@@ -64,11 +64,15 @@
|
||||
|
||||
new /obj/item/storage/belt/security/full(src)
|
||||
new /obj/item/watertank/pepperspray(src)
|
||||
new /obj/item/gun/energy/disabler(src)
|
||||
new /obj/item/storage/bag/garment/master_arms(src)
|
||||
new /obj/item/radio/headset/interdyne(src)
|
||||
new /obj/item/storage/toolbox/guncase/skyrat/c20r(src)
|
||||
|
||||
/obj/structure/closet/secure_closet/interdynefob/maa_locker/populate_contents_immediate()
|
||||
. = ..()
|
||||
|
||||
new /obj/item/gun/energy/disabler(src)
|
||||
|
||||
/obj/structure/closet/secure_closet/interdynefob/cl_locker
|
||||
icon_door = "hop"
|
||||
icon_state = "hop"
|
||||
|
||||
@@ -33,10 +33,14 @@
|
||||
..()
|
||||
|
||||
new /obj/item/storage/belt/security/full(src)
|
||||
new /obj/item/gun/energy/e_gun(src)
|
||||
new /obj/item/storage/bag/garment/brig_officer(src)
|
||||
new /obj/item/radio/headset/interdyne(src)
|
||||
|
||||
/obj/structure/closet/secure_closet/interdynefob/brig_officer_locker/populate_contents_immediate()
|
||||
. = ..()
|
||||
|
||||
new /obj/item/gun/energy/e_gun(src)
|
||||
|
||||
/obj/structure/closet/secure_closet/interdynefob/armory_gear_locker
|
||||
anchored = 1
|
||||
icon = 'modular_skyrat/master_files/icons/obj/closet.dmi'
|
||||
|
||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
ambidet1.ogg is Fast Talking by Kevin Macleod. It has been licensed under the CC-BY 3.0 license.
|
||||
ambidet1.ogg and spy.ogg is Fast Talking by Kevin Macleod. It has been licensed under the CC-BY 3.0 license.
|
||||
It has been cropped for use ingame.
|
||||
ambidet2.ogg is Night on the Docks, Piano by Kevin Macleod. It has been licensed under CC-BY 3.0 license.
|
||||
It has been cropped for use ingame, and also fades in.
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"objective_body": [
|
||||
"Assassinate a high profile crewmember without being caught.",
|
||||
"Cause a disaster to shake the station.",
|
||||
"Cause a station evacuation.",
|
||||
"Deprive the station of as many @pick(stealables) as you can.",
|
||||
"Ensure @pick(department) is @pick(affected) by the end of the shift.",
|
||||
"Ensure @pick(location) is @pick(affected) by the end of the shift.",
|
||||
"Ensure no heads of staff @pick(escape) the station.",
|
||||
"Ensure no members of @pick(department) @pick(escape) the station.",
|
||||
"Ensure no rival @pick(rivals) @pick(escape) the station.",
|
||||
"Frame a crewmember for a crime.",
|
||||
"Free the station's AI from its laws.",
|
||||
"Halt the station's @pick(happenings).",
|
||||
"Invoke a mutiny against the heads of staff.",
|
||||
"Make it difficult, but not impossible to @pick(escape) the station.",
|
||||
"Sabotage the station's power grid or engine.",
|
||||
"Steal as many @pick(stealables) as you can.",
|
||||
"Take control of the station as the new Captain.",
|
||||
"Take hostages of high value crewmembers and demand a ransom."
|
||||
],
|
||||
"department": [
|
||||
"Security",
|
||||
"Engineering",
|
||||
"Medical",
|
||||
"Science",
|
||||
"Supply"
|
||||
],
|
||||
"location": [
|
||||
"engineering",
|
||||
"genetics",
|
||||
"hydroponics",
|
||||
"medbay",
|
||||
"the bar",
|
||||
"the bridge",
|
||||
"the brig",
|
||||
"the cargo bay",
|
||||
"the chapel",
|
||||
"the kitchen",
|
||||
"the library",
|
||||
"xenobiology"
|
||||
],
|
||||
"happenings": [
|
||||
"research",
|
||||
"cargo operations",
|
||||
"communications",
|
||||
"genetic research",
|
||||
"mining operation"
|
||||
],
|
||||
"affected": [
|
||||
"ablaze",
|
||||
"burning",
|
||||
"covered in blood",
|
||||
"demolished",
|
||||
"destroyed",
|
||||
"engulfed in flames",
|
||||
"obliterated",
|
||||
"on fire",
|
||||
"ruined",
|
||||
"sabotaged",
|
||||
"wrecked"
|
||||
],
|
||||
"rivals": [
|
||||
"agents",
|
||||
"moles",
|
||||
"operatives",
|
||||
"spies",
|
||||
"traitors"
|
||||
],
|
||||
"stealables": [
|
||||
"items",
|
||||
"objects",
|
||||
"things",
|
||||
"tools",
|
||||
"weapons"
|
||||
],
|
||||
"escape": [
|
||||
"depart",
|
||||
"escape",
|
||||
"evacuate",
|
||||
"flee",
|
||||
"leave"
|
||||
]
|
||||
}
|
||||
@@ -3251,6 +3251,10 @@
|
||||
#include "code\modules\antagonists\space_dragon\space_dragon.dm"
|
||||
#include "code\modules\antagonists\space_ninja\space_ninja.dm"
|
||||
#include "code\modules\antagonists\spiders\spiders.dm"
|
||||
#include "code\modules\antagonists\spy\spy.dm"
|
||||
#include "code\modules\antagonists\spy\spy_bounty.dm"
|
||||
#include "code\modules\antagonists\spy\spy_bounty_handler.dm"
|
||||
#include "code\modules\antagonists\spy\spy_uplink.dm"
|
||||
#include "code\modules\antagonists\survivalist\survivalist.dm"
|
||||
#include "code\modules\antagonists\syndicate_monkey\syndicate_monkey.dm"
|
||||
#include "code\modules\antagonists\traitor\balance_helper.dm"
|
||||
@@ -5970,6 +5974,7 @@
|
||||
#include "code\modules\uplink\uplink_items\nukeops.dm"
|
||||
#include "code\modules\uplink\uplink_items\special.dm"
|
||||
#include "code\modules\uplink\uplink_items\species.dm"
|
||||
#include "code\modules\uplink\uplink_items\spy_unique.dm"
|
||||
#include "code\modules\uplink\uplink_items\stealthy.dm"
|
||||
#include "code\modules\uplink\uplink_items\stealthy_tools.dm"
|
||||
#include "code\modules\uplink\uplink_items\suits.dm"
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Section, Stack } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { Objective, ObjectivePrintout } from './common/Objectives';
|
||||
|
||||
const greenText = {
|
||||
fontWeight: 'italics',
|
||||
color: '#20b142',
|
||||
};
|
||||
|
||||
const redText = {
|
||||
fontWeight: 'italics',
|
||||
color: '#e03c3c',
|
||||
};
|
||||
|
||||
type Data = {
|
||||
antag_name: string;
|
||||
uplink_location: string | null;
|
||||
objectives: Objective[];
|
||||
};
|
||||
|
||||
export const AntagInfoSpy = () => {
|
||||
const { data } = useBackend<Data>();
|
||||
const { antag_name, uplink_location, objectives } = data;
|
||||
return (
|
||||
<Window width={380} height={420} theme="ntos_darkmode">
|
||||
<Window.Content
|
||||
style={{
|
||||
backgroundImage: 'none',
|
||||
}}
|
||||
>
|
||||
<Section title={`You are the ${antag_name || 'Spy'}.`}>
|
||||
<Stack vertical fill ml={1} mr={1}>
|
||||
<Stack.Item fontSize={1.2}>
|
||||
You have been equipped with a special uplink device disguised as{' '}
|
||||
{uplink_location || 'something'} that will allow you to steal from
|
||||
the station.
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<span style={greenText}>
|
||||
<b>Use it in hand</b> to access your uplink, and{' '}
|
||||
<b>right click</b> on bounty targets to steal them.
|
||||
</span>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item>
|
||||
You may not be alone: There may be other spies on the station.
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
Work together or work against them: The choice is yours, but{' '}
|
||||
<span style={redText}>you cannot share the rewards.</span>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item>
|
||||
<ObjectivePrintout
|
||||
titleMessage={'Your mission, should you choose to accept it'}
|
||||
objectives={objectives}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import { multiline } from 'common/string';
|
||||
|
||||
import { Antagonist, Category } from '../base';
|
||||
|
||||
const Spy: Antagonist = {
|
||||
key: 'spy',
|
||||
name: 'Spy',
|
||||
description: [
|
||||
multiline`
|
||||
Your mission, should you choose to accept it: Infiltrate Space Station 13.
|
||||
Disguise yourself as a member of their crew and steal vital equipment.
|
||||
Should you be caught or killed, your employer will disavow any knowledge
|
||||
of your actions. Good luck agent.
|
||||
`,
|
||||
|
||||
multiline`
|
||||
Complete Spy Bounties to earn rewards from your employer.
|
||||
Use these rewards to sow chaos and mischief!
|
||||
`,
|
||||
],
|
||||
category: Category.Roundstart,
|
||||
};
|
||||
|
||||
export default Spy;
|
||||
@@ -0,0 +1,122 @@
|
||||
import { BooleanLike } from 'common/react';
|
||||
|
||||
import { useBackend } from '../backend';
|
||||
import { BlockQuote, Box, Dimmer, Icon, Section, Stack } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
type Bounty = {
|
||||
name: string;
|
||||
help: string;
|
||||
difficulty: string;
|
||||
reward: string;
|
||||
claimed: BooleanLike;
|
||||
can_claim: BooleanLike;
|
||||
};
|
||||
|
||||
type Data = {
|
||||
time_left: number;
|
||||
bounties: Bounty[];
|
||||
};
|
||||
|
||||
const difficulty_to_color = {
|
||||
easy: 'good',
|
||||
medium: 'average',
|
||||
hard: 'bad',
|
||||
};
|
||||
|
||||
const BountyDimmer = (props: { text: string; color: string }) => {
|
||||
return (
|
||||
<Dimmer>
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Icon name="user-secret" size={2} color={props.color} />
|
||||
</Stack.Item>
|
||||
<Stack.Item align={'center'} italic>
|
||||
{props.text}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Dimmer>
|
||||
);
|
||||
};
|
||||
|
||||
const BountyDisplay = (props: { bounty: Bounty }) => {
|
||||
const { bounty } = props;
|
||||
|
||||
return (
|
||||
<Section>
|
||||
{!!bounty.claimed && <BountyDimmer color="bad" text="Claimed!" />}
|
||||
{!bounty.can_claim && !bounty.claimed && (
|
||||
<BountyDimmer
|
||||
color="average"
|
||||
text="Your benefactors see you unfit to complete this."
|
||||
/>
|
||||
)}
|
||||
<Stack vertical ml={1}>
|
||||
<Stack.Item>
|
||||
<Box
|
||||
style={{
|
||||
textTransform: 'capitalize',
|
||||
}}
|
||||
color={difficulty_to_color[bounty.difficulty.toLowerCase()]}
|
||||
>
|
||||
{bounty.name}
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<BlockQuote italic>{bounty.help}</BlockQuote>
|
||||
</Stack.Item>
|
||||
<Stack.Item italic>Reward: {bounty.reward}</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
// Formats a number of deciseconds into a string minutes:seconds
|
||||
const format_deciseconds = (deciseconds: number) => {
|
||||
const seconds = Math.floor(deciseconds / 10);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
|
||||
const seconds_left = seconds % 60;
|
||||
const minutes_left = minutes % 60;
|
||||
|
||||
const seconds_string = seconds_left.toString().padStart(2, '0');
|
||||
const minutes_string = minutes_left.toString().padStart(2, '0');
|
||||
|
||||
return `${minutes_string}:${seconds_string}`;
|
||||
};
|
||||
|
||||
export const SpyUplink = () => {
|
||||
const { data } = useBackend<Data>();
|
||||
const { bounties, time_left } = data;
|
||||
|
||||
return (
|
||||
<Window width={450} height={615} theme="ntos_darkmode">
|
||||
<Window.Content
|
||||
style={{
|
||||
backgroundImage: 'none',
|
||||
}}
|
||||
>
|
||||
<Section
|
||||
fill
|
||||
title="Spy Bounties"
|
||||
scrollable
|
||||
buttons={
|
||||
<Box mt={0.4}>
|
||||
Time until refresh: {format_deciseconds(time_left)}
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
{bounties.map((bounty) => (
|
||||
<Stack.Item key={bounty.name} className="candystripe">
|
||||
<BountyDisplay bounty={bounty} />
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user