Ghost Busting (#18854)
* bustin makes me feel good * Update vorestation.dme * Update trap.dm * more * Edits * Make this work on incorp entities * Update entrepreneur_items.dm * Ghost Capture! * awwa * Update entrepreneur_items.dm * Update entrepreneur_items.dm * vore * Other ways to spawn it * Spectral shot * ammo * yeh * get these * Update ammo.dm * AAAA * Update weapons.dm * Update weapons.dm * beeem * better * Update weapons.dm * more stuffs * make sure this is unticked * Update supplypack.dm * spooky * yeh * mob sprites * aaa * Update research_nodes.dm * Remove RND_CATEGORY_INITIAL from ammunition designs * ?. * subtype
@@ -14,3 +14,7 @@
|
||||
#define COMSIG_ITEM_AFTER_TRASH_EAT_HIDE_MESSAGE (1<<0)
|
||||
///called after mob eats item: (obj/item/thing)
|
||||
#define COMSIG_MOB_AFTER_TRASH_EATING "mob_after_trash_eating"
|
||||
///called when a ghost or phaser is captured by a ghosttrap: (mob/passing_entity)
|
||||
#define COMSIG_GLOB_GHOST_CAPTURED "ghost_captured_by_trap"
|
||||
///Called when a shadow wright passes by a ghosttrap: (obj/effect/shadow_wight)
|
||||
#define COMSIG_GLOB_WIGHT_CAPTURED "wight_captured_by_trap"
|
||||
|
||||
@@ -162,3 +162,6 @@
|
||||
#define TECHWEB_NODE_RSF "rapid_service_fab"
|
||||
#define TECHWEB_NODE_MATERIALSHEETS "material_sheets"
|
||||
#define TECHWEB_NODE_SMARTFRIDGES "smartfridges"
|
||||
#define TECHWEB_NODE_GHOST_BASIC "ghost_basics"
|
||||
#define TECHWEB_NODE_GHOST_ADVANCED "ghost_advanced"
|
||||
#define TECHWEB_NODE_GHOST_ROUNDS "ghost_rounds"
|
||||
|
||||
@@ -31,3 +31,4 @@
|
||||
#define isrobotmultibelt(A) istype(A, /obj/item/robotic_multibelt)
|
||||
#define isgripperpocket(A) istype(A, /obj/item/storage/internal/gripper)
|
||||
#define iscapturecrystal(A) istype(A, /obj/item/capture_crystal)
|
||||
#define isghosttrap(A) istype(A, /obj/item/ghost_trap)
|
||||
|
||||
@@ -8,6 +8,7 @@ GLOBAL_LIST_INIT(global_huds, list(
|
||||
GLOB.global_hud.druggy,
|
||||
GLOB.global_hud.blurry,
|
||||
GLOB.global_hud.whitense,
|
||||
GLOB.global_hud.heavy_whitense,
|
||||
GLOB.global_hud.vimpaired,
|
||||
GLOB.global_hud.darkMask,
|
||||
GLOB.global_hud.centermarker,
|
||||
@@ -28,6 +29,7 @@ GLOBAL_LIST_INIT(global_huds, list(
|
||||
var/atom/movable/screen/druggy
|
||||
var/atom/movable/screen/blurry
|
||||
var/atom/movable/screen/whitense
|
||||
var/atom/movable/screen/heavy_whitense
|
||||
var/list/vimpaired
|
||||
var/list/darkMask
|
||||
var/atom/movable/screen/centermarker
|
||||
@@ -70,6 +72,11 @@ GLOBAL_LIST_INIT(global_huds, list(
|
||||
whitense.icon = 'icons/effects/static.dmi'
|
||||
whitense.icon_state = "1 light"
|
||||
|
||||
//static overlay effect for cameras and the like
|
||||
heavy_whitense = new /atom/movable/screen/global_screen()
|
||||
heavy_whitense.icon = 'icons/effects/static.dmi'
|
||||
heavy_whitense.icon_state = "1 heavy"
|
||||
|
||||
//darksight 'hanger' for attached icons
|
||||
darksight = new /atom/movable/screen()
|
||||
darksight.icon = null
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
if(A == mind.current || (mind.current in A)) // double click your corpse or whatever holds it
|
||||
reenter_corpse() // (cloning scanner, body bag, closet, mech, etc)
|
||||
return
|
||||
|
||||
if(isghosttrap(src.loc))
|
||||
return
|
||||
// Things you might plausibly want to follow
|
||||
if(istype(A,/atom/movable))
|
||||
ManualFollow(A)
|
||||
|
||||
@@ -69,7 +69,6 @@
|
||||
/obj/item/personal_shield_generator/loaded //starts with a cell
|
||||
bcell = /obj/item/cell/device/shield_generator/backpack
|
||||
|
||||
|
||||
/obj/item/personal_shield_generator/update_icon()
|
||||
if(shield_active)
|
||||
icon_state = "shieldpack_basic_on"
|
||||
|
||||
@@ -75,6 +75,10 @@
|
||||
name = "entertainment intercom"
|
||||
frequency = ENT_FREQ
|
||||
|
||||
/obj/item/radio/intercom/science
|
||||
name = "station intercom (Science)"
|
||||
channels=list("Science")
|
||||
|
||||
/obj/item/radio/intercom/omni
|
||||
name = "global announcer"
|
||||
/obj/item/radio/intercom/omni/Initialize(mapload)
|
||||
|
||||
@@ -298,7 +298,7 @@ GLOBAL_DATUM(autospeaker, /mob/living/silicon/ai/announcer)
|
||||
if(!GLOB.autospeaker)
|
||||
return
|
||||
var/datum/radio_frequency/connection = null
|
||||
if(channel && channels && channels.len > 0)
|
||||
if(channel && channels && LAZYLEN(channels))
|
||||
if(channel == "department")
|
||||
channel = channels[1]
|
||||
connection = secure_radio_connections[channel]
|
||||
@@ -325,7 +325,7 @@ GLOBAL_DATUM(autospeaker, /mob/living/silicon/ai/announcer)
|
||||
return radio_connection
|
||||
|
||||
// Otherwise, if a channel is specified, look for it.
|
||||
if(channels && channels.len > 0)
|
||||
if(channels && LAZYLEN(channels))
|
||||
if (message_mode == "department") // Department radio shortcut
|
||||
message_mode = channels[1]
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
|
||||
//9mm
|
||||
/obj/item/ammo_magazine/m9mm/spectral
|
||||
name = "magazine (9mm specter shot)"
|
||||
ammo_type = /obj/item/ammo_casing/a9mm/spectral
|
||||
|
||||
/obj/item/ammo_magazine/m9mml/spectral
|
||||
name = "compact magazine (9mm specter shot)"
|
||||
ammo_type = /obj/item/ammo_casing/a9mm/spectral
|
||||
|
||||
/obj/item/ammo_casing/a9mm/spectral
|
||||
desc = "A 9mm spectral bullet casing. Hits incorporeal entities and brings them into the corporeal realm, but near-useless against corporeal ones."
|
||||
icon_state = "r-casing"
|
||||
projectile_type = /obj/item/projectile/bullet/spectral
|
||||
|
||||
//357
|
||||
/obj/item/ammo_magazine/s357/spectral
|
||||
name = "speedloader (.357 specter shot)"
|
||||
icon_state = "T38"
|
||||
ammo_type = /obj/item/ammo_casing/a357/spectral
|
||||
|
||||
/obj/item/ammo_casing/a357/spectral
|
||||
desc = "A .357 spectral bullet casing. Hits incorporeal entities and brings them into the corporeal realm, but near-useless against corporeal ones."
|
||||
caliber = ".357"
|
||||
icon_state = "r-casing"
|
||||
projectile_type = /obj/item/projectile/bullet/spectral/strong
|
||||
matter = list(MAT_STEEL = 210)
|
||||
|
||||
//38
|
||||
/obj/item/ammo_magazine/s38/spectral
|
||||
name = "speedloader (.38 spectral)"
|
||||
icon_state = "T38"
|
||||
ammo_type = /obj/item/ammo_casing/a38/spectral
|
||||
|
||||
/obj/item/ammo_magazine/ammo_box/b38/spectral
|
||||
name = "ammo box (.38 spectral)"
|
||||
icon_state = "pistol_p"
|
||||
ammo_type = /obj/item/ammo_casing/a38/spectral
|
||||
max_ammo = 24
|
||||
|
||||
/obj/item/ammo_casing/a38/spectral
|
||||
desc = "A .38 rubber bullet casing. Hits incorporeal entities and brings them into the corporeal realm, but near-useless against corporeal ones."
|
||||
icon_state = "empcasing"
|
||||
projectile_type = /obj/item/projectile/bullet/spectral
|
||||
|
||||
//44
|
||||
/obj/item/ammo_magazine/s44/spectral
|
||||
name = "speedloader (.44 specter shot)"
|
||||
icon_state = "R44"
|
||||
ammo_type = /obj/item/ammo_casing/a44/spectral
|
||||
|
||||
/obj/item/ammo_magazine/ammo_box/b44/spectral
|
||||
name = "ammo box (.44 specter shot)"
|
||||
icon_state = "box44-rubber"
|
||||
ammo_type = /obj/item/ammo_casing/a44/spectral
|
||||
max_ammo = 24
|
||||
|
||||
/obj/item/ammo_casing/a44/spectral
|
||||
desc = "A .44 bullet casing. Hits incorporeal entities and brings them into the corporeal realm, but near-useless against corporeal ones."
|
||||
caliber = ".44"
|
||||
icon_state = "r-casing"
|
||||
projectile_type = /obj/item/projectile/bullet/spectral/strong
|
||||
matter = list(MAT_STEEL = 210)
|
||||
|
||||
//The actual spectral bullet.
|
||||
/obj/item/projectile/bullet/spectral
|
||||
damage = 1 //Very, very weak.
|
||||
hud_state = "smg_light"
|
||||
hits_phased = TRUE
|
||||
dephasing = TRUE
|
||||
var/phaser_damage = 10
|
||||
var/weaken_duration = 5
|
||||
var/stun_duration = 3
|
||||
|
||||
/obj/item/projectile/bullet/spectral/attack_mob(mob/living/target_mob, distance, miss_modifier)
|
||||
var/incorp = is_incorporeal()
|
||||
if(incorp)
|
||||
damage = phaser_damage
|
||||
. = ..()
|
||||
if(. && incorp)
|
||||
target_mob.Stun(stun_duration)
|
||||
target_mob.Weaken(weaken_duration)
|
||||
|
||||
/obj/item/projectile/bullet/spectral/can_embed()
|
||||
return FALSE
|
||||
|
||||
//Subtypes
|
||||
/obj/item/projectile/bullet/spectral/strong
|
||||
damage = 5
|
||||
phaser_damage = 25
|
||||
stun_duration = 5
|
||||
weaken_duration = 10
|
||||
@@ -0,0 +1,5 @@
|
||||
/obj/item/entrepreneur/emf/professional
|
||||
name = "Professional EMF scanner"
|
||||
desc = "A handheld device used for detecting disturbances to electromagnetic fields. This one boasts 'improved ghost detection' functions."
|
||||
advanced = TRUE
|
||||
detection_range = 7
|
||||
@@ -0,0 +1,28 @@
|
||||
/obj/item/clothing/glasses/ghost
|
||||
name = "spectral field analysis goggles"
|
||||
desc = "A pair of goggles claiming to let you see into the beyond!"
|
||||
description_fluff = "A pair of goggles created by K.E's Spectral-Division, a popular item among ghost hunters and paranormal investigators. \
|
||||
Due to their extremely poor visual clarity and the extreme static coming from the display, users often claim to see 'specters' and 'spirits' \
|
||||
while wearing them with no physcial proof to back up these claims, as any attempt to take a photo or record through the goggles results in \
|
||||
results in a blurry, static-filled mess."
|
||||
icon_state = "proton_goggles"
|
||||
item_state_slots = list(slot_r_hand_str = "glasses", slot_l_hand_str = "glasses")
|
||||
darkness_view = 7 //So you can really see those ghosts~
|
||||
see_invisible = SEE_INVISIBLE_CULT
|
||||
toggleable = 1
|
||||
actions_types = list(/datum/action/item_action/toggle_goggles)
|
||||
off_state = "denight"
|
||||
flash_protection = FLASH_PROTECTION_VULNERABLE
|
||||
enables_planes = list(VIS_GHOSTS)
|
||||
///If we can see without static or not.
|
||||
var/static_vision = TRUE
|
||||
|
||||
/obj/item/clothing/glasses/ghost/Initialize(mapload)
|
||||
. = ..()
|
||||
if(static_vision)
|
||||
overlay = GLOB.global_hud.heavy_whitense //obscures your vision of the real realm AND makes actually seeing ghosts difficult.
|
||||
|
||||
/obj/item/clothing/glasses/ghost/advanced
|
||||
name = "improved spectral field analysis goggles"
|
||||
desc = "A pair of goggles claiming to let you see into the beyond! This one allows for extra clarity."
|
||||
static_vision = FALSE
|
||||
@@ -0,0 +1,12 @@
|
||||
/datum/supply_pack/sci/spectral
|
||||
name = "Spectral Countermeasure Crate"
|
||||
desc = "A crate containing various items used for ghost hunting and countering paranormal activity."
|
||||
contains = list(
|
||||
/obj/item/entrepreneur/emf/professional = 5,
|
||||
/obj/item/clothing/glasses/ghost = 1,
|
||||
/obj/item/ghost_trap = 5,
|
||||
/obj/item/proton_pack = 1
|
||||
)
|
||||
cost = 1500 //You can see ghosts with this!
|
||||
containertype = /obj/structure/closet/crate/secure/weapon
|
||||
containername = "Spectral Countermeasure Crate"
|
||||
@@ -0,0 +1,250 @@
|
||||
/obj/item/ghost_trap
|
||||
name = "spectral trap"
|
||||
desc = "A mechanically activated 'ghost trap'."
|
||||
description_fluff = "A 'ghost trap' created by Krow Enterprise's Spectral Division. Used by self proclaimed ghost-hunters and \
|
||||
paranormal investigators to supposedly capture spirits and specters."
|
||||
throw_speed = 5
|
||||
throw_range = 7
|
||||
gender = PLURAL
|
||||
icon = 'icons/obj/ghost_trap.dmi'
|
||||
icon_state = "item"
|
||||
randpixel = 0
|
||||
center_of_mass_x = 0
|
||||
center_of_mass_y = 0
|
||||
throwforce = 0
|
||||
w_class = ITEMSIZE_NORMAL
|
||||
var/deployed = FALSE
|
||||
///The entity we currently have captured.
|
||||
var/datum/weakref/captured_entity
|
||||
var/obj/item/radio/intercom/science/ghost_reporter
|
||||
|
||||
/obj/item/ghost_trap/Initialize(mapload)
|
||||
. = ..()
|
||||
if(deployed)
|
||||
update_icon()
|
||||
ghost_reporter = new(null)
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
/obj/item/ghost_trap/Destroy()
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
var/mob/our_entity = captured_entity?.resolve()
|
||||
if(our_entity)
|
||||
REMOVE_TRAIT(our_entity, TRAIT_NO_TRANSFORM, src)
|
||||
our_entity.forceMove(get_turf(src))
|
||||
captured_entity = null
|
||||
QDEL_NULL(ghost_reporter)
|
||||
. = ..()
|
||||
|
||||
/obj/item/ghost_trap/verb/release_occupant()
|
||||
set src in oview(1)
|
||||
set category = "Object"
|
||||
set name = "Relase Entity"
|
||||
release_entity(usr)
|
||||
|
||||
/obj/item/ghost_trap/proc/release_entity(mob/living/user)
|
||||
if(!isliving(user)) //no ghosts
|
||||
return
|
||||
|
||||
if((user.loc == src))
|
||||
to_chat(user, span_warning("You need to be outside \the [src] to do this."))
|
||||
return
|
||||
|
||||
if(captured_entity)
|
||||
var/mob/our_entity = captured_entity.resolve()
|
||||
if(our_entity && (our_entity.loc == src))
|
||||
REMOVE_TRAIT(our_entity, TRAIT_NO_TRANSFORM, src)
|
||||
captured_entity = null
|
||||
our_entity.forceMove(get_turf(src))
|
||||
update_icon()
|
||||
return
|
||||
|
||||
to_chat(user, span_info("There appears to be nothing in the trap!"))
|
||||
return
|
||||
|
||||
/obj/item/ghost_trap/update_icon()
|
||||
..()
|
||||
|
||||
if(deployed)
|
||||
icon_state = "on"
|
||||
return
|
||||
|
||||
if(captured_entity)
|
||||
var/mob/our_entity = captured_entity.resolve()
|
||||
if(our_entity)
|
||||
icon_state = "item_captured"
|
||||
return
|
||||
|
||||
icon_state = initial(icon_state)
|
||||
return
|
||||
icon_state = initial(icon_state)
|
||||
|
||||
|
||||
/obj/item/ghost_trap/start_active
|
||||
deployed = TRUE
|
||||
|
||||
/obj/item/ghost_trap/process()
|
||||
if(captured_entity)
|
||||
var/mob/our_entity = captured_entity.resolve()
|
||||
if(our_entity && our_entity.loc != src)
|
||||
REMOVE_TRAIT(our_entity, TRAIT_NO_TRANSFORM, src)
|
||||
captured_entity = null
|
||||
announce_escape(our_entity)
|
||||
update_icon()
|
||||
|
||||
/obj/item/ghost_trap/proc/announce_escape(mob/our_entity)
|
||||
var/area/our_area = get_area(src)
|
||||
log_and_message_admins("[our_entity] escaped \the [name] at \the [our_area]", our_entity)
|
||||
ghost_reporter.autosay("Attention: Spectral event detected. Captured entity at [our_area] has breached containment.", "Spectral Trap", "Science", using_map.get_map_levels(z))
|
||||
|
||||
/obj/item/ghost_trap/proc/can_use(mob/user)
|
||||
return (user.IsAdvancedToolUser() && !isAI(user) && !user.stat && !user.restrained())
|
||||
|
||||
/obj/item/ghost_trap/attack_self(mob/user)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
if(captured_entity)
|
||||
var/mob/our_entity = captured_entity.resolve()
|
||||
if(our_entity)
|
||||
to_chat(user, "You are unable to use \the [src]! It beeps that it an entity contained inside!")
|
||||
return
|
||||
|
||||
if(!deployed && can_use(user))
|
||||
user.visible_message(
|
||||
span_danger("[user] starts to deploy \the [src]."),
|
||||
span_danger("You begin deploying \the [src]!")
|
||||
)
|
||||
|
||||
if(do_after(user, 6 SECONDS, target = src))
|
||||
user.visible_message(
|
||||
span_danger("[user] has deployed \the [src]."),
|
||||
span_danger("You have deployed \the [src]!")
|
||||
)
|
||||
playsound(src, 'sound/machines/click.ogg', 70, 1)
|
||||
|
||||
deployed = TRUE
|
||||
user.drop_from_inventory(src)
|
||||
update_icon()
|
||||
anchored = TRUE
|
||||
log_and_message_admins("has set up a [name] at \the [get_area(loc)]", user)
|
||||
|
||||
/obj/item/ghost_trap/container_resist(mob/living/escapee)
|
||||
if(!ismob(escapee))
|
||||
return
|
||||
visible_message(span_danger("Lights flicker and buzzers beep from \the [src], alerting that a containment breach is imminent!"))
|
||||
if(do_after(escapee, 2 MINUTES, target = src)) //Escape!
|
||||
REMOVE_TRAIT(escapee, TRAIT_NO_TRANSFORM, src)
|
||||
captured_entity = null
|
||||
escapee.forceMove(get_turf(src))
|
||||
announce_escape(escapee)
|
||||
visible_message(span_danger("A loud buzzer rings out as \the [src] suddenly opens, alerting that a containment breach has ocurred!"))
|
||||
update_icon()
|
||||
|
||||
|
||||
/obj/item/ghost_trap/attack_hand(mob/user)
|
||||
if(has_buckled_mobs() && can_use(user))
|
||||
user.visible_message(
|
||||
span_notice("[user] begins freeing something from \the [src]."),
|
||||
span_notice("You carefully begin to free something from \the [src]."),
|
||||
)
|
||||
if(do_after(user, 6 SECONDS, target = src))
|
||||
user.visible_message(span_notice("Something has been freed from \the [src] by [user]."))
|
||||
for(var/A in buckled_mobs)
|
||||
unbuckle_mob(A)
|
||||
anchored = FALSE
|
||||
deployed = FALSE
|
||||
else if(deployed && can_use(user))
|
||||
user.visible_message(
|
||||
span_danger("[user] starts to deactivate \the [src]."),
|
||||
span_notice("You begin deactivate \the [src]!")
|
||||
)
|
||||
playsound(src, 'sound/machines/click.ogg', 50, 1)
|
||||
|
||||
if(do_after(user, 6 SECONDS, target = src))
|
||||
user.visible_message(
|
||||
span_danger("[user] has deactivated \the [src]."),
|
||||
span_notice("You have deactivated \the [src]!")
|
||||
)
|
||||
deployed = FALSE
|
||||
anchored = FALSE
|
||||
update_icon()
|
||||
else
|
||||
..()
|
||||
|
||||
/obj/item/ghost_trap/proc/catch_ghost(mob/passing_entity)
|
||||
if(!ismob(passing_entity)) //wtf did you do
|
||||
return
|
||||
captured_entity = WEAKREF(passing_entity)
|
||||
|
||||
if(isliving(passing_entity))
|
||||
var/mob/living/living_entity = passing_entity
|
||||
var/datum/component/shadekin/SK = living_entity.get_shadekin_component()
|
||||
living_entity.phase_in(get_turf(src), SK)
|
||||
|
||||
passing_entity.forceMove(src)
|
||||
var/area/our_area = get_area(src)
|
||||
ghost_reporter.autosay("Attention: Spectral event detected. Trap activated at [our_area.name]", "Spectral Trap", "Science", using_map.get_map_levels(z))
|
||||
ADD_TRAIT(passing_entity, TRAIT_NO_TRANSFORM, src)
|
||||
|
||||
to_chat(passing_entity, span_danger("You feel a sudden sensation pulling you into \the [src]!"))
|
||||
if(isobserver(passing_entity))
|
||||
to_chat(passing_entity, span_info("((You are incapable of moving or 'jumping' to turf by clicking, but can still escape via teleport or orbit!))"))
|
||||
|
||||
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_GHOST_CAPTURED, passing_entity)
|
||||
|
||||
/obj/item/ghost_trap/Crossed(atom/movable/AM)
|
||||
|
||||
if(istype(AM, /obj/effect/shadow_wight))
|
||||
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_WIGHT_CAPTURED, AM)
|
||||
visible_message(span_danger("A flurry of beams shoot into the air from \the [src] and into [AM], capturing and disintegrating it!"))
|
||||
return
|
||||
|
||||
if(!ismob(AM)) //Only affects mobs!
|
||||
return
|
||||
|
||||
//Ghost catching.
|
||||
var/mob/passing_entity = AM
|
||||
if(isobserver(passing_entity))
|
||||
var/mob/observer/dead/ghost = AM
|
||||
if(ghost.admin_ghosted || !ghost.interact_with_world)
|
||||
return
|
||||
|
||||
//Catching phasers.
|
||||
if(!passing_entity.is_incorporeal())
|
||||
return
|
||||
|
||||
if(deployed)
|
||||
visible_message(span_danger("A flurry of beams shoot into the air from \the [src]!"))
|
||||
SSmotiontracker.ping(src,100) // Clunk!
|
||||
catch_ghost(passing_entity)
|
||||
deployed = FALSE
|
||||
anchored = FALSE
|
||||
update_icon()
|
||||
log_and_message_admins("has been captured at \the [get_area(loc)] by the [name], last touched by [forensic_data?.get_lastprint()]", passing_entity)
|
||||
|
||||
|
||||
/obj/item/ghost_trap/verb/hidden_vore()
|
||||
set src in oview(1)
|
||||
set category = "Object"
|
||||
set name = "Eat Entity"
|
||||
eat_entity(usr)
|
||||
|
||||
/obj/item/ghost_trap/proc/eat_entity(mob/living/user)
|
||||
if(!isliving(user)) //no ghosts
|
||||
return
|
||||
|
||||
if((user in contents))
|
||||
to_chat(user, span_warning("You need to be inside \the [src] to do this."))
|
||||
return
|
||||
|
||||
if(captured_entity)
|
||||
var/mob/our_entity = captured_entity.resolve()
|
||||
if(our_entity && (our_entity.loc == src) && our_entity.devourable)
|
||||
REMOVE_TRAIT(our_entity, TRAIT_NO_TRANSFORM, src)
|
||||
captured_entity = null
|
||||
user.begin_instant_nom(user, our_entity, user, user.vore_selected)
|
||||
return
|
||||
|
||||
to_chat(user, span_info("There appears to be nothing in the trap to eat!"))
|
||||
return
|
||||
@@ -0,0 +1,157 @@
|
||||
/obj/item/ghost_catcher
|
||||
name = "proton rifle"
|
||||
desc = "A two handed device, used for 'catching ghosts'. Weakens the entity initially grabbed."
|
||||
icon = 'icons/obj/guns/ghost_beam.dmi'
|
||||
icon_state = "ghost_beam"
|
||||
w_class = ITEMSIZE_NORMAL
|
||||
force = 0
|
||||
slot_flags = SLOT_BELT
|
||||
var/grab_range = 5 // How many tiles away it can grab. Changing this also changes the box size.
|
||||
/// Stops multiple grabbs if set to TRUE
|
||||
var/busy = FALSE
|
||||
/// The entity we are currently grabbing.
|
||||
var/datum/weakref/grabbed_entity
|
||||
/// How far we can move an entity in one go!
|
||||
var/max_move_distance = 1
|
||||
/// If we're held in two hands or not...Used until we get two handed component.
|
||||
var/wielded = FALSE
|
||||
|
||||
COOLDOWN_DECLARE(ghost_cooldown)
|
||||
COOLDOWN_DECLARE(click_cooldown)
|
||||
|
||||
//TODO:
|
||||
// Make ghosts/phased entities slow when grabbed
|
||||
// Make it so it searches in an AOE and grabs thing.
|
||||
|
||||
/obj/item/ghost_catcher/Destroy()
|
||||
grabbed_entity = null
|
||||
return ..()
|
||||
|
||||
/obj/item/ghost_catcher/update_icon()
|
||||
if(busy)
|
||||
icon_state = "ghost_beam_active"
|
||||
else
|
||||
icon_state = initial(icon_state)
|
||||
|
||||
/obj/item/ghost_catcher/update_held_icon()
|
||||
var/mob/living/M = loc
|
||||
if(istype(M) && M.can_wield_item(src) && is_held_twohanded(M))
|
||||
wielded = TRUE
|
||||
else
|
||||
wielded = FALSE
|
||||
..()
|
||||
|
||||
/obj/item/ghost_catcher/afterattack(atom/target, mob/user, proximity_flag)
|
||||
if(!wielded)
|
||||
to_chat(user, span_warning("You need to hold \the [src] in two hands to use it!"))
|
||||
return
|
||||
|
||||
if(!COOLDOWN_FINISHED(src, ghost_cooldown))
|
||||
to_chat(user, span_warning("The [src] is recharging!"))
|
||||
return
|
||||
if(!COOLDOWN_FINISHED(src, click_cooldown))
|
||||
to_chat(user, span_warning("The [src] needs time between movements!"))
|
||||
return
|
||||
|
||||
if(isturf(target) && (!target.incorporeal_grab()))
|
||||
var/turf/T = target
|
||||
if(!busy)
|
||||
for(var/mob/entity in range(1, T)) //We'll let you grab things ON the tile or AROUND the tile you click on.
|
||||
if(entity.incorporeal_grab())
|
||||
target = entity
|
||||
break
|
||||
if(!ismob(target))
|
||||
to_chat(user, span_warning("No valid target in range! Beginning cooldown."))
|
||||
COOLDOWN_START(src, ghost_cooldown, 3 SECONDS) //Prevent from spamclicking to find ghosts.
|
||||
return
|
||||
else
|
||||
if(grabbed_entity)
|
||||
var/atom/movable/entity = grabbed_entity.resolve()
|
||||
if(get_dist(T, entity) > max_move_distance)
|
||||
to_chat(user, span_warning("\The [src] is unable to pull the entity that far!"))
|
||||
return
|
||||
entity.forceMove(T)
|
||||
COOLDOWN_START(src, click_cooldown, 1 SECOND)
|
||||
return
|
||||
// Things that invalidate the scan immediately.
|
||||
if(busy)
|
||||
to_chat(user, span_warning("\The [src] is already grabbing an entity!"))
|
||||
return
|
||||
|
||||
if(!target.incorporeal_grab(user)) // This will tell the user what is wrong.
|
||||
return
|
||||
|
||||
if(get_dist(target, user) > grab_range)
|
||||
to_chat(user, span_warning("You are too far away from \the [target] to catalogue it. Get closer."))
|
||||
return
|
||||
|
||||
// Start the special effects.
|
||||
busy = TRUE
|
||||
update_icon()
|
||||
var/datum/beam/scan_beam = user.Beam(target, icon_state = "curse1", time = 60 SECONDS)
|
||||
var/filter = filter(type = "outline", size = 1, color = "#330099")
|
||||
target.filters += filter
|
||||
var/list/box_segments = list()
|
||||
if(user.client)
|
||||
box_segments = draw_box(target, grab_range, user.client)
|
||||
color_box(box_segments, "#330099", 60 SECONDS)
|
||||
|
||||
playsound(src, 'sound/machines/beep.ogg', 50)
|
||||
|
||||
grabbed_entity = WEAKREF(target)
|
||||
if(isliving(target))
|
||||
var/mob/living/target_mob = target
|
||||
target_mob.Weaken(3)
|
||||
target_mob.Stun(3)
|
||||
to_chat(target, span_danger("You feel yourself weakened from the [src]'s beam!"))
|
||||
|
||||
// The delay, and test for if the scan succeeds or not.
|
||||
if(do_after(user, 60 SECONDS, target, timed_action_flags = IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE, max_distance = grab_range))
|
||||
to_chat(user, span_warning("With a buzz, \the [src] flashes red, the beam on \the [target] has broken!"))
|
||||
playsound(src, 'sound/machines/buzz-two.ogg', 50)
|
||||
color_box(box_segments, "#330099", 3)
|
||||
busy = FALSE
|
||||
|
||||
// Now clean up the effects.
|
||||
update_icon()
|
||||
QDEL_NULL(scan_beam)
|
||||
if(target)
|
||||
target.filters -= filter
|
||||
if(user.client) // If for some reason they logged out mid-scan the box will be gone anyways.
|
||||
delete_box(box_segments, user.client)
|
||||
grabbed_entity = null
|
||||
COOLDOWN_START(src, ghost_cooldown, 10 SECONDS) // Arbitrary cooldown to prevent spam. Adjust as needed.
|
||||
|
||||
/atom/proc/incorporeal_grab(mob/user)
|
||||
if(is_incorporeal())
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/mob/observer/dead/incorporeal_grab(mob/user) // Dead mobs can't be scanned.
|
||||
if(admin_ghosted || !interact_with_world)
|
||||
to_chat(user, span_notice("No entity detected!")) //The goggles ARE listed as unreliable. You could just be seeing static.
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
//The backpack the gun is (typically) attached to!
|
||||
/obj/item/proton_pack
|
||||
name = "proton pack"
|
||||
desc = "A complex backpack that houses a miniature antimatter reactor to power a spectral capture device."
|
||||
icon = 'icons/obj/technomancer.dmi'
|
||||
default_worn_icon = 'icons/inventory/back/mob.dmi'
|
||||
icon_state = "technomancer_core"
|
||||
item_state = "proton_pack"
|
||||
slot_flags = SLOT_BACK
|
||||
preserve_item = 1
|
||||
w_class = ITEMSIZE_LARGE
|
||||
var/obj/item/ghost_catcher/who_ya_gunna_call = /obj/item/ghost_catcher
|
||||
|
||||
/obj/item/proton_pack/Initialize(mapload)
|
||||
AddComponent(/datum/component/tethered_item, who_ya_gunna_call)
|
||||
. = ..()
|
||||
|
||||
/obj/item/proton_pack/attack_hand(mob/living/user)
|
||||
// See important note in tethered_item.dm
|
||||
if(SEND_SIGNAL(src,COMSIG_ITEM_ATTACK_SELF,user) & COMPONENT_CANCEL_ATTACK_CHAIN)
|
||||
return TRUE
|
||||
. = ..()
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
// Draws a box showing the limits of movement while scanning something.
|
||||
// Only the client supplied will see the box.
|
||||
/obj/item/cataloguer/proc/draw_box(atom/A, box_size, client/C)
|
||||
/proc/draw_box(atom/A, box_size, client/C)
|
||||
. = list()
|
||||
// Things moved with pixel_[x|y] will move the box, so this is to correct that.
|
||||
var/pixel_x_correction = -A.pixel_x
|
||||
@@ -47,7 +47,7 @@
|
||||
#undef ICON_SIZE
|
||||
|
||||
// Draws an individual segment of the box.
|
||||
/obj/item/cataloguer/proc/draw_line(atom/A, line_dir, line_pixel_x, line_pixel_y, client/C)
|
||||
/proc/draw_line(atom/A, line_dir, line_pixel_x, line_pixel_y, client/C)
|
||||
var/image/line = image(icon = 'icons/effects/effects.dmi', loc = A, icon_state = "stripes", dir = line_dir)
|
||||
line.pixel_x = line_pixel_x
|
||||
line.pixel_y = line_pixel_y
|
||||
@@ -58,11 +58,11 @@
|
||||
return line
|
||||
|
||||
// Removes the box that was generated before from the client.
|
||||
/obj/item/cataloguer/proc/delete_box(list/box_segments, client/C)
|
||||
/proc/delete_box(list/box_segments, client/C)
|
||||
for(var/i in box_segments)
|
||||
C.images -= i
|
||||
qdel(i)
|
||||
|
||||
/obj/item/cataloguer/proc/color_box(list/box_segments, new_color, new_time)
|
||||
/proc/color_box(list/box_segments, new_color, new_time)
|
||||
for(var/i in box_segments)
|
||||
animate(i, color = new_color, time = new_time)
|
||||
|
||||
@@ -315,31 +315,86 @@
|
||||
icon = 'icons/obj/entrepreneur.dmi'
|
||||
icon_state = "emf"
|
||||
var/emf = 5
|
||||
var/turf/last_used = 0
|
||||
var/emf_change = 0
|
||||
///If our scanner will accurately detect ghosts or not.
|
||||
var/advanced = FALSE
|
||||
///How far our we search. 0 = own turf.
|
||||
var/detection_range = 0
|
||||
|
||||
///How often we can use the EMF actively.
|
||||
COOLDOWN_DECLARE(scan_cooldown)
|
||||
|
||||
/obj/item/entrepreneur/emf/examine(mob/user)
|
||||
. = ..()
|
||||
switch(emf)
|
||||
if(-1000 to 20)
|
||||
. += span_info("The EMF reader is very low, reading [emf]mG.")
|
||||
if(20 to 40)
|
||||
. += span_info("The EMF reader is low, reading [emf]mG.")
|
||||
if(40 to 60)
|
||||
. += span_info(span_red("The EMF reader is reading moderate interference, reading [emf]mG."))
|
||||
if(60 to 80)
|
||||
. += span_info(span_red("The EMF reader is reading high interference, reading [emf]mG."))
|
||||
if(80 to 1000)
|
||||
. += span_info(span_red("The EMF reader is reading extremely high interference, reading [emf]mG."))
|
||||
|
||||
/obj/item/entrepreneur/emf/Initialize(mapload)
|
||||
. = ..()
|
||||
emf = rand(1,100)
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
/obj/item/entrepreneur/emf/process()
|
||||
search_for_ghosts()
|
||||
|
||||
/obj/item/entrepreneur/emf/attack_self(mob/user)
|
||||
. = ..(user)
|
||||
if(.)
|
||||
return TRUE
|
||||
if(!last_used)
|
||||
emf = rand(1,100)
|
||||
last_used = get_turf(user)
|
||||
var/current_used = get_turf(user)
|
||||
var/mob/observer/spooky = locate() in current_used
|
||||
if(last_used != current_used)
|
||||
if(emf >= 100)
|
||||
emf = 100
|
||||
if(emf <= 20)
|
||||
emf = 20
|
||||
if(spooky)
|
||||
if(COOLDOWN_FINISHED(src, scan_cooldown))
|
||||
search_for_ghosts(user)
|
||||
else
|
||||
to_chat(user, span_warning("Your EMF scanner is recharging. The current reading is [emf]mG."))
|
||||
|
||||
/obj/item/entrepreneur/emf/proc/search_for_ghosts(mob/user)
|
||||
var/turf/our_turf = get_turf(src)
|
||||
if(!our_turf)
|
||||
return
|
||||
|
||||
///How many ghosts are around us.
|
||||
var/ghosts_present = 0
|
||||
///How much we'll increase/decrease the EMF reading.
|
||||
var/emf_change = 0
|
||||
|
||||
//Loop for the 'advanced' emfs, which detect actual ghosts/phasers.
|
||||
if(advanced)
|
||||
for(var/mob/living/entity in range(detection_range, our_turf))
|
||||
if(isobserver(entity))
|
||||
var/mob/observer/dead/ghost = entity
|
||||
if(ghost.following || !ghost.interact_with_world || ghost.admin_ghosted) //Ghosts orbiting us or someone else, or have opted out of interactions.
|
||||
continue
|
||||
ghosts_present++
|
||||
if(entity.is_incorporeal())
|
||||
ghosts_present++
|
||||
|
||||
if(emf >= 100)
|
||||
emf = 100
|
||||
if(emf <= 20)
|
||||
emf = 20
|
||||
|
||||
if(ghosts_present)
|
||||
if(advanced)
|
||||
emf_change = rand(3 * ghosts_present, 10 * ghosts_present)
|
||||
else
|
||||
emf_change = rand(-15,20) //Trend upwards but not by enough to prove ghosts actually exist
|
||||
else
|
||||
if(advanced)
|
||||
emf_change = rand(-5, -1)
|
||||
else
|
||||
emf_change = rand(-20,15) //Trend downwards
|
||||
last_used = get_turf(user)
|
||||
emf = (emf + emf_change)
|
||||
update_icon()
|
||||
to_chat(user, span_notice("You update the EMF scanner and check the reading. It reads [emf]mG!"))
|
||||
emf = (emf + emf_change)
|
||||
update_icon()
|
||||
if(user)
|
||||
to_chat(user, span_notice("You update the EMF scanner and check the reading. It reads [emf]mG!"))
|
||||
COOLDOWN_START(src, scan_cooldown, 5 SECONDS)
|
||||
|
||||
/obj/item/entrepreneur/emf/update_icon()
|
||||
switch(emf)
|
||||
@@ -361,10 +416,14 @@
|
||||
icon = 'icons/obj/entrepreneur.dmi'
|
||||
icon_state = "spirit_board"
|
||||
var/list/possible_results = list("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","Yes","No","1","2","3","4","5","6","7","8","9","0","Nothing")
|
||||
///What the next letter/number will be.
|
||||
var/next_result = 0
|
||||
///If ghosts can interact with it.
|
||||
var/ghost_enabled = 1
|
||||
///If the board will always display what ghosts put
|
||||
var/accurate = FALSE
|
||||
|
||||
/obj/item/entrepreneur/spirit_board/attackby(obj/item/reagent_containers/food/drinks/W as obj, mob/living/user as mob)
|
||||
/obj/item/entrepreneur/spirit_board/attackby(obj/item/reagent_containers/food/drinks/W, mob/living/user)
|
||||
if(!istype(user))
|
||||
return 0
|
||||
if(!istype(W))
|
||||
@@ -392,7 +451,7 @@
|
||||
to_chat(user, span_warning("You cannot interact with this board because you are banned from playing ghost roles."))
|
||||
return
|
||||
next_result = tgui_input_list(user, "What should it land on next?", "Next result", possible_results)
|
||||
if(!is_admin(user)) //admins can bypass this for event stuff
|
||||
if(!is_admin(user) || !accurate) //admins can bypass this for event stuff
|
||||
if(prob(25))
|
||||
next_result = 0 //25% chance for the ghost to fail to manipulate the board
|
||||
|
||||
|
||||
@@ -184,7 +184,6 @@
|
||||
w_class = ITEMSIZE_NORMAL
|
||||
requires_wield = FALSE
|
||||
|
||||
|
||||
/obj/item/kinetic_crusher/machete
|
||||
// general purpose. cleaves though
|
||||
name = "proto-kinetic machete"
|
||||
@@ -209,9 +208,6 @@
|
||||
update_item_state = FALSE
|
||||
slot_flags = SLOT_BELT
|
||||
|
||||
|
||||
|
||||
|
||||
/obj/item/kinetic_crusher/machete/gauntlets
|
||||
// did someone say single target damage
|
||||
name = "\improper proto-kinetic gear"
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
icon = 'icons/mob/ghost.dmi'
|
||||
icon_state = "ghost"
|
||||
stat = DEAD
|
||||
canmove = 0
|
||||
blinded = 0
|
||||
canmove = FALSE
|
||||
blinded = FALSE
|
||||
anchored = TRUE // don't get pushed around
|
||||
var/list/visibleChunks = list()
|
||||
var/datum/visualnet/ghost/visualnet
|
||||
@@ -24,21 +24,23 @@
|
||||
var/started_as_observer //This variable is set to 1 when you enter the game as an observer.
|
||||
//If you died in the game and are a ghsot - this will remain as null.
|
||||
//Note that this is not a reliable way to determine if admins started as observers, since they change mobs a lot.
|
||||
var/has_enabled_antagHUD = 0
|
||||
var/medHUD = 0
|
||||
var/secHUD = 0
|
||||
var/antagHUD = 0
|
||||
universal_speak = 1
|
||||
var/has_enabled_antagHUD = FALSE
|
||||
var/medHUD = FALSE
|
||||
var/secHUD = FALSE
|
||||
var/antagHUD = FALSE
|
||||
universal_speak = TRUE
|
||||
var/atom/movable/following = null
|
||||
var/admin_ghosted = 0
|
||||
var/anonsay = 0
|
||||
var/ghostvision = 1 //is the ghost able to see things humans can't?
|
||||
var/admin_ghosted = FALSE
|
||||
var/anonsay = FALSE
|
||||
var/ghostvision = TRUE //is the ghost able to see things humans can't?
|
||||
var/lighting_alpha = 255
|
||||
incorporeal_move = 1
|
||||
|
||||
var/is_manifest = 0 //If set to 1, the ghost is able to whisper. Usually only set if a cultist drags them through the veil.
|
||||
var/toggled_invisible = 0
|
||||
incorporeal_move = TRUE
|
||||
/// If set to TRUE, the ghost is able to whisper. Usually only set if a cultist drags them through the veil.
|
||||
var/is_manifest = FALSE
|
||||
var/toggled_invisible = FALSE
|
||||
var/ghost_sprite = null
|
||||
/// If TRUE, the ghost can be interacted with by the corporeal world (ghost traps, photon pack, etc)
|
||||
var/interact_with_world = TRUE
|
||||
var/last_revive_notification = null // world.time of last notification, used to avoid spamming players from defibs or cloners.
|
||||
var/cleanup_timer // Refernece to a timer that will delete this mob if no client returns
|
||||
var/selecting_ghostrole = FALSE
|
||||
@@ -568,11 +570,11 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
|
||||
return ..()
|
||||
|
||||
/mob/proc/check_holy(var/turf/T)
|
||||
return 0
|
||||
return FALSE
|
||||
|
||||
/mob/observer/dead/check_holy(var/turf/T)
|
||||
if(check_rights_for(src.client, R_ADMIN|R_FUN|R_EVENT))
|
||||
return 0
|
||||
return FALSE
|
||||
|
||||
return (T && T.holy) && (is_manifest || (mind in GLOB.cult.current_antagonists))
|
||||
|
||||
@@ -783,6 +785,21 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
|
||||
var/image/J = image('icons/mob/mob.dmi', loc = src, icon_state = icon)
|
||||
client.images += J
|
||||
|
||||
/mob/observer/dead/verb/toggle_interactions()
|
||||
|
||||
set name = "Toggle Interactions"
|
||||
set desc = "Allows you to toggle if you wish for the corporeal world to interact with you!"
|
||||
set category = "Ghost.Settings"
|
||||
toggle_ghost_interactions()
|
||||
|
||||
/mob/observer/dead/proc/toggle_ghost_interactions()
|
||||
if(is_manifest)
|
||||
to_chat(src, span_info("You are currently manifested into the world and can not toggle this!"))
|
||||
return
|
||||
|
||||
interact_with_world = !interact_with_world
|
||||
to_chat(src, span_info("You will [interact_with_world ? "now" : "no longer"] be able to be interacted with by the corporeal world!"))
|
||||
|
||||
/mob/observer/dead/verb/toggle_visibility()
|
||||
|
||||
set name = "Toggle Visibility"
|
||||
|
||||
@@ -1438,7 +1438,7 @@
|
||||
if(!.)
|
||||
return
|
||||
|
||||
client.screen.Remove(GLOB.global_hud.blurry, GLOB.global_hud.druggy, GLOB.global_hud.vimpaired, GLOB.global_hud.darkMask, GLOB.global_hud.nvg, GLOB.global_hud.thermal, GLOB.global_hud.meson, GLOB.global_hud.science, GLOB.global_hud.material, GLOB.global_hud.whitense)
|
||||
client.screen.Remove(GLOB.global_hud.blurry, GLOB.global_hud.druggy, GLOB.global_hud.vimpaired, GLOB.global_hud.darkMask, GLOB.global_hud.nvg, GLOB.global_hud.thermal, GLOB.global_hud.meson, GLOB.global_hud.science, GLOB.global_hud.material, GLOB.global_hud.whitense, GLOB.global_hud.heavy_whitense)
|
||||
|
||||
if(istype(client.eye,/obj/machinery/camera))
|
||||
var/obj/machinery/camera/cam = client.eye
|
||||
|
||||
@@ -285,3 +285,6 @@
|
||||
var/vent_crawl_time = 4.5 SECONDS // Time to animate entering a vent
|
||||
VAR_PRIVATE/is_motion_tracking = FALSE // Prevent multiple unsubs and resubs, also used to check if the vis layer is enabled, use has_motiontracking() to get externally.
|
||||
VAR_PRIVATE/wants_to_see_motion_echos = TRUE
|
||||
|
||||
var/is_slipping = FALSE
|
||||
var/slip_protect = 1
|
||||
|
||||
@@ -389,6 +389,8 @@
|
||||
is_leaving_belly = FALSE
|
||||
return
|
||||
is_leaving_belly = FALSE
|
||||
if(isghosttrap(mob.loc))
|
||||
return
|
||||
var/turf/mobloc = get_turf(mob)
|
||||
|
||||
if(incorporeal_speed)
|
||||
|
||||
@@ -334,6 +334,16 @@
|
||||
RND_SUBCATEGORY_WEAPONS_AMMO + RND_SUBCATEGORY_WEAPONS_AMMO_PISTOL
|
||||
)
|
||||
|
||||
/datum/design_techweb/pistol_mag_9mm_spectral
|
||||
SET_AMMO_DESIGN_NAMEDESC("pistol magazine (9mm spectral)")
|
||||
id = "pistol_mag_9mm_spectral"
|
||||
materials = list(MAT_STEEL = 750)
|
||||
build_type = AUTOLATHE
|
||||
build_path = /obj/item/ammo_magazine/m9mm/spectral
|
||||
category = list(
|
||||
RND_SUBCATEGORY_WEAPONS_AMMO + RND_SUBCATEGORY_WEAPONS_AMMO_PISTOL
|
||||
)
|
||||
|
||||
// Small mags for small or old guns. These are all hidden because they are traitor mags and will otherwise just clutter the Autolathe.
|
||||
|
||||
/datum/design_techweb/pistol_mag_compact_9mm
|
||||
@@ -478,6 +488,16 @@
|
||||
)
|
||||
departmental_flags = DEPARTMENT_BITFLAG_SECURITY | DEPARTMENT_BITFLAG_SCIENCE
|
||||
|
||||
/datum/design_techweb/smg_mag_9mm_spectral
|
||||
SET_AMMO_DESIGN_NAMEDESC("SMG magazine (9mm spectral)")
|
||||
id = "smg_mag_9mm_spectral"
|
||||
materials = list(MAT_STEEL = 1500)
|
||||
build_type = AUTOLATHE
|
||||
build_path = /obj/item/ammo_magazine/m9mml/spectral
|
||||
category = list(
|
||||
RND_SUBCATEGORY_WEAPONS_AMMO + RND_SUBCATEGORY_WEAPONS_AMMO_RIFLE
|
||||
)
|
||||
|
||||
/////// 10mm
|
||||
|
||||
/datum/design_techweb/smg_mag_10m
|
||||
@@ -987,6 +1007,16 @@
|
||||
RND_SUBCATEGORY_WEAPONS_AMMO + RND_SUBCATEGORY_WEAPONS_AMMO_LOADERS
|
||||
)
|
||||
|
||||
/datum/design_techweb/loader_357_spectral
|
||||
SET_AMMO_DESIGN_NAMEDESC("speedloader (.357 spectral)")
|
||||
id = "loader_357_spectral"
|
||||
materials = list(MAT_STEEL = 1575)
|
||||
build_type = AUTOLATHE
|
||||
build_path = /obj/item/ammo_magazine/s357/spectral
|
||||
category = list(
|
||||
RND_SUBCATEGORY_WEAPONS_AMMO + RND_SUBCATEGORY_WEAPONS_AMMO_LOADERS
|
||||
)
|
||||
|
||||
/////// 44
|
||||
|
||||
/datum/design_techweb/loader_44
|
||||
@@ -1013,6 +1043,16 @@
|
||||
RND_SUBCATEGORY_WEAPONS_AMMO + RND_SUBCATEGORY_WEAPONS_AMMO_LOADERS
|
||||
)
|
||||
|
||||
/datum/design_techweb/loader_44_spectral
|
||||
SET_AMMO_DESIGN_NAMEDESC("speedloader (.44 spectral)")
|
||||
id = "loader_44_spectral"
|
||||
materials = list(MAT_STEEL = 1575)
|
||||
build_type = AUTOLATHE
|
||||
build_path = /obj/item/ammo_magazine/s44/spectral
|
||||
category = list(
|
||||
RND_SUBCATEGORY_WEAPONS_AMMO + RND_SUBCATEGORY_WEAPONS_AMMO_LOADERS
|
||||
)
|
||||
|
||||
///////////////////
|
||||
/*Rifle Ammoboxes*/
|
||||
///////////////////
|
||||
@@ -1071,6 +1111,16 @@
|
||||
RND_SUBCATEGORY_WEAPONS_AMMO + RND_SUBCATEGORY_WEAPONS_AMMO_BOXES
|
||||
)
|
||||
|
||||
/datum/design_techweb/ammobox_38_spectral
|
||||
SET_AMMO_DESIGN_NAMEDESC("ammo box (.38 spectral)")
|
||||
id = "ammobox_38_spectral"
|
||||
materials = list(MAT_STEEL = 1440)
|
||||
build_type = AUTOLATHE
|
||||
build_path = /obj/item/ammo_magazine/ammo_box/b38/spectral
|
||||
category = list(
|
||||
RND_SUBCATEGORY_WEAPONS_AMMO + RND_SUBCATEGORY_WEAPONS_AMMO_BOXES
|
||||
)
|
||||
|
||||
/*
|
||||
* 10mm
|
||||
*/
|
||||
@@ -1148,6 +1198,16 @@
|
||||
RND_SUBCATEGORY_WEAPONS_AMMO + RND_SUBCATEGORY_WEAPONS_AMMO_BOXES
|
||||
)
|
||||
|
||||
/datum/design_techweb/ammobox_44_spectral
|
||||
SET_AMMO_DESIGN_NAMEDESC("ammo box (.44 spectral)")
|
||||
id = "ammobox_44_spectral"
|
||||
materials = list(MAT_STEEL = 1440)
|
||||
build_type = AUTOLATHE
|
||||
build_path = /obj/item/ammo_magazine/ammo_box/b44/spectral
|
||||
category = list(
|
||||
RND_SUBCATEGORY_WEAPONS_AMMO + RND_SUBCATEGORY_WEAPONS_AMMO_BOXES
|
||||
)
|
||||
|
||||
/*
|
||||
* .45
|
||||
*/
|
||||
|
||||
@@ -1103,3 +1103,60 @@
|
||||
RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_RANGED
|
||||
)
|
||||
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
|
||||
|
||||
//Standalone photon rifle. Not unlockable for now.
|
||||
/datum/design_techweb/photon_rifle
|
||||
name = "Photon Rifle"
|
||||
id = "ghost_gun"
|
||||
build_type = PROTOLATHE
|
||||
materials = list(MAT_STEEL = 8000, MAT_GLASS = 8000, MAT_PHORON = 8000, MAT_DIAMOND = 6000, MAT_VERDANTIUM = 6000) //Expensive.
|
||||
build_path = /obj/item/ghost_catcher
|
||||
category = list(
|
||||
RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_MISC
|
||||
)
|
||||
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
|
||||
|
||||
/datum/design_techweb/photon_pack
|
||||
name = "Proton Pack"
|
||||
id = "ghost_pack"
|
||||
build_type = PROTOLATHE
|
||||
materials = list(MAT_STEEL = 4000, MAT_GLASS = 4000, MAT_PHORON = 4000, MAT_DIAMOND = 2000, MAT_VERDANTIUM = 2000) //Expensive.
|
||||
build_path = /obj/item/proton_pack
|
||||
category = list(
|
||||
RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_MISC
|
||||
)
|
||||
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
|
||||
|
||||
/datum/design_techweb/spectral_trap
|
||||
name = "Spectral Trap"
|
||||
id = "ghost_trap"
|
||||
build_type = PROTOLATHE
|
||||
materials = list(MAT_STEEL = 2000, MAT_GLASS = 2000, MAT_PHORON = 1000, MAT_DIAMOND = 500)
|
||||
build_path = /obj/item/ghost_trap
|
||||
category = list(
|
||||
RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_MISC
|
||||
)
|
||||
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
|
||||
|
||||
|
||||
/datum/design_techweb/advanced_emf
|
||||
name = "Advanced EMF Reader"
|
||||
id = "ghost_emf"
|
||||
build_type = PROTOLATHE
|
||||
materials = list(MAT_STEEL = 5000, MAT_GLASS = 5000) //Assumed you're making this with the ghost_gun, so it has similar values.
|
||||
build_path = /obj/item/entrepreneur/emf/professional
|
||||
category = list(
|
||||
RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_MISC
|
||||
)
|
||||
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
|
||||
|
||||
/datum/design_techweb/spectral_goggles
|
||||
name = "Spectral Goggles"
|
||||
id = "ghost_goggles"
|
||||
build_type = PROTOLATHE
|
||||
materials = list(MAT_STEEL = 2000, MAT_GLASS = 2000, MAT_PHORON = 2000, MAT_DIAMOND = 500) //Assumed you're making this with the ghost_gun, so it has similar values.
|
||||
build_path = /obj/item/clothing/glasses/ghost
|
||||
category = list(
|
||||
RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_MISC
|
||||
)
|
||||
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
//For spooky, ghost related experiments.
|
||||
/datum/experiment/ghost_capture
|
||||
name = "Spectral Capture"
|
||||
description = "There's supposed 'ghosts' and 'specters' roaming around this section of space! Let's capture one of these entities for science!"
|
||||
exp_tag = "Spectral"
|
||||
allowed_experimentors = list(/obj/machinery/rnd/server)
|
||||
|
||||
/datum/experiment/ghost_capture/is_complete()
|
||||
return completed
|
||||
|
||||
/datum/experiment/ghost_capture/New()
|
||||
..()
|
||||
RegisterSignal(SSdcs, COMSIG_GLOB_GHOST_CAPTURED, PROC_REF(ghost_captured))
|
||||
RegisterSignal(SSdcs, COMSIG_GLOB_WIGHT_CAPTURED, PROC_REF(ghost_captured))
|
||||
return TRUE
|
||||
|
||||
/datum/experiment/ghost_capture/proc/ghost_captured(datum/source, mob/captured_entity)
|
||||
SIGNAL_HANDLER
|
||||
// We don't actually care about the mob that's captured, just that something was captured.
|
||||
completed = TRUE
|
||||
UnregisterSignal(SSdcs, COMSIG_GLOB_GHOST_CAPTURED)
|
||||
UnregisterSignal(SSdcs, COMSIG_GLOB_WIGHT_CAPTURED)
|
||||
@@ -24,6 +24,13 @@
|
||||
stored_research.techweb_servers |= src
|
||||
name += " [num2hex(rand(1,65535), -1)]" //gives us a random four-digit hex number as part of the name. Y'know, for fluff.
|
||||
|
||||
//So we can be listening for experiments that are global.
|
||||
AddComponent(/datum/component/experiment_handler, \
|
||||
allowed_experiments = list(/datum/experiment/ghost_capture), \
|
||||
config_mode = EXPERIMENT_CONFIG_UI, \
|
||||
config_flags = EXPERIMENT_CONFIG_ALWAYS_ACTIVE)
|
||||
|
||||
|
||||
/obj/machinery/rnd/server/Destroy()
|
||||
if(stored_research)
|
||||
stored_research.techweb_servers -= src
|
||||
|
||||
@@ -178,3 +178,49 @@
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS)
|
||||
announce_channels = list(CHANNEL_SCIENCE)
|
||||
*/
|
||||
|
||||
//Ghost catching stuff! Disabled on Virgo, but used downstream.
|
||||
/datum/techweb_node/ghost_basic
|
||||
id = TECHWEB_NODE_GHOST_BASIC
|
||||
display_name = "Spectral Detection and Containment"
|
||||
description = "Determining high-energy signatures indicative of spectral entities and developing methods to safely contain them."
|
||||
prereq_ids = list(TECHWEB_NODE_APPLIED_BLUESPACE, TECHWEB_NODE_APPLIED_ANOMALY_HARVESTING)
|
||||
design_ids = list(
|
||||
"ghost_trap",
|
||||
"ghost_emf",
|
||||
)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS)
|
||||
announce_channels = list(CHANNEL_SCIENCE, CHANNEL_SECURITY)
|
||||
hidden = TRUE //Hidden on Virgo
|
||||
|
||||
/datum/techweb_node/ghost_advanced
|
||||
id = TECHWEB_NODE_GHOST_ADVANCED
|
||||
display_name = "Spectral Hunting"
|
||||
description = "Developing advanced weaponry for tracking and engaging spectral entities."
|
||||
prereq_ids = list(TECHWEB_NODE_GHOST_BASIC)
|
||||
design_ids = list(
|
||||
"ghost_pack",
|
||||
"ghost_goggles",
|
||||
)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS)
|
||||
announce_channels = list(CHANNEL_SCIENCE, CHANNEL_SECURITY)
|
||||
required_experiments = list(/datum/experiment/ghost_capture)
|
||||
hidden = TRUE //Hidden on Virgo
|
||||
|
||||
/datum/techweb_node/ghost_rounds
|
||||
id = TECHWEB_NODE_GHOST_ROUNDS
|
||||
display_name = "Spectral Rounds"
|
||||
description = "Having reached the pinnacle of spectral research, we can now produce specialized ammunition for ghost hunting."
|
||||
prereq_ids = list(TECHWEB_NODE_GHOST_ADVANCED)
|
||||
design_ids = list(
|
||||
"pistol_mag_9mm_spectral",
|
||||
"smg_mag_9mm_spectral",
|
||||
"loader_357_spectral",
|
||||
"loader_44_spectral",
|
||||
"ammobox_38_spectral",
|
||||
"ammobox_44_spectral",
|
||||
)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS)
|
||||
announce_channels = list(CHANNEL_SCIENCE, CHANNEL_SECURITY)
|
||||
//required_experiments = list(ADD_A_GOOD_EXPERIMENT_HERE)
|
||||
hidden = TRUE //Hidden on Virgo
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
// Slipnoms from chompstation downstream, credit to cadyn for the original PR.
|
||||
|
||||
/mob/living
|
||||
var/is_slipping = FALSE
|
||||
var/slip_protect = 1
|
||||
|
||||
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
@@ -1735,6 +1735,11 @@
|
||||
#include "code\game\objects\items\devices\scanners\reagents.dm"
|
||||
#include "code\game\objects\items\devices\scanners\sleevemate.dm"
|
||||
#include "code\game\objects\items\devices\scanners\slime.dm"
|
||||
#include "code\game\objects\items\ghost_hunting\ammo.dm"
|
||||
#include "code\game\objects\items\ghost_hunting\equipment.dm"
|
||||
#include "code\game\objects\items\ghost_hunting\goggles.dm"
|
||||
#include "code\game\objects\items\ghost_hunting\trap.dm"
|
||||
#include "code\game\objects\items\ghost_hunting\weapons.dm"
|
||||
#include "code\game\objects\items\robot\robot_items.dm"
|
||||
#include "code\game\objects\items\robot\robot_parts.dm"
|
||||
#include "code\game\objects\items\robot\robot_upgrades.dm"
|
||||
@@ -4560,6 +4565,7 @@
|
||||
#include "code\modules\research\tg\experisci\experiment\instances\ordinance.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\instances\people.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\instances\physical.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\instances\spectral.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\instances\teleporting.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\types\experiment.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\types\ordinance.dm"
|
||||
@@ -4874,7 +4880,6 @@
|
||||
#include "code\modules\vore\eating\mob_vr.dm"
|
||||
#include "code\modules\vore\eating\silicon_vr.dm"
|
||||
#include "code\modules\vore\eating\simple_animal_vr.dm"
|
||||
#include "code\modules\vore\eating\slipvore_vr.dm"
|
||||
#include "code\modules\vore\eating\soulcatcher.dm"
|
||||
#include "code\modules\vore\eating\soulcatcher_import.dm"
|
||||
#include "code\modules\vore\eating\soulcatcher_mob.dm"
|
||||
|
||||