Exfiltration - Early Antag Escape (#29554)

* Ghost bar antag addition

* Adds extraction flares and extraction procs

* Ghostbarification, antag-specific extractions

* Objectives reroll if target extracts

* Comment format

* Adds blackboxing. Prevents extraction if you're an objective target.

* Fixed objective freezing, removes mindslaves on exfiltration

* Vampire Portal Sprites

* Better sprite for portal

* Flayer telepad, assorted visual adjustments

* Makes flayer and vampire portals truly one time use. Flayer ability icon

* Fixed indents, vampire ability icon

* Changeling flare

* Removed some debug adjustments

* Map fixes

* Makes the exfil restriction a var on the datum

* More map fixes

* Centcomm my beloathed

* More map fixes

* Checks delay before objectives

* Objective freezing list fix

* Review fixes

* Moves a lot of exfil item granting logic to antag datum.

* Changed objective exfil block from var to proc

* Centcommmm

* Adds true return. Thanks Contra.

Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com>
Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>

* Efficiency

Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com>
Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>

* Moved jaunter subtypes

* Forgot to move the admin one

* Capitalization

Co-authored-by: AmityBlamity <142629851+AmityBlamity@users.noreply.github.com>
Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>

* Reworked area selection

* Black box, admin log, proper clearing of martial arts and bio chips

* Exfiltration now properly checks completion of alive and free escape objective

* Adds announcement to security comms when exfil item is used

* Linters

* Map adjustments

* Fixes syndiebar library computer access

* Merge conflict resolution

* Updates action buttons

* New Linters

* Update centcomm.dmm

---------

Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com>
Co-authored-by: AmityBlamity <142629851+AmityBlamity@users.noreply.github.com>
This commit is contained in:
PollardTheDragon
2025-09-08 21:28:09 -04:00
committed by GitHub
parent 7a0918b330
commit fca03efeb0
18 changed files with 4291 additions and 2297 deletions
File diff suppressed because it is too large Load Diff
+1
View File
@@ -22,6 +22,7 @@ GLOBAL_LIST_EMPTY(newplayer_start)
GLOBAL_LIST_EMPTY(latejoin)
GLOBAL_LIST_EMPTY(prisonwarp) //prisoners go to these
GLOBAL_LIST_EMPTY(syndieprisonwarp) //contractor targets go to these
GLOBAL_LIST_EMPTY(antagextractwarp) // exfiltrating antags go here
GLOBAL_LIST_EMPTY(backroomswarp) //backrooms hallucinators go to these
GLOBAL_LIST_EMPTY(xeno_spawn)//Aliens spawn at these.
GLOBAL_LIST_EMPTY(ertdirector)
+94
View File
@@ -0,0 +1,94 @@
/datum/outfit/admin/ghostbar_antag
name = "Ghostly Antagonist"
l_ear = /obj/item/radio/headset/deadsay
l_pocket = /obj/item/stack/spacecash/c1000
id = /obj/item/card/id/syndicate
backpack_contents = list(
/obj/item/storage/box/engineer = 1,
/obj/item/flashlight = 1
)
/datum/outfit/admin/ghostbar_antag/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
. = ..()
if(visualsOnly)
return
var/obj/item/card/id/our_id = H.wear_id
if(!istype(our_id))
return
apply_to_card(our_id, H, list(ACCESS_MAINT_TUNNELS), name)
our_id.assignment = src.name
our_id.registered_name = H.real_name
our_id.sex = capitalize(H.gender)
our_id.age = H.age
our_id.name = "[our_id.registered_name]'s ID Card ([our_id.assignment])"
our_id.photo = get_id_photo(H)
our_id.owner_uid = H.UID()
our_id.owner_ckey = H.ckey
our_id.icon_state = "syndie"
H.job = src.name
H.mind.assigned_role = src.name
H.mind.special_role = src.name
H.mind.offstation_role = TRUE
H.sec_hud_set_ID()
/datum/outfit/admin/ghostbar_antag/syndicate
name = "Syndicate Agent"
uniform = /obj/item/clothing/under/syndicate
suit = /obj/item/clothing/suit/jacket/bomber/syndicate
back = /obj/item/storage/backpack
gloves = /obj/item/clothing/gloves/combat
shoes = /obj/item/clothing/shoes/combat
backpack_contents = list(
/obj/item/storage/box/engineer = 1,
/obj/item/flashlight = 1,
/obj/item/food/syndidonkpocket = 1,
/obj/item/gun/projectile/automatic/toy/pistol = 1,
/obj/item/ammo_box/magazine/toy/pistol = 2
)
/datum/outfit/admin/ghostbar_antag/vampire
name = "Vampire"
uniform = /obj/item/clothing/under/suit/black
suit = /obj/item/clothing/suit/draculacoat
neck = /obj/item/clothing/neck/cloak
back = /obj/item/storage/backpack
gloves = /obj/item/clothing/gloves/color/black
shoes = /obj/item/clothing/shoes/black
backpack_contents = list(
/obj/item/storage/box/engineer = 1,
/obj/item/reagent_containers/iv_bag/blood/o_plus = 3
)
/datum/outfit/admin/ghostbar_antag/changeling
name = "Changeling"
uniform = /obj/item/clothing/under/chameleon
suit = /obj/item/clothing/suit/chameleon
neck = /obj/item/clothing/neck/chameleon
back = /obj/item/storage/backpack/chameleon
gloves = /obj/item/clothing/gloves/chameleon
shoes = /obj/item/clothing/shoes/chameleon
backpack_contents = list(
/obj/item/storage/box/engineer = 1,
/obj/item/toy/foamblade = 1
)
/datum/outfit/admin/ghostbar_antag/mindflayer
name = "Mindflayer"
uniform = /obj/item/clothing/under/psysuit
suit = /obj/item/clothing/suit/blacktrenchcoat
neck = /obj/item/clothing/neck/cloak
back = /obj/item/storage/backpack
gloves = /obj/item/clothing/gloves/color/black
shoes = /obj/item/clothing/shoes/black
backpack_contents = list(
/obj/item/storage/box/engineer = 1,
/obj/item/toy/plushie/ipcplushie = 1
)
@@ -581,6 +581,14 @@
cost = 50
excludefrom = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
/datum/uplink_item/device_tools/extraction_beacon
name = "Extraction Flare"
desc = "A special flare used to call in an extraction portal. The portal takes time to generate, and will only work in certain rooms that it is pre-calibrated for. The Syndicate withholds the right to deny a portal to agents with certain objectives."
reference = "EXTF"
item = /obj/item/wormhole_jaunter/extraction
limited_stock = 1
excludefrom = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
/datum/uplink_item/device_tools/hyper_medipen
name = "Hyper-regenerative Medipen"
desc = "An autoinjector filled with a variety of medical chemicals. It rapidly heals conventional injuries and genetic damage, but loses potency just as quickly. May have side effects if multiple are used in quick succession."
+45 -2
View File
@@ -72,6 +72,9 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
/datum/objective/proc/found_target()
return target
/datum/objective/proc/is_valid_exfiltration()
return TRUE
/**
* This is for objectives that need to register signals, so place them in here.
*/
@@ -215,6 +218,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
explanation_text = "Free Objective"
/datum/objective/assassinate/check_completion()
if(..())
return TRUE
if(target?.current)
if(target.current.stat == DEAD)
return TRUE
@@ -271,6 +276,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
return TARGET_INVALID_NOTHEAD
/datum/objective/mutiny/check_completion()
if(..())
return TRUE
if(target?.current)
if(target.current.stat == DEAD || !ishuman(target.current) || !target.current.ckey || !target.current.client)
return TRUE
@@ -334,6 +341,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
/datum/objective/debrain/check_completion()
if(!target) // If it's a free objective.
return TRUE
if(..())
return TRUE
if(!target.current || !isbrain(target.current))
return FALSE
for(var/datum/mind/M in get_owners())
@@ -358,6 +367,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
/datum/objective/protect/check_completion()
if(!target) //If it's a free objective.
return TRUE
if(..())
return TRUE
if(target.current)
if(target.current.stat == DEAD)
return FALSE
@@ -395,6 +406,9 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
return FALSE
return SSshuttle.emergency.is_hijacked()
/datum/objective/hijack/is_valid_exfiltration()
return FALSE
/datum/objective/hijackclone
name = "Hijack (with clones)"
explanation_text = "Hijack the shuttle by ensuring only you (or your copies) escape."
@@ -453,7 +467,9 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
explanation_text = "Escape on the shuttle or an escape pod alive and free."
needs_target = FALSE
/datum/objective/escape/check_completion()
/datum/objective/escape/check_completion(exfilling = FALSE)
if(..())
return TRUE
var/list/owners = get_owners()
for(var/datum/mind/M in owners)
// These are mandatory conditions, they should come before the freebie conditions below.
@@ -464,6 +480,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
return TRUE
if(SSticker.mode.station_was_nuked) // If they escaped the blast somehow, let them win.
return TRUE
if(exfilling)
return TRUE
if(SSshuttle.emergency.mode < SHUTTLE_ENDGAME)
return FALSE
@@ -536,7 +554,9 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
// This objective should only be given to a single owner since only 1 person can have the ID card of the target.
// We're fine to use `owner` instead of `get_owners()`.
/datum/objective/escape/escape_with_identity/check_completion()
/datum/objective/escape/escape_with_identity/check_completion(exfilling = FALSE)
if(..())
return TRUE
if(!target_real_name)
return TRUE
if(!ishuman(owner.current))
@@ -554,6 +574,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
needs_target = FALSE
/datum/objective/survive/check_completion()
if(..())
return TRUE
for(var/datum/mind/M in get_owners())
if(QDELETED(M.current) || M.current.stat == DEAD || is_special_dead(M.current, check_silicon = FALSE))
return FALSE
@@ -576,6 +598,11 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
/datum/objective/steal/found_target()
return steal_target
/datum/objective/steal/is_valid_exfiltration()
if(istype(steal_target, /datum/theft_objective/nukedisc) || istype(steal_target, /datum/theft_objective/plutonium_core))
return FALSE
return TRUE
/datum/objective/steal/proc/get_location()
return steal_target.location_override || "an unknown area"
@@ -651,6 +678,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
/datum/objective/steal/check_completion()
if(!steal_target)
return TRUE // Free Objective
if(..())
return TRUE
for(var/datum/mind/M in get_owners())
if(!M.current)
@@ -733,6 +762,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
explanation_text = "Acquire [target_amount] compatible genomes. The 'Extract DNA Sting' can be used to stealthily get genomes without killing somebody."
/datum/objective/absorb/check_completion()
if(..())
return TRUE
for(var/datum/mind/M in get_owners())
var/datum/antagonist/changeling/cling = M?.has_antag_datum(/datum/antagonist/changeling)
if(cling?.absorbed_dna && (cling.absorbed_count >= target_amount))
@@ -758,6 +789,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
explanation_text = "Free Objective"
/datum/objective/destroy/check_completion()
if(..())
return TRUE
if(target?.current)
if(target.current.stat == DEAD || is_away_level(target.current.z) || !target.current.ckey)
return TRUE
@@ -778,6 +811,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
wanted_items = typecacheof(wanted_items)
/datum/objective/steal_five_of_type/check_completion()
if(..())
return TRUE
var/stolen_count = 0
var/list/owners = get_owners()
var/list/all_items = list()
@@ -805,6 +840,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
..()
/datum/objective/steal_five_of_type/summon_magic/check_completion()
if(..())
return TRUE
var/stolen_count = 0
var/list/owners = get_owners()
var/list/all_items = list()
@@ -843,6 +880,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
explanation_text = "Accumulate at least [target_amount] total units of blood."
/datum/objective/blood/check_completion()
if(..())
return TRUE
for(var/datum/mind/M in get_owners())
var/datum/antagonist/vampire/V = M.has_antag_datum(/datum/antagonist/vampire)
if(V.bloodtotal >= target_amount)
@@ -870,6 +909,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
explanation_text = "Accumulate at least [target_amount] worth of swarms."
/datum/objective/swarms/check_completion()
if(..())
return TRUE
for(var/datum/mind/M in get_owners())
var/datum/antagonist/mindflayer/flayer = M.has_antag_datum(/datum/antagonist/mindflayer)
return flayer?.total_swarms_gathered >= target_amount
@@ -930,6 +971,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
needs_target = FALSE
/datum/objective/potentially_backstabbed/check_completion()
if(..())
return TRUE
for(var/datum/mind/M in get_owners())
var/datum/antagonist/traitor/T = M.has_antag_datum(/datum/antagonist/traitor)
for(var/datum/objective/our_objective in T.get_antag_objectives(FALSE))
+7
View File
@@ -198,6 +198,13 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark/newplayer_start) //Without this you sp
spawner_list = GLOB.syndieprisonwarp
return ..()
/obj/effect/landmark/spawner/antag_extract_warp
name = "antagextractwarp"
/obj/effect/landmark/spawner/antag_extract_warp/Initialize(mapload)
spawner_list = GLOB.antagextractwarp
return ..()
/obj/effect/landmark/spawner/prisonwarp
name = "prisonwarp"
@@ -61,6 +61,8 @@ GLOBAL_LIST_EMPTY(antagonists)
/// Do we have delayed objective giving?
var/delayed_objectives = FALSE
/// The title of the players "boss", used for exfil strings
var/boss_title = "Operations"
/datum/antagonist/New()
GLOB.antagonists += src
@@ -498,4 +500,24 @@ GLOBAL_LIST_EMPTY(antagonists)
/datum/antagonist/proc/custom_blurb()
return FALSE
/datum/antagonist/proc/exfiltrate(mob/living/carbon/human/extractor, obj/item/radio/radio)
return
/datum/antagonist/proc/prepare_exfiltration(mob/user, obj/item/wormhole_jaunter/extraction/extraction_type = null)
// No extraction for certian steals/hijack
var/objectives = user.mind.get_all_objectives()
for(var/datum/objective/goal in objectives)
if(!goal.is_valid_exfiltration())
to_chat(user, "<span class='warning'>The [boss_title] has deemed your objectives too delicate for an early extraction.</span>")
return
if(world.time < 60 MINUTES) // 60 minutes of no exfil
to_chat(user, "<span class='warning'>The [boss_title] is still preparing an exfiltration portal. Please wait another [round((36000 - world.time) / 600)] minutes before trying again.</span>")
return
var/mob/living/L = user
if(!istype(L))
return
var/obj/item/wormhole_jaunter/extraction/extractor = new extraction_type()
L.put_in_active_hand(extractor)
#undef SUCCESSFUL_DETACH
@@ -0,0 +1,613 @@
// Traitor flare
/obj/item/wormhole_jaunter/extraction
name = "extraction flare"
icon = 'icons/obj/lighting.dmi'
desc = "A single-use extraction flare that will let you escape the station. One way trip."
icon_state = "flare-contractor"
item_state = "flare"
/// What areas can be extracted from?
var/list/extractable_areas = list()
/// Is there a roundstart delay?
var/delayed_extraction = TRUE
/// How long does the extraction take?
var/extraction_time = 90 SECONDS
/// Type of setup once a flare is lit
var/setup_type = /obj/effect/temp_visual/exfiltration
/// Type of portal spawned
var/portal_type = /obj/effect/portal/advanced/exfiltration
/obj/item/wormhole_jaunter/extraction/Initialize(mapload)
. = ..()
var/list/possible_areas = list(
// Rooms
/area/station/hallway/primary/aft,
/area/station/engineering/atmos,
/area/station/public/arcade,
/area/station/maintenance/assembly_line,
/area/station/public/storage/tools/auxiliary,
/area/station/command/office/blueshield,
/area/station/supply/storage,
/area/station/service/chapel,
/area/station/service/chapel/office,
/area/station/service/clown,
/area/station/legal/courtroom,
/area/station/public/toilet,
/area/station/engineering/control,
/area/station/engineering/controlroom,
/area/station/hallway/secondary/exit,
/area/holodeck/alphadeck,
/area/station/service/hydroponics,
/area/station/service/library,
/area/station/service/mime,
/area/station/supply/miningdock,
/area/station/medical/morgue,
/area/station/public/storage/office,
/area/station/public/pet_store,
/area/station/public/storage/tools,
/area/station/public/mrchangs,
/area/station/science/research,
/area/station/security/checkpoint,
/area/station/engineering/tech_storage,
/area/station/command/teleporter,
/area/station/science/storage,
/area/station/science/misc_lab,
/area/station/science/xenobiology,
/area/station/turret_protected/aisat/interior,
/area/station/aisat/atmos,
/area/station/aisat/hall,
/area/station/aisat/service,
/area/station/service/bar,
/area/station/supply/office,
/area/station/medical/chemistry,
/area/station/command/office/ce,
/area/station/command/office/cmo,
/area/station/medical/cloning,
/area/station/medical/cryo,
/area/station/public/dorms,
/area/station/engineering/equipmentstorage,
/area/station/engineering/break_room,
/area/station/ai_monitored/storage/eva,
/area/station/supply/expedition,
/area/station/science/genetics,
/area/station/engineering/gravitygenerator,
/area/station/command/office/hop,
/area/station/command/meeting_room,
/area/station/service/kitchen,
/area/station/science/robotics/chargebay,
/area/station/medical/medbay,
/area/station/medical/medbay2,
/area/station/medical/medbay3,
/area/station/medical/reception,
/area/station/medical/storage,
/area/station/medical/sleeper,
/area/station/command/server,
/area/station/command/office/ntrep,
/area/station/medical/paramedic,
/area/station/hallway/primary/port,
/area/station/supply/qm,
/area/station/command/office/rd,
/area/station/science/rnd,
/area/station/science/robotics,
/area/station/medical/surgery/primary,
/area/station/medical/surgery/secondary,
/area/station/telecomms/chamber,
/area/station/engineering/secure_storage
)
while(length(extractable_areas) < 3)
var/area/selected_area = pick_n_take(possible_areas)
for(var/area/potential in SSmapping.existing_station_areas)
if(potential.type != selected_area)
continue
extractable_areas += potential
break
/obj/item/wormhole_jaunter/extraction/examine(mob/user)
. = ..()
if(isAntag(user))
. += "<span class='warning'>The target extraction locations are:</span>"
for(var/area/A in extractable_areas)
. += "<span class='warning'> - [A.name]</span>"
/obj/item/wormhole_jaunter/extraction/activate(mob/user)
if(!turf_check(user))
return
// Delay extractions
if(world.time < 60 MINUTES && delayed_extraction) // 60 minutes of no exfil
to_chat(user, "<span class='warning'>The exfiltration teleporter is calibrating. Please wait another [round((36000 - world.time) / 600)] minutes before trying again.</span>")
return
// Objective checks
var/denied = FALSE
var/is_target = FALSE
var/has_target_objective = FALSE
var/objectives = user.mind.get_all_objectives()
// Check if you're the target of any other antags. If you are, using the beacon will find it jammed and turned to ash.
for(var/datum/objective/O in GLOB.all_objectives)
if(O.target != user.mind || istype(O, /datum/objective/protect))
continue
denied = TRUE
is_target = TRUE
break
// No extraction for NAD, Plutonium core theft, or Hijack
for(var/datum/objective/goal in objectives)
if(!goal.is_valid_exfiltration())
denied = TRUE
if(istype(goal, /datum/objective/potentially_backstabbed))
has_target_objective = TRUE
if(denied && is_target)
to_chat(user, "<span class='warning'>Someone or something has jammed your extraction beacon, forcing it to disintegrate early!</span>")
if(!has_target_objective)
user.mind.add_mind_objective(/datum/objective/potentially_backstabbed, "Someone or something has jammed your extraction! Survive!")
var/list/messages = user.mind.prepare_announce_objectives(FALSE)
to_chat(user, chat_box_red(messages.Join("<br>")))
new /obj/effect/decal/cleanable/ash(get_turf(src))
qdel(src)
return
else if(denied)
to_chat(user, "<span class='warning'>Your objectives are too delicate for an early extraction.</span>")
new /obj/effect/decal/cleanable/ash(get_turf(src))
qdel(src)
return
var/obj/effect/temp_visual/exfiltration/F = new setup_type(get_turf(src))
show_activation_message(user)
user.drop_item()
forceMove(F)
log_and_message_admins("[user] activated an exfiltration flare.")
var/obj/item/radio/radio = new
radio.listening = FALSE
radio.follow_target = src
radio.config(list("Security" = 0))
radio.autosay("<b>Unknown portal beacon detected at [get_area(src.loc)].</b>", "Automated Announcement", "Security")
qdel(radio)
addtimer(CALLBACK(src, PROC_REF(create_portal), user), extraction_time)
/obj/item/wormhole_jaunter/extraction/turf_check(mob/user)
var/turf/device_turf = get_turf(user)
var/area/our_area = get_area(device_turf)
var/invalid_area = TRUE
for(var/area/A in extractable_areas)
if(istype(our_area, A))
invalid_area = FALSE
break
if(!device_turf || invalid_area)
to_chat(user, "<span class='notice'>You're having difficulties getting the [name] to work.</span>")
return FALSE
return TRUE
/obj/item/wormhole_jaunter/extraction/proc/create_portal(mob/user)
new /obj/effect/decal/cleanable/ash(get_turf(src))
var/obj/effect/portal/advanced/exfiltration/P = new portal_type(get_turf(src), pick(GLOB.antagextractwarp), src, 30 SECONDS, user)
P.antag_mind = user.mind
qdel(src)
/obj/item/wormhole_jaunter/extraction/emag_act(mob/user)
to_chat(user, "<span class='warning'>Emagging [src] has no effect.</span>")
/obj/item/wormhole_jaunter/extraction/chasm_react(mob/user)
return // This is not an instant getaway portal like the jaunter
/obj/item/wormhole_jaunter/extraction/proc/show_activation_message(mob/user)
user.visible_message("<span class='notice'>[user] pulls out a black and gold flare and lights it.</span>",
"<span class='notice'>You light an extraction flare, initiating the extraction process.</span>")
/obj/item/wormhole_jaunter/extraction/vampire
name = "blood chalice"
icon = 'icons/obj/items.dmi'
desc = "An unholy construct that will create a single-use portal that will let you escape the station. One way trip."
icon_state = "blood-chalice"
setup_type = /obj/effect/temp_visual/exfiltration/vampire
/obj/item/wormhole_jaunter/extraction/vampire/show_activation_message(mob/user)
user.visible_message("<span class='notice'>[user] sets a blood-filled chalice on the ground. It begins to bubble ominously...</span>",
"<span class='notice'>You set a blood-filled chalice on the ground. It begins to bubble ominously...</span>")
/obj/item/wormhole_jaunter/extraction/changeling
name = "writhing mass"
icon = 'icons/obj/items.dmi'
desc = "A mass of writhing flesh that will create a single-use portal that will let you escape the station. One way trip."
icon_state = "changeling_organ"
setup_type = /obj/effect/temp_visual/exfiltration/changeling
/obj/item/wormhole_jaunter/extraction/changeling/show_activation_message(mob/user)
user.visible_message("<span class='notice'>[user] sets a grotesque fleshy mass on the floor.</span>",
"<span class='notice'>You set a pulsing piece of yourself on the floor.</span>")
/obj/item/wormhole_jaunter/extraction/mindflayer
name = "nanite telepad"
desc = "A swarm of mindflayer nanites in the shape of a telepad that will create a single-use portal that will let you escape the station. One way trip."
icon_state = "flayer_telepad_base"
setup_type = /obj/effect/temp_visual/exfiltration/mindflayer
/obj/item/wormhole_jaunter/extraction/changeling/show_activation_message(mob/user)
user.visible_message("<span class='notice'>[user] sets a strange telepad on the floor. It begins to unfold.</span>",
"<span class='notice'>You push a button on [src], and watch as it begins to unfold.</span>")
/obj/item/wormhole_jaunter/extraction/admin
name = "advanced extraction flare"
desc = "An advanced single-use extraction flare that will let you escape the station quickly. One way trip."
delayed_extraction = FALSE
extraction_time = 5 SECONDS
setup_type = /obj/effect/temp_visual/exfiltration/admin
// MARK: Traitor Flare
/obj/effect/temp_visual/exfiltration
name = "extraction flare"
layer = BELOW_MOB_LAYER
icon = 'icons/obj/lighting.dmi'
icon_state = "flare-contractor-on"
duration = 90.1 SECONDS
/// Sound that plays when activated
var/activation_sound = 'sound/goonstation/misc/matchstick_light.ogg'
/// Light emitted when activated
var/emitted_color = "#FFD165"
/// Does this one emit light by default?
var/start_lit = TRUE
/obj/effect/temp_visual/exfiltration/Initialize(mapload)
. = ..()
playsound(loc, activation_sound, 50, TRUE)
if(start_lit)
set_light(8, l_color = emitted_color)
// MARK: Vampire Chalice
/obj/effect/temp_visual/exfiltration/vampire
name = "bloody portal"
icon_state = "vampire-portal"
activation_sound = 'sound/magic/strings.ogg'
emitted_color = "#710C04"
// MARK: Changeling Mass
/obj/effect/temp_visual/exfiltration/changeling
name = "writhing mass"
icon_state = "changeling-organ"
activation_sound = 'sound/effects/squelch1.ogg'
emitted_color = "#E79592"
// MARK: Mindflayer telepad
/obj/effect/temp_visual/exfiltration/mindflayer
name = "mindflayer telepad"
desc = "A swarm of mindflayer nanites in the shape of a telepad that will create a single-use portal."
icon_state = "flayer_telepad_deploy"
activation_sound = 'sound/mecha/skyfall_power_up.ogg'
start_lit = FALSE
/obj/effect/temp_visual/exfiltration/mindflayer/Initialize(mapload)
. = ..()
addtimer(CALLBACK(src, PROC_REF(blink)), 10 SECONDS)
/obj/effect/temp_visual/exfiltration/mindflayer/proc/blink()
icon_state = "flayer_telepad_blink"
do_sparks(4, 0, src)
addtimer(CALLBACK(src, PROC_REF(no_blink)), 5 SECONDS)
/obj/effect/temp_visual/exfiltration/mindflayer/proc/no_blink()
icon_state = "flayer_telepad_base"
do_sparks(4, 0, src)
set_light(3, 0.5, LIGHT_COLOR_DARKGREEN)
new /obj/effect/flayer_telepad_binary_effect(get_turf(src))
/obj/effect/flayer_telepad_binary_effect
icon = 'icons/obj/lighting.dmi'
icon_state = "qpad-charge"
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/obj/effect/flayer_telepad_binary_effect/Initialize(mapload)
. = ..()
appearance_flags |= KEEP_TOGETHER
var/icon/our_icon = icon('icons/obj/lighting.dmi', "qpad-charge")
var/icon/alpha_mask
alpha_mask = new('icons/effects/effects.dmi', "scanline") // Scanline effect.
our_icon.AddAlphaMask(alpha_mask) // Finally, let's mix in a distortion effect.
icon = our_icon
color = list(0.2,0.45,0,0, 0,1,0,0, 0,0,0.2,0, 0,0,0,1, 0,0,0,0)
var/mutable_appearance/theme_icon = mutable_appearance('icons/misc/pic_in_pic.dmi', "room_background", appearance_flags = appearance_flags | RESET_TRANSFORM)
theme_icon.blend_mode = BLEND_INSET_OVERLAY
overlays += theme_icon
addtimer(CALLBACK(src, PROC_REF(progress_step)), 1 SECONDS)
/obj/effect/flayer_telepad_binary_effect/proc/progress_step()
new /obj/effect/flayer_telepad_binary_effect_secondary(get_turf(src))
qdel(src)
/obj/effect/flayer_telepad_binary_effect_secondary
icon = 'icons/obj/lighting.dmi'
icon_state = "qpad-charge2"
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/obj/effect/flayer_telepad_binary_effect_secondary/Initialize(mapload)
. = ..()
appearance_flags |= KEEP_TOGETHER
var/icon/our_icon = icon('icons/obj/lighting.dmi', "qpad-charge2")
var/icon/alpha_mask
alpha_mask = new('icons/effects/effects.dmi', "scanline") // Scanline effect.
our_icon.AddAlphaMask(alpha_mask) // Finally, let's mix in a distortion effect.
icon = our_icon
color = list(0.2,0.45,0,0, 0,1,0,0, 0,0,0.2,0, 0,0,0,1, 0,0,0,0)
var/mutable_appearance/theme_icon = mutable_appearance('icons/misc/pic_in_pic.dmi', "room_background", appearance_flags = appearance_flags | RESET_TRANSFORM)
theme_icon.blend_mode = BLEND_INSET_OVERLAY
overlays += theme_icon
addtimer(CALLBACK(src, PROC_REF(sparky)), 4 SECONDS)
addtimer(CALLBACK(src, PROC_REF(progress_step)), 24 SECONDS)
addtimer(CALLBACK(src, PROC_REF(complete_portal)), 74 SECONDS)
/obj/effect/flayer_telepad_binary_effect_secondary/proc/sparky()
new /obj/effect/flayer_binary_portal(get_turf(src))
do_sparks(4, 0, src)
/obj/effect/flayer_telepad_binary_effect_secondary/proc/progress_step()
new /obj/effect/flayer_telepad_binary_effect_tertiary(get_turf(src))
/obj/effect/flayer_telepad_binary_effect_secondary/proc/complete_portal()
qdel(src)
/obj/effect/flayer_binary_portal
icon_state = "bluespace"
icon = 'icons/obj/projectiles.dmi'
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
pixel_y = 24
/obj/effect/flayer_binary_portal/Initialize(mapload)
. = ..()
// transform *= 0.25
appearance_flags |= KEEP_TOGETHER | PIXEL_SCALE
var/icon/our_icon = icon('icons/obj/projectiles.dmi', "bluespace")
var/icon/alpha_mask
alpha_mask = new('icons/effects/effects.dmi', "scanline") // Scanline effect.
our_icon.AddAlphaMask(alpha_mask) // Finally, let's mix in a distortion effect.
icon = our_icon
color = list(0.2,0.45,0,0, 0,1,0,0, 0,0,0.2,0, 0,0,0,1, 0,0,0,0)
var/mutable_appearance/theme_icon = mutable_appearance('icons/misc/pic_in_pic.dmi', "room_background", appearance_flags = appearance_flags | RESET_TRANSFORM)
theme_icon.blend_mode = BLEND_INSET_OVERLAY
overlays += theme_icon
addtimer(CALLBACK(src, PROC_REF(wormhole)), 25 SECONDS)
animate(src, transform = matrix().Scale(0.25), time = 0.1 SECONDS)
animate(transform = matrix().Scale(1, 2), time = 24 SECONDS)
animate(transform = matrix().Scale(0.5), time = 0.9 SECONDS)
/obj/effect/flayer_binary_portal/proc/wormhole()
new /obj/effect/flayer_binary_portal_secondary(get_turf(src))
do_sparks(4, 0, src)
qdel(src)
/obj/effect/flayer_binary_portal_secondary
icon_state = "kinesis"
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
pixel_y = 24
/obj/effect/flayer_binary_portal_secondary/Initialize(mapload)
. = ..()
set_light(5, 2, LIGHT_COLOR_DARKGREEN)
var/obj/effect/warp_effect/bsg/warp = new /obj/effect/warp_effect/bsg(get_turf(src))
warp.pixel_y = 24
appearance_flags |= KEEP_TOGETHER | PIXEL_SCALE
var/icon/our_icon = icon('icons/effects/effects.dmi', "kinesis")
var/icon/alpha_mask
alpha_mask = new('icons/effects/effects.dmi', "scanline") // Scanline effect.
our_icon.AddAlphaMask(alpha_mask) // Finally, let's mix in a distortion effect.
icon = our_icon
color = list(0.2,0.45,0,0, 0,1,0,0, 0,0,0.2,0, 0,0,0,1, 0,0,0,0)
var/mutable_appearance/theme_icon = mutable_appearance('icons/misc/pic_in_pic.dmi', "room_background", appearance_flags = appearance_flags | RESET_TRANSFORM)
theme_icon.blend_mode = BLEND_INSET_OVERLAY
overlays += theme_icon
addtimer(CALLBACK(src, PROC_REF(complete_portal)), 45 SECONDS)
animate(src, transform = matrix().Scale(0.3), time = 0.1 SECONDS)
animate(transform = matrix().Scale(0.3, 0.6), time = 5 SECONDS)
animate(transform = matrix().Scale(0.6, 1.2), time = 15 SECONDS)
/obj/effect/flayer_binary_portal_secondary/proc/complete_portal()
animate(src, transform = matrix().Scale(0.1), time = 0.1 SECONDS)
do_sparks(4, 0, src)
qdel(src)
/obj/effect/flayer_telepad_binary_effect_tertiary
icon = 'icons/obj/lighting.dmi'
icon_state = "qpad-beam"
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/obj/effect/flayer_telepad_binary_effect_tertiary/Initialize(mapload)
. = ..()
appearance_flags |= KEEP_TOGETHER
var/icon/our_icon = icon('icons/obj/lighting.dmi', "qpad-beam")
var/icon/alpha_mask
alpha_mask = new('icons/effects/effects.dmi', "scanline") // Scanline effect.
our_icon.AddAlphaMask(alpha_mask) // Finally, let's mix in a distortion effect.
icon = our_icon
color = list(0.2,0.45,0,0, 0,1,0,0, 0,0,0.2,0, 0,0,0,1, 0,0,0,0)
var/mutable_appearance/theme_icon = mutable_appearance('icons/misc/pic_in_pic.dmi', "room_background", appearance_flags = appearance_flags | RESET_TRANSFORM)
theme_icon.blend_mode = BLEND_INSET_OVERLAY
overlays += theme_icon
addtimer(CALLBACK(src, PROC_REF(complete_portal)), 50 SECONDS)
/obj/effect/flayer_telepad_binary_effect_tertiary/proc/complete_portal()
qdel(src)
// MARK: Debug/Admin
/obj/effect/temp_visual/exfiltration/admin
duration = 5.1 SECONDS
// MARK: Extraction Portal
/obj/effect/portal/advanced/exfiltration
name = "exfiltration portal"
icon_state = "portal-syndicate"
one_use = TRUE
/// The mind of the exfiltrating antag.
var/datum/mind/antag_mind = null
/// Radio for handling extraction taunts
var/obj/item/radio/radio
/// Did we succeed?
var/exfil_success = FALSE
/obj/effect/portal/advanced/exfiltration/Initialize(mapload)
. = ..()
radio = new(src)
radio.listening = FALSE
radio.follow_target = src
radio.config(list("Security" = 0))
/obj/effect/portal/advanced/exfiltration/Destroy()
if(exfil_success)
return ..()
if(antag_mind)
log_and_message_admins("[antag_mind.original_mob_name] failed to exfiltrate.")
SSblackbox.record_feedback("tally", "exfiltration", 1, "exfiltration_failure")
return ..()
/obj/effect/portal/advanced/exfiltration/can_teleport(atom/movable/A)
var/mob/living/M = A
if(!istype(M))
return FALSE
return ..()
/obj/effect/portal/advanced/exfiltration/attempt_teleport(atom/movable/victim, turf/destination, variance = 0, force_teleport = TRUE)
if(teleports_this_cycle >= MAX_ALLOWED_TELEPORTS_PER_PROCESS)
return
var/use_effects = world.time >= effect_cooldown
var/effect = null // Will result in the default effect being used
if(!use_effects)
effect = NONE // No effect
// Filter out non-antags
var/mob/living/user = victim
if(!istype(user))
return FALSE
var/datum/mind/user_mind = user.mind
if(user_mind != antag_mind)
to_chat(user, "<span class='warning'>You jump through the portal! Right before you see the other side, you feel an immense, sharp pain in your head!</span>")
var/list/L = get_destinations()
if(!length(L))
to_chat(user, "<span class='warning'>[src] found no beacons in the sector to target.</span>")
return
var/random_destination = pick(L)
if(!do_teleport(victim, random_destination, 2, force_teleport, effect, effect, bypass_area_flag = ignore_tele_proof_area_setting))
invalid_teleport()
return FALSE
fail_extraction(user)
effect_cooldown = world.time + 0.5 SECONDS
teleports_this_cycle++
return FALSE
// For the antag
if(!do_teleport(victim, destination, variance, force_teleport, effect, effect, bypass_area_flag = ignore_tele_proof_area_setting))
invalid_teleport()
return FALSE
pass_extraction(user)
return TRUE
/obj/effect/portal/advanced/exfiltration/proc/get_destinations()
var/list/destinations = list()
for(var/obj/item/beacon/B in GLOB.beacons)
var/turf/T = get_turf(B)
if(is_station_level(T.z))
destinations += B
return destinations
/obj/effect/portal/advanced/exfiltration/proc/fail_extraction(mob/living/schmuck)
to_chat(schmuck, "<span class='warning'>You feel immense pain!</span>")
log_and_message_admins("[schmuck] went through an exfiltration portal they didn't control.")
schmuck.Paralyse(30 SECONDS)
schmuck.EyeBlind(35 SECONDS)
schmuck.EyeBlurry(35 SECONDS)
schmuck.AdjustConfused(35 SECONDS)
// Same effects as if they got contracted
var/obj/item/organ/external/injury_target
if(prob(20))
injury_target = schmuck.get_organ(pick(BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_PRECISE_L_FOOT))
if(!injury_target)
return
schmuck.adjustBruteLoss(40)
schmuck.adjustBrainLoss(25)
injury_target.droplimb()
to_chat(schmuck, "<span class='warning'>You were interrogated by your captors before being sent back! Oh god, something's missing!</span>")
return
// Species specific punishments first
if(ismachineperson(schmuck))
schmuck.emp_act(EMP_HEAVY)
schmuck.adjustBrainLoss(30)
to_chat(schmuck, "<span class='warning'>You were interrogated by your captors before being sent back! You feel like some of your components are loose!</span>")
return
schmuck.adjustBruteLoss(40)
schmuck.adjustBrainLoss(25)
if(isslimeperson(schmuck))
injury_target = schmuck.get_organ(pick(BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_PRECISE_L_FOOT))
if(!injury_target)
return
injury_target.cause_internal_bleeding()
injury_target = schmuck.get_organ(BODY_ZONE_CHEST)
injury_target.cause_internal_bleeding()
to_chat(schmuck, "<span class='warning'>You were interrogated by your captors before being sent back! You feel like your inner membrane has been punctured!</span>")
return
if(prob(25))
injury_target = schmuck.get_organ(BODY_ZONE_CHEST)
injury_target.fracture()
else
injury_target = schmuck.get_organ(pick(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_R_LEG))
if(!injury_target)
return
injury_target.fracture()
injury_target.cause_internal_bleeding()
/obj/effect/portal/advanced/exfiltration/proc/pass_extraction(mob/living/M)
// Freeze objectives
for(var/datum/objective/objective in antag_mind.get_all_objectives(include_team = FALSE))
if(istype(objective, /datum/objective/escape))
var/datum/objective/escape/escape_alive = objective
escape_alive.completed = escape_alive.check_completion(exfilling = TRUE)
continue
objective.completed = objective.check_completion()
// Handle syndicate barification
log_and_message_admins("[M] extracted using an exfiltration portal.")
exfil_success = TRUE
SSblackbox.record_feedback("tally", "exfiltration", 1, "exfiltration_success")
prepare_ghosting(M)
/obj/effect/portal/advanced/exfiltration/proc/prepare_ghosting(mob/living/carbon/human/extractor)
if(!istype(extractor))
return
// Remove all clothing and bio chips
for(var/obj/item/I in extractor)
qdel(I)
// Remove cybernetic implants
for(var/obj/item/organ/internal/cyberimp/I in extractor.internal_organs)
// Greys get to keep their implant
if(isgrey(extractor) && istype(I, /obj/item/organ/internal/cyberimp/brain/speech_translator))
continue
// IPCs keep their implant
if(ismachineperson(extractor) && istype(I, /obj/item/organ/internal/cyberimp/arm/power_cord))
continue
// Try removing it
I = I.remove(extractor)
// Remove martial arts
for(var/datum/martial_art/MA in extractor.mind.known_martial_arts)
MA.remove(extractor)
// Equip outfits and remove spells
var/datum/mind/extractor_mind = extractor.mind
for(var/datum/antagonist/antag in extractor_mind.antag_datums)
antag.exfiltrate(extractor, radio)
// Apply traits
ADD_TRAIT(extractor, TRAIT_PACIFISM, GHOST_ROLE)
ADD_TRAIT(extractor, TRAIT_RESPAWNABLE, GHOST_ROLE)
var/obj/item/bio_chip/dust/I = new
I.implant(extractor, null)
if(extractor.mind)
if(extractor.mind.initial_account)
GLOB.station_money_database.delete_user_account(extractor.mind.initial_account.account_number, "NAS Trurl Financial Services", FALSE)
if(extractor.mind && extractor.mind.assigned_role)
// Handle job slot
var/job = extractor.mind.assigned_role
SSjobs.FreeRole(job)
@@ -50,6 +50,7 @@ RESTRICT_TYPE(/datum/antagonist/changeling)
var/regenerating = FALSE
blurb_text_color = COLOR_PURPLE
blurb_text_outline_width = 1
boss_title = "Greater Hivemind"
/datum/antagonist/changeling/New()
..()
@@ -186,6 +187,14 @@ RESTRICT_TYPE(/datum/antagonist/changeling)
chem_charges = clamp(0, chem_charges + chem_recharge_rate - chem_recharge_slowdown, chem_storage)
update_chem_charges_ui(H)
/datum/antagonist/changeling/exfiltrate(mob/living/carbon/human/extractor, obj/item/radio/radio)
remove_changeling_powers(FALSE)
var/datum/action/changeling/power = new /datum/action/changeling/transform
power.Grant(extractor)
extractor.equipOutfit(/datum/outfit/admin/ghostbar_antag/changeling)
radio.autosay("<b>--ZZZT!- Welcome home, [extractor.real_name]. -ZZT!-</b>", "Changeling Hive", "Security")
SSblackbox.record_feedback("tally", "successful_extraction", 1, "Changeling")
/datum/antagonist/changeling/proc/update_chem_charges_ui(mob/living/carbon/human/H = owner.current)
if(H.hud_used?.lingchemdisplay)
H.hud_used.lingchemdisplay.invisibility = 0
@@ -0,0 +1,15 @@
/datum/action/changeling/exfiltration
name = "Generate Extraction Mass"
desc = "We separate a specialized organ that will generate an extraction portal. Costs 30 chemicals."
helptext = "Creates a fleshy mass that can be used in certain areas to create a portal to leave the station early."
button_icon = 'icons/obj/items.dmi'
button_icon_state = "changeling-organ"
chemical_cost = 30
req_human = TRUE
power_type = CHANGELING_INNATE_POWER
// Create extraction mass
/datum/action/changeling/exfiltration/sting_action(mob/living/user)
cling.prepare_exfiltration(user, /obj/item/wormhole_jaunter/extraction/changeling)
cling.remove_specific_power(/datum/action/changeling/exfiltration)
return TRUE
@@ -27,6 +27,7 @@
var/can_pick_capstone = TRUE
/// Have we notified that our victim does not give swarms from draining
var/has_notified = FALSE
boss_title = "Master Mindflayer"
/datum/antagonist/mindflayer/New()
. = ..()
@@ -60,6 +61,13 @@
add_antag_objective(/datum/objective/swarms)
forge_basic_objectives()
/datum/antagonist/mindflayer/exfiltrate(mob/living/carbon/human/extractor, obj/item/radio/radio)
remove_all_abilities()
remove_all_passives()
extractor.equipOutfit(/datum/outfit/admin/ghostbar_antag/mindflayer)
radio.autosay("<b>--ZZZT!- Excellent job, [extractor.real_name]. Proceed to -^%&!-ZZT!-</b>", "Master Flayer", "Security")
SSblackbox.record_feedback("tally", "successful_extraction", 1, "Mindflayer")
/datum/antagonist/mindflayer/proc/get_swarms()
return usable_swarms
@@ -221,3 +221,19 @@
borg.make_mindflayer_robot(user)
return TRUE
/datum/spell/flayer/self/extraction
name = "Nanite Portal Generator"
desc = "Allows us to use our nanites to create an extraction portal."
action_icon = 'icons/obj/lighting.dmi'
action_icon_state = "flayer_telepad_base"
power_type = FLAYER_PURCHASABLE_POWER
base_cooldown = 2 SECONDS
var/used = FALSE
/datum/spell/flayer/self/extraction/cast(list/targets, mob/user)
if(used)
to_chat(user, "<span class='warning'>You have already attempted to create a portal generator!</span>")
return
flayer.prepare_exfiltration(user, /obj/item/wormhole_jaunter/extraction/mindflayer)
used = TRUE
@@ -17,6 +17,7 @@ RESTRICT_TYPE(/datum/antagonist/traitor)
var/give_uplink = TRUE
blurb_r = 200
blurb_a = 0.75
boss_title = "Syndicate Operations"
/// Have we / are we sending a backstab message at this time. If we are, do not send another.
var/sending_backstab = FALSE
@@ -116,6 +117,20 @@ RESTRICT_TYPE(/datum/antagonist/traitor)
else
forge_human_objectives()
/datum/antagonist/traitor/exfiltrate(mob/living/carbon/human/extractor, obj/item/radio/radio)
extractor.equipOutfit(/datum/outfit/admin/ghostbar_antag/syndicate)
// Remove mindslaves
var/list/mindslaves = SSticker.mode.implanted
for(var/datum/mind/possible_slave in mindslaves)
for(var/datum/antagonist/slavetag in possible_slave.antag_datums)
if(!istype(slavetag, /datum/antagonist/mindslave))
continue
var/datum/antagonist/mindslave/slave = slavetag
if(slave.master == extractor.mind)
possible_slave.remove_antag_datum(/datum/antagonist/mindslave/implant)
radio.autosay("<b>--ZZZT!- Good work, $@gent [extractor.real_name]. Return to -^%&!-ZZT!-</b>", "Syndicate Operations", "Security")
SSblackbox.record_feedback("tally", "successful_extraction", 1, "Traitor")
/**
* Create and assign a full set of randomized human traitor objectives.
*/
@@ -24,6 +24,7 @@ RESTRICT_TYPE(/datum/antagonist/vampire)
/datum/spell/vampire/glare = 0,
/datum/vampire_passive/vision = 100,
/datum/spell/vampire/self/specialize = 150,
/datum/spell/vampire/self/exfiltrate = 150,
/datum/vampire_passive/regen = 200,
/datum/vampire_passive/vision/advanced = 500)
@@ -34,6 +35,7 @@ RESTRICT_TYPE(/datum/antagonist/vampire)
blurb_g = 221
blurb_b = 138
blurb_a = 1
boss_title = "Master Vampire"
/datum/antagonist/vampire/Destroy(force, ...)
draining = null
@@ -95,6 +97,23 @@ RESTRICT_TYPE(/datum/antagonist/vampire)
REMOVE_TRAITS_IN(owner.current, "vampire")
UnregisterSignal(owner, COMSIG_ATOM_HOLY_ATTACK)
/datum/antagonist/vampire/exfiltrate(mob/living/carbon/human/extractor, obj/item/radio/radio)
remove_all_powers()
// Remove thralls
if(istype(subclass, SUBCLASS_DANTALION))
var/list/thralls = SSticker.mode.vampire_enthralled
for(var/datum/mind/possible_thrall in thralls)
for(var/datum/antagonist/slavetag in possible_thrall.antag_datums)
if(!istype(slavetag, /datum/antagonist/mindslave))
continue
var/datum/antagonist/mindslave/slave = slavetag
if(slave.master == extractor.mind)
possible_thrall.remove_antag_datum(/datum/antagonist/mindslave/thrall)
extractor.equipOutfit(/datum/outfit/admin/ghostbar_antag/vampire)
radio.autosay("<b>--ZZZT!- Wonderfully done, [extractor.real_name]. Welcome to -^%&!-ZZT!-</b>", "Ancient Vampire", "Security")
SSblackbox.record_feedback("tally", "successful_extraction", 1, "Vampire")
#define BLOOD_GAINED_MODIFIER 0.5
/datum/antagonist/vampire/proc/handle_bloodsucking(mob/living/carbon/human/H, suck_rate = 5 SECONDS)
@@ -108,6 +108,22 @@
return TRUE
/datum/spell/vampire/self/exfiltrate
name = "Conjure Blood Chalice"
desc = "Congeal blood into a chalice that will generate a portal away from the station."
gain_desc = "You can now decide to leave the station."
base_cooldown = 2 SECONDS
action_icon = 'icons/obj/items.dmi'
action_icon_state = "blood-chalice"
var/used = FALSE
/datum/spell/vampire/self/exfiltrate/cast(mob/user)
if(used)
to_chat(user, "<span class='warning'>You have already attempted to create a blood chalice!</span>")
return
var/datum/antagonist/vampire/vamp = user.mind.has_antag_datum(/datum/antagonist/vampire)
vamp.prepare_exfiltration(user, /obj/item/wormhole_jaunter/extraction/vampire)
used = TRUE
/datum/spell/vampire/self/specialize
name = "Choose Specialization"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 107 KiB

+3
View File
@@ -701,6 +701,7 @@
#include "code\datums\outfits\outfit.dm"
#include "code\datums\outfits\outfit_admin.dm"
#include "code\datums\outfits\outfit_debug.dm"
#include "code\datums\outfits\outfit_ghostbar.dm"
#include "code\datums\outfits\plasmamen_outfits.dm"
#include "code\datums\outfits\vv_outfit.dm"
#include "code\datums\proximity\advanced_proximity_monitor.dm"
@@ -1663,6 +1664,7 @@
#include "code\modules\admin\verbs\SDQL2\SDQL_2_parser.dm"
#include "code\modules\admin\verbs\SDQL2\useful_procs.dm"
#include "code\modules\antagonists\_common\antag_datum.dm"
#include "code\modules\antagonists\_common\antag_exfiltration.dm"
#include "code\modules\antagonists\_common\antag_helpers.dm"
#include "code\modules\antagonists\_common\antag_hud.dm"
#include "code\modules\antagonists\_common\antag_spawner.dm"
@@ -1685,6 +1687,7 @@
#include "code\modules\antagonists\changeling\powers\digitalcamo.dm"
#include "code\modules\antagonists\changeling\powers\environmental_adaption.dm"
#include "code\modules\antagonists\changeling\powers\epinephrine.dm"
#include "code\modules\antagonists\changeling\powers\exfilling.dm"
#include "code\modules\antagonists\changeling\powers\fakedeath.dm"
#include "code\modules\antagonists\changeling\powers\fleshmend.dm"
#include "code\modules\antagonists\changeling\powers\hivemind.dm"