mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-25 22:19:30 +01:00
creates more deticated folders for tools and weaponry and move dm files into them (#94804)
## About The Pull Request Hello hello the evil folder mover goat is back, i will destroy years worth of muscle memory. Items is an folder alot of stuff that really didn't get a spefific location. Items/Weaponry Contains Shields.dm Items/Weaponry/Melee Contains (Most of the extra dm files come from me splitting misc/weaponry.dm into its own categories killing offf weaponry.dm in its whole) baseball_bat.dm, baton.dm, chainofcommand.dm, chainsaw.dm, claymore.dm, dualsaber.dm, energy.dm fireaxe.dm, high_frequency_blade.dm, his_grace.dm, katana.dm, knives.dm, misc.dm, powerfist.dm, sabre.dm soulscythe.dm, spear.dm, supermatter_sword.dm. Items/weaponry/ranged Contains flamethrower.dm, pnuematic_cannon.dm, throwing_star.dm items/tools Contains control_wand.dm, extinguisher.dm, inducer.dm, spess_knife.dm, theft_tools.dm items/tools/engineering Contains airlock_painter.dm, decal_painter.dm, paintable_decals.dm, crowbar.dm, screwdriver.dm, weldingtool.dm wirebbrush.dm, wirecutters.dm, wrench.dm items/tools/janitorial Contains broom.dm, janitor_key.dm items/tools/medical Contains bodybag.dm, cane.dm, defib.dm, reflex_hammer.dm boxcutter.dm is merged into knives.dm Kitchen.dm renamed into kitchen utensils and moved into food and drinks module surgery_tray.dm moved into surgery module banhammer/extendo hand moved from weaponry into toys.dm balloon mallet moved into clown_items.dm ectoplasm moved from weaponry.dm into soulstone.dm (since thats the resulting residue when a shade dies, and not really a weapon) ## Why It's Good For The Game So things are way easier to navigate ## Changelog
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
#define WAND_OPEN "open"
|
||||
#define WAND_BOLT "bolt"
|
||||
#define WAND_EMERGENCY "emergency"
|
||||
#define WAND_SHOCK "shock"
|
||||
#define WAND_DEPOWER "depower"
|
||||
|
||||
/obj/item/door_remote
|
||||
icon_state = "remote"
|
||||
base_icon_state = "remote"
|
||||
inhand_icon_state = "electronic"
|
||||
lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi'
|
||||
icon = 'icons/obj/devices/remote.dmi'
|
||||
name = "control wand"
|
||||
desc = "A remote for controlling a set of airlocks."
|
||||
w_class = WEIGHT_CLASS_TINY
|
||||
drop_sound = 'sound/items/door_remote/door_remote_drop1.ogg'
|
||||
pickup_sound = 'sound/items/door_remote/door_remote_pick_up1.ogg'
|
||||
|
||||
var/department = "civilian"
|
||||
var/mode = WAND_OPEN
|
||||
var/region_access = REGION_GENERAL
|
||||
var/list/access_list = list()
|
||||
// the trim of the owner of this remote, if applied
|
||||
var/datum/id_trim/job/owner_trim = null
|
||||
// areas that this remote is the exclusive owner of
|
||||
var/list/area/our_domain = null
|
||||
// areas specifically considered as restricted from a remote
|
||||
// accessing them unless specifically allowed (vault, security, etc)
|
||||
var/static/list/area/restricted_areas = list(
|
||||
/area/station/command/bridge, /*so Captain's remote isn't totally useless*/
|
||||
/area/station/security, /*so antag RD/HoP/QM/CMO can't easily screw up the brig doors*/
|
||||
/area/station/command/vault, /*aka Vault since it's QM's special thing*/
|
||||
/area/station/ai/satellite/chamber, // these are areas exclusive to RD
|
||||
/area/station/ai/upload, // but sometimes mappers might misconfig their doors with our several dozen access helpers
|
||||
)
|
||||
COOLDOWN_DECLARE(shock_cooldown)
|
||||
/// sound played when mode is switched
|
||||
var/mode_switch_sound = SFX_REMOTE_MODE_SWITCH
|
||||
/// sound played when an action is done
|
||||
var/action_sound = SFX_REMOTE_ACTION
|
||||
|
||||
/obj/item/door_remote/Initialize(mapload)
|
||||
. = ..()
|
||||
update_icon_state()
|
||||
// initialize late to make sure job accesses are fully configured
|
||||
return INITIALIZE_HINT_LATELOAD
|
||||
|
||||
/obj/item/door_remote/LateInitialize()
|
||||
access_list = SSid_access.get_region_access_list(list(region_access))
|
||||
if(!isnull(owner_trim))
|
||||
var/datum/id_trim/job/trim_singlet = SSid_access.trim_singletons_by_path[owner_trim]
|
||||
access_list |= trim_singlet.access
|
||||
|
||||
/obj/item/door_remote/proc/is_my_domain(area/restricted_area)
|
||||
for(var/area/dominion as anything in our_domain)
|
||||
if(istype(restricted_area, dominion))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/obj/item/door_remote/emag_act(mob/user, obj/item/card/emag/emag_card)
|
||||
. = ..()
|
||||
if(obj_flags & EMAGGED)
|
||||
return FALSE
|
||||
balloon_alert(user, "restricted functions unlocked")
|
||||
obj_flags |= EMAGGED
|
||||
update_icon_state()
|
||||
return TRUE
|
||||
|
||||
/obj/item/door_remote/attack_self(mob/user)
|
||||
var/static/list/ops = list(WAND_OPEN = "Open Door", WAND_BOLT = "Toggle Bolts", WAND_EMERGENCY = "Toggle Emergency Access", WAND_SHOCK = "Shock Door", WAND_DEPOWER = "Depower Door")
|
||||
switch(mode)
|
||||
if(WAND_OPEN)
|
||||
mode = WAND_BOLT
|
||||
if(WAND_BOLT)
|
||||
mode = WAND_EMERGENCY
|
||||
if(WAND_EMERGENCY)
|
||||
if(!(obj_flags & EMAGGED))
|
||||
mode = WAND_OPEN
|
||||
else
|
||||
mode = WAND_SHOCK
|
||||
if(WAND_SHOCK)
|
||||
mode = WAND_DEPOWER
|
||||
if(WAND_DEPOWER)
|
||||
mode = WAND_OPEN
|
||||
update_icon_state()
|
||||
balloon_alert(user, "mode: [ops[mode]]")
|
||||
if(mode_switch_sound)
|
||||
playsound(src, mode_switch_sound, 50, TRUE)
|
||||
|
||||
/obj/item/door_remote/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
|
||||
if(!istype(interacting_with, /obj/machinery/door) && !isturf(interacting_with))
|
||||
return NONE
|
||||
return ranged_interact_with_atom(interacting_with, user, modifiers)
|
||||
|
||||
/obj/item/door_remote/omni
|
||||
name = "omni door remote"
|
||||
desc = "This control wand can access any door on the station."
|
||||
department = "omni"
|
||||
region_access = REGION_ALL_STATION
|
||||
our_domain = list( /area/station )
|
||||
|
||||
/obj/item/door_remote/captain
|
||||
name = "command door remote"
|
||||
desc = "A remote for controlling a set of airlocks. Despite its gaudy insignia denoting the Captain as its owner, some fine print \
|
||||
indicates that its access is exclusively relegated to the Bridge and high-security command areas -- an additional byline \
|
||||
specifically excludes Security from the high-security areas. Ironic."
|
||||
department = "command"
|
||||
region_access = REGION_COMMAND
|
||||
our_domain = list(
|
||||
/area/station, // still restricted by limited accesses in REGION_COMMAND
|
||||
)
|
||||
|
||||
/obj/item/door_remote/chief_engineer
|
||||
name = "engineering door remote"
|
||||
desc = "A remote for controlling a set of airlocks. This one smells like burnt flesh and ozone."
|
||||
department = "engi"
|
||||
region_access = REGION_ENGINEERING
|
||||
owner_trim = /datum/id_trim/job/chief_engineer
|
||||
// doesn't need a domain because their specific high-security areas aren't on anyone else's trim but cap
|
||||
|
||||
/obj/item/door_remote/research_director
|
||||
name = "research door remote"
|
||||
desc = "A remote for controlling a set of airlocks. This one is slightly misshapen, as if squeezed by a person possessing ludicrous strength."
|
||||
department = "sci"
|
||||
region_access = REGION_RESEARCH
|
||||
owner_trim = /datum/id_trim/job/research_director
|
||||
our_domain = list(
|
||||
/area/station/ai/satellite/chamber,
|
||||
/area/station/ai/upload,
|
||||
)
|
||||
|
||||
/obj/item/door_remote/head_of_security
|
||||
name = "security door remote"
|
||||
desc = "A remote for controlling a set of airlocks. This one smells like sweat, blood, resentment, and coffee. \
|
||||
Someone appears to have tampered with the identifier."
|
||||
department = "security"
|
||||
region_access = REGION_SECURITY
|
||||
owner_trim = /datum/id_trim/job/head_of_security
|
||||
our_domain = list( /area/station/security )
|
||||
|
||||
/obj/item/door_remote/quartermaster
|
||||
name = "cargo door remote"
|
||||
desc = "Remotely controls airlocks. This remote has additional Vault access. Despite that, holding it makes you feel insecure for some reason."
|
||||
department = "cargo"
|
||||
region_access = REGION_SUPPLY
|
||||
owner_trim = /datum/id_trim/job/quartermaster
|
||||
our_domain = list( /area/station/command/vault )
|
||||
|
||||
/obj/item/door_remote/chief_medical_officer
|
||||
name = "medical door remote"
|
||||
desc = "A remote for controlling a set of airlocks. It has the overpowering odor of blood and, despite its medical insignia, \
|
||||
has absolutely no accompanying odor of disinfectant."
|
||||
department = "med"
|
||||
region_access = REGION_MEDBAY
|
||||
owner_trim = /datum/id_trim/job/chief_medical_officer
|
||||
|
||||
/obj/item/door_remote/head_of_personnel
|
||||
name = "service door remote"
|
||||
desc = "A remote for controlling a set of airlocks. This one smells like printer ink, and fills its holder with the urge \
|
||||
to mysteriously vanish."
|
||||
department = "civilian"
|
||||
region_access = REGION_GENERAL
|
||||
owner_trim = /datum/id_trim/job/head_of_personnel
|
||||
|
||||
/obj/item/door_remote/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
|
||||
var/obj/machinery/door/door
|
||||
if(action_sound)
|
||||
playsound(src, action_sound, 50, TRUE)
|
||||
|
||||
if (istype(interacting_with, /obj/machinery/door))
|
||||
door = interacting_with
|
||||
if (!door.opens_with_door_remote)
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
|
||||
else
|
||||
for (var/obj/machinery/door/door_on_turf in get_turf(interacting_with))
|
||||
if (door_on_turf.opens_with_door_remote)
|
||||
door = door_on_turf
|
||||
break
|
||||
|
||||
if (isnull(door))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
|
||||
if (!door.check_access_list(access_list) || !door.requiresID())
|
||||
interacting_with.balloon_alert(user, "can't access!")
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
|
||||
var/area/door_area = get_area(door)
|
||||
if(is_type_in_list(door_area, restricted_areas) && !is_my_domain(get_area(door)))
|
||||
interacting_with.balloon_alert(user, "can't access!")
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
|
||||
var/obj/machinery/door/airlock/airlock = door
|
||||
|
||||
if (!door.hasPower() || (istype(airlock) && !airlock.canAIControl()))
|
||||
interacting_with.balloon_alert(user, mode == WAND_OPEN ? "it won't budge!" : "nothing happens!")
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
|
||||
switch (mode)
|
||||
if (WAND_OPEN)
|
||||
if (door.density)
|
||||
door.open()
|
||||
else
|
||||
door.close()
|
||||
|
||||
if (WAND_BOLT)
|
||||
if (!istype(airlock))
|
||||
interacting_with.balloon_alert(user, "only airlocks!")
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
|
||||
if (airlock.locked)
|
||||
airlock.unbolt()
|
||||
log_combat(user, airlock, "unbolted", src)
|
||||
else
|
||||
airlock.bolt()
|
||||
log_combat(user, airlock, "bolted", src)
|
||||
|
||||
if (WAND_EMERGENCY)
|
||||
if (!istype(airlock))
|
||||
interacting_with.balloon_alert(user, "only airlocks!")
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
|
||||
airlock.emergency = !airlock.emergency
|
||||
airlock.update_appearance(UPDATE_ICON)
|
||||
|
||||
if (WAND_SHOCK)
|
||||
if (!istype(airlock))
|
||||
interacting_with.balloon_alert(user, "only airlocks!")
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
if (!COOLDOWN_FINISHED(src, shock_cooldown))
|
||||
interacting_with.balloon_alert(user, "shock pulse resetting!")
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
if (airlock.isElectrified())
|
||||
interacting_with.balloon_alert(user, "already electrified!")
|
||||
else
|
||||
airlock.set_electrified(MACHINE_DEFAULT_ELECTRIFY_TIME, user)
|
||||
COOLDOWN_START(src, shock_cooldown, 10 SECONDS)
|
||||
|
||||
if (WAND_DEPOWER)
|
||||
if (!istype(airlock))
|
||||
interacting_with.balloon_alert(user, "only airlocks!")
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
// First hit disrupts main power, backup comes back in ten seconds, if you stick around you can hit backup for 60 more seconds of downtime.
|
||||
if (!airlock.main_power_timer)
|
||||
airlock.loseMainPower()
|
||||
else if (!airlock.backup_power_time)
|
||||
airlock.loseBackupPower()
|
||||
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/obj/item/door_remote/update_icon_state()
|
||||
var/icon_state_mode
|
||||
if(!(obj_flags & EMAGGED))
|
||||
switch(mode)
|
||||
if(WAND_OPEN)
|
||||
icon_state_mode = "open"
|
||||
if(WAND_BOLT)
|
||||
icon_state_mode = "bolt"
|
||||
if(WAND_EMERGENCY)
|
||||
icon_state_mode = "emergency"
|
||||
else
|
||||
icon_state_mode = "emergency"
|
||||
|
||||
icon_state = "[base_icon_state]_[department]_[icon_state_mode]"
|
||||
return ..()
|
||||
|
||||
#undef WAND_OPEN
|
||||
#undef WAND_BOLT
|
||||
#undef WAND_EMERGENCY
|
||||
#undef WAND_SHOCK
|
||||
#undef WAND_DEPOWER
|
||||
@@ -0,0 +1,394 @@
|
||||
/obj/item/extinguisher
|
||||
name = "fire extinguisher"
|
||||
desc = "A traditional red fire extinguisher."
|
||||
icon = 'icons/obj/tools.dmi'
|
||||
icon_state = "fire_extinguisher0"
|
||||
worn_icon_state = "fire_extinguisher"
|
||||
inhand_icon_state = "fire_extinguisher"
|
||||
icon_angle = 90
|
||||
hitsound = 'sound/items/weapons/smash.ogg'
|
||||
pickup_sound = 'sound/items/handling/gas_tank/gas_tank_pick_up.ogg'
|
||||
drop_sound = 'sound/items/handling/gas_tank/gas_tank_drop.ogg'
|
||||
obj_flags = CONDUCTS_ELECTRICITY
|
||||
throwforce = 13
|
||||
w_class = WEIGHT_CLASS_BULKY
|
||||
throw_speed = 2
|
||||
throw_range = 7
|
||||
force = 13
|
||||
demolition_mod = 1.25
|
||||
custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 0.9)
|
||||
attack_verb_continuous = list("slams", "whacks", "bashes", "thunks", "batters", "bludgeons", "thrashes")
|
||||
attack_verb_simple = list("slam", "whack", "bash", "thunk", "batter", "bludgeon", "thrash")
|
||||
dog_fashion = /datum/dog_fashion/back
|
||||
resistance_flags = FIRE_PROOF
|
||||
interaction_flags_click = NEED_DEXTERITY|NEED_HANDS|ALLOW_RESTING
|
||||
/// The max amount of water this extinguisher can hold.
|
||||
var/max_water = 100
|
||||
/// Does the welder extinguisher start with water.
|
||||
var/starting_water = TRUE
|
||||
/// Cooldown between uses.
|
||||
var/last_use = 1
|
||||
/// Chem we use for our extinguishing.
|
||||
var/chem = /datum/reagent/water
|
||||
/// Can we actually fire currently?
|
||||
var/safety = TRUE
|
||||
/// Can we refill this at a water tank?
|
||||
var/refilling = FALSE
|
||||
/// What tank we need to refill this.
|
||||
var/tanktypes = list(
|
||||
/obj/structure/reagent_dispensers/watertank,
|
||||
/obj/structure/reagent_dispensers/plumbed,
|
||||
/obj/structure/reagent_dispensers/water_cooler,
|
||||
)
|
||||
/// something that should be replaced with base_icon_state
|
||||
var/sprite_name = "fire_extinguisher"
|
||||
/// Maximum distance launched water will travel.
|
||||
var/power = 5
|
||||
/// By default, turfs picked from a spray are random, set to TRUE to make it always have at least one water effect per row.
|
||||
var/precision = FALSE
|
||||
/// Sets the cooling_temperature of the water reagent datum inside of the extinguisher when it is refilled.
|
||||
var/cooling_power = 2
|
||||
/// Icon state when inside a tank holder.
|
||||
var/tank_holder_icon_state = "holder_extinguisher"
|
||||
///The sound a fire extinguisher makes when picked up, dropped if there is liquid inside.
|
||||
var/fire_extinguisher_reagent_sloshing_sound = SFX_DEFAULT_LIQUID_SLOSH
|
||||
|
||||
|
||||
/obj/item/extinguisher/Initialize(mapload)
|
||||
. = ..()
|
||||
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/ghettojetpack)
|
||||
|
||||
AddElement(
|
||||
/datum/element/slapcrafting,\
|
||||
slapcraft_recipes = slapcraft_recipe_list,\
|
||||
)
|
||||
|
||||
register_context()
|
||||
|
||||
/obj/item/extinguisher/add_context(atom/source, list/context, obj/item/held_item, mob/user)
|
||||
if(held_item != src)
|
||||
return
|
||||
context[SCREENTIP_CONTEXT_LMB] = "Engage nozzle"
|
||||
context[SCREENTIP_CONTEXT_ALT_LMB] = "Empty"
|
||||
return CONTEXTUAL_SCREENTIP_SET
|
||||
|
||||
/obj/item/extinguisher/dropped(mob/user, silent)
|
||||
. = ..()
|
||||
if(fire_extinguisher_reagent_sloshing_sound && reagents.total_volume > 0)
|
||||
playsound(src, fire_extinguisher_reagent_sloshing_sound, LIQUID_SLOSHING_SOUND_VOLUME, vary = TRUE, ignore_walls = FALSE)
|
||||
|
||||
/obj/item/extinguisher/equipped(mob/user, slot, initial = FALSE)
|
||||
. = ..()
|
||||
if((slot & ITEM_SLOT_HANDS) && fire_extinguisher_reagent_sloshing_sound && reagents.total_volume > 0)
|
||||
playsound(src, fire_extinguisher_reagent_sloshing_sound, LIQUID_SLOSHING_SOUND_VOLUME, vary = TRUE, ignore_walls = FALSE)
|
||||
|
||||
// A secondary attack letting you wind up the extinguisher for a real wallopping to your targets head
|
||||
/obj/item/extinguisher/attack_secondary(mob/living/victim, mob/living/user, params)
|
||||
// This only makes sense for heavier extinguishers
|
||||
if(w_class < WEIGHT_CLASS_BULKY)
|
||||
return SECONDARY_ATTACK_CALL_NORMAL
|
||||
|
||||
if(issilicon(user))
|
||||
return SECONDARY_ATTACK_CALL_NORMAL
|
||||
|
||||
if(!iscarbon(victim))
|
||||
return SECONDARY_ATTACK_CALL_NORMAL
|
||||
|
||||
var/mob/living/carbon/wallopee = victim
|
||||
var/obj/item/bodypart/head/head_to_bash = wallopee.get_bodypart(BODY_ZONE_HEAD)
|
||||
|
||||
if(!head_to_bash)
|
||||
return SECONDARY_ATTACK_CALL_NORMAL
|
||||
|
||||
var/head_name = head_to_bash.name
|
||||
|
||||
if(fire_extinguisher_reagent_sloshing_sound && reagents.total_volume > 0)
|
||||
playsound(src, fire_extinguisher_reagent_sloshing_sound, LIQUID_SLOSHING_SOUND_VOLUME, vary = TRUE, ignore_walls = FALSE)
|
||||
|
||||
log_combat(user, wallopee, "prepared to use a bash attack with a [src] against [wallopee]")
|
||||
|
||||
wallopee.visible_message(span_danger("[user] begins to raise [src] above [wallopee]'s [head_name]."), span_userdanger("[user] begins to raise [src], aiming to cave in your [head_name]!"))
|
||||
|
||||
if(!do_after(user, 2 SECONDS, target = wallopee))
|
||||
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
||||
|
||||
wallopee.visible_message(span_danger("[user] brings [src] heavily down on [wallopee]'s [head_name]."), span_userdanger("[user] brings [src] heavily down on your [head_name]!"))
|
||||
|
||||
var/min_wound = head_to_bash.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_SEVERE, return_value_if_no_wound = 30, wound_source = src)
|
||||
var/max_wound = head_to_bash.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_CRITICAL, return_value_if_no_wound = 50, wound_source = src)
|
||||
|
||||
wallopee.apply_damage(src.force * 3, src.damtype, head_to_bash, wound_bonus = rand(min_wound, max_wound + 10), attacking_item = src)
|
||||
wallopee.emote("scream")
|
||||
log_combat(user, wallopee, "used a bash attack with a [src] against [wallopee]")
|
||||
user.do_attack_animation(wallopee, used_item = src)
|
||||
|
||||
if(fire_extinguisher_reagent_sloshing_sound && reagents.total_volume > 0)
|
||||
playsound(src, fire_extinguisher_reagent_sloshing_sound, LIQUID_SLOSHING_SOUND_VOLUME, vary = TRUE, ignore_walls = FALSE)
|
||||
|
||||
playsound(source = src, soundin = src.hitsound, vol = src.get_clamped_volume(), vary = TRUE)
|
||||
|
||||
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/obj/item/extinguisher/empty
|
||||
starting_water = FALSE
|
||||
|
||||
/obj/item/extinguisher/mini
|
||||
name = "pocket fire extinguisher"
|
||||
desc = "A light and compact fibreglass-framed model fire extinguisher."
|
||||
icon_state = "miniFE0"
|
||||
worn_icon_state = "miniFE"
|
||||
inhand_icon_state = "miniFE"
|
||||
hitsound = null //it is much lighter, after all.
|
||||
obj_flags = NONE //doesn't conduct electricity
|
||||
throwforce = 2
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
force = 3
|
||||
custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT* 0.5, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 0.4)
|
||||
max_water = 30
|
||||
sprite_name = "miniFE"
|
||||
dog_fashion = null
|
||||
|
||||
/obj/item/extinguisher/mini/empty
|
||||
starting_water = FALSE
|
||||
|
||||
/obj/item/extinguisher/crafted
|
||||
name = "Improvised cooling spray"
|
||||
desc = "Spraycan turned coolant dispenser. Can be sprayed on containers to cool them. Refill using water."
|
||||
icon_state = "coolant0"
|
||||
worn_icon_state = "miniFE"
|
||||
inhand_icon_state = "miniFE"
|
||||
hitsound = null //it is much lighter, after all.
|
||||
obj_flags = NONE //doesn't conduct electricity
|
||||
throwforce = 1
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
force = 3
|
||||
custom_materials = null
|
||||
max_water = 30
|
||||
sprite_name = "coolant"
|
||||
dog_fashion = null
|
||||
cooling_power = 1.5
|
||||
power = 3
|
||||
|
||||
/obj/item/extinguisher/crafted/attack_self(mob/user)
|
||||
safety = !safety
|
||||
icon_state = "[sprite_name][!safety]"
|
||||
to_chat(user, "[safety ? "You remove the straw and put it on the side of the cool canister" : "You insert the straw, readying it for use"].")
|
||||
|
||||
/obj/item/extinguisher/proc/refill()
|
||||
if(!chem)
|
||||
return
|
||||
create_reagents(max_water, AMOUNT_VISIBLE)
|
||||
reagents.add_reagent(chem, max_water)
|
||||
|
||||
/obj/item/extinguisher/Initialize(mapload)
|
||||
. = ..()
|
||||
if(tank_holder_icon_state)
|
||||
AddComponent(/datum/component/container_item/tank_holder, tank_holder_icon_state)
|
||||
if(starting_water)
|
||||
refill()
|
||||
else if(chem)
|
||||
create_reagents(max_water, AMOUNT_VISIBLE)
|
||||
|
||||
/obj/item/extinguisher/advanced
|
||||
name = "advanced fire extinguisher"
|
||||
desc = "Used to stop thermonuclear fires from spreading inside your engine."
|
||||
icon_state = "foam_extinguisher0"
|
||||
worn_icon_state = "foam_extinguisher"
|
||||
inhand_icon_state = "foam_extinguisher"
|
||||
tank_holder_icon_state = "holder_foam_extinguisher"
|
||||
dog_fashion = null
|
||||
throwforce = 10
|
||||
w_class = WEIGHT_CLASS_NORMAL
|
||||
force = 10
|
||||
chem = /datum/reagent/firefighting_foam
|
||||
tanktypes = list(
|
||||
/obj/structure/reagent_dispensers/foamtank,
|
||||
/obj/structure/reagent_dispensers/plumbed,
|
||||
)
|
||||
sprite_name = "foam_extinguisher"
|
||||
precision = TRUE
|
||||
|
||||
/obj/item/extinguisher/advanced/empty
|
||||
starting_water = FALSE
|
||||
|
||||
/obj/item/extinguisher/suicide_act(mob/living/carbon/user)
|
||||
if (!safety && (reagents.total_volume >= 1))
|
||||
user.visible_message(span_suicide("[user] puts the nozzle to [user.p_their()] mouth. It looks like [user.p_theyre()] trying to extinguish the spark of life!"))
|
||||
interact_with_atom(user, user)
|
||||
return OXYLOSS
|
||||
else if (safety && (reagents.total_volume >= 1))
|
||||
user.visible_message(span_warning("[user] puts the nozzle to [user.p_their()] mouth... The safety's still on!"))
|
||||
return SHAME
|
||||
else
|
||||
user.visible_message(span_warning("[user] puts the nozzle to [user.p_their()] mouth... [src] is empty!"))
|
||||
return SHAME
|
||||
|
||||
/obj/item/extinguisher/attack_self(mob/user)
|
||||
safety = !safety
|
||||
src.icon_state = "[sprite_name][!safety]"
|
||||
balloon_alert(user, "safety [safety ? "on" : "off"]")
|
||||
return
|
||||
|
||||
/obj/item/extinguisher/attack(mob/M, mob/living/user)
|
||||
if(!user.combat_mode && !safety) //If we're on help intent and going to spray people, don't bash them.
|
||||
return FALSE
|
||||
else
|
||||
return ..()
|
||||
|
||||
/obj/item/extinguisher/attack_atom(obj/attacked_obj, mob/living/user, list/modifiers, list/attack_modifiers)
|
||||
if(AttemptRefill(attacked_obj, user))
|
||||
refilling = TRUE
|
||||
return FALSE
|
||||
else
|
||||
return ..()
|
||||
|
||||
/obj/item/extinguisher/examine(mob/user)
|
||||
. = ..()
|
||||
. += "The safety is [safety ? "on" : "off"]."
|
||||
|
||||
if(reagents.total_volume)
|
||||
. += span_notice("Alt-click to empty it.")
|
||||
|
||||
/obj/item/extinguisher/proc/AttemptRefill(atom/target, mob/user)
|
||||
if(is_type_in_list(target, tanktypes) && target.Adjacent(user))
|
||||
if(reagents.total_volume == reagents.maximum_volume)
|
||||
balloon_alert(user, "already full!")
|
||||
return TRUE
|
||||
// Make sure we're refilling with the proper chem.
|
||||
if(!(target.reagents.has_reagent(chem, check_subtypes = TRUE)))
|
||||
balloon_alert(user, "can't refill with this liquid!")
|
||||
return TRUE
|
||||
var/obj/structure/reagent_dispensers/W = target //will it work?
|
||||
var/transferred = W.reagents.trans_to(src, max_water, transferred_by = user)
|
||||
if(transferred > 0)
|
||||
to_chat(user, span_notice("\The [src] has been refilled by [transferred] units."))
|
||||
playsound(src.loc, 'sound/effects/refill.ogg', 50, TRUE, -6)
|
||||
for(var/datum/reagent/water/R in reagents.reagent_list)
|
||||
R.cooling_temperature = cooling_power
|
||||
else
|
||||
to_chat(user, span_warning("\The [W] is empty!"))
|
||||
|
||||
return TRUE
|
||||
else
|
||||
return FALSE
|
||||
|
||||
/obj/item/extinguisher/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
|
||||
if(interacting_with.loc == user)
|
||||
return NONE
|
||||
// Always skip interaction if it's a bag or table (that's not on fire)
|
||||
if(!(interacting_with.resistance_flags & ON_FIRE) && HAS_TRAIT(interacting_with, TRAIT_COMBAT_MODE_SKIP_INTERACTION))
|
||||
return NONE
|
||||
return ranged_interact_with_atom(interacting_with, user, modifiers)
|
||||
|
||||
/obj/item/extinguisher/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
|
||||
if(refilling)
|
||||
refilling = FALSE
|
||||
return NONE
|
||||
if(safety)
|
||||
return NONE
|
||||
|
||||
if (src.reagents.total_volume < 1)
|
||||
balloon_alert(user, "it's empty!")
|
||||
return .
|
||||
|
||||
if (world.time < src.last_use + 12)
|
||||
return .
|
||||
|
||||
src.last_use = world.time
|
||||
|
||||
playsound(src.loc, 'sound/effects/extinguish.ogg', 75, TRUE, -3)
|
||||
|
||||
var/direction = get_dir(src,interacting_with)
|
||||
|
||||
if(user.buckled && isobj(user.buckled) && !user.buckled.anchored)
|
||||
var/obj/B = user.buckled
|
||||
var/movementdirection = REVERSE_DIR(direction)
|
||||
addtimer(CALLBACK(src, TYPE_PROC_REF(/obj/item/extinguisher, move_chair), B, movementdirection), 0.1 SECONDS)
|
||||
else
|
||||
user.newtonian_move(dir2angle(REVERSE_DIR(direction)))
|
||||
|
||||
//Get all the turfs that can be shot at
|
||||
var/turf/T = get_turf(interacting_with)
|
||||
var/turf/T1 = get_step(T,turn(direction, 90))
|
||||
var/turf/T2 = get_step(T,turn(direction, -90))
|
||||
var/list/the_targets = list(T,T1,T2)
|
||||
if(precision)
|
||||
var/turf/T3 = get_step(T1, turn(direction, 90))
|
||||
var/turf/T4 = get_step(T2,turn(direction, -90))
|
||||
the_targets.Add(T3,T4)
|
||||
|
||||
var/list/water_particles = list()
|
||||
for(var/a in 1 to 5)
|
||||
var/obj/effect/particle_effect/water/extinguisher/water = new /obj/effect/particle_effect/water/extinguisher(get_turf(src))
|
||||
var/my_target = pick(the_targets)
|
||||
water_particles[water] = my_target
|
||||
// If precise, remove turf from targets so it won't be picked more than once
|
||||
if(precision)
|
||||
the_targets -= my_target
|
||||
var/datum/reagents/water_reagents = new /datum/reagents(5)
|
||||
water.reagents = water_reagents
|
||||
water_reagents.my_atom = water
|
||||
reagents.trans_to(water, 1, transferred_by = user)
|
||||
|
||||
//Make em move dat ass, hun
|
||||
move_particles(water_particles)
|
||||
return ITEM_INTERACT_SKIP_TO_ATTACK // You can smack while spraying
|
||||
|
||||
//Particle movement loop
|
||||
/obj/item/extinguisher/proc/move_particles(list/particles)
|
||||
var/delay = 2
|
||||
// Second loop: Get all the water particles and make them move to their target
|
||||
for(var/obj/effect/particle_effect/water/extinguisher/water as anything in particles)
|
||||
water.move_at(particles[water], delay, power)
|
||||
|
||||
//Chair movement loop
|
||||
/obj/item/extinguisher/proc/move_chair(obj/buckled_object, movementdirection)
|
||||
var/datum/move_loop/loop = GLOB.move_manager.move(buckled_object, movementdirection, 1, timeout = 9, flags = MOVEMENT_LOOP_START_FAST, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
|
||||
//This means the chair slowing down is dependant on the extinguisher existing, which is weird
|
||||
//Couldn't figure out a better way though
|
||||
RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(manage_chair_speed))
|
||||
|
||||
/obj/item/extinguisher/proc/manage_chair_speed(datum/move_loop/move/source)
|
||||
SIGNAL_HANDLER
|
||||
switch(source.lifetime)
|
||||
if(4 to 5)
|
||||
source.delay = 2
|
||||
if(1 to 3)
|
||||
source.delay = 3
|
||||
|
||||
/obj/item/extinguisher/click_alt(mob/user)
|
||||
if(!user.is_holding(src))
|
||||
to_chat(user, span_notice("You must be holding [src] in your hands to do this!"))
|
||||
return CLICK_ACTION_BLOCKING
|
||||
EmptyExtinguisher(user)
|
||||
return CLICK_ACTION_SUCCESS
|
||||
|
||||
/obj/item/extinguisher/proc/EmptyExtinguisher(mob/user)
|
||||
if(loc == user && reagents.total_volume)
|
||||
reagents.expose(user.loc, TOUCH)
|
||||
reagents.clear_reagents()
|
||||
user.visible_message(span_notice("[user] empties out [src] onto the floor using the release valve."), span_info("You quietly empty out [src] using its release valve."))
|
||||
|
||||
// Firebot assembly
|
||||
/obj/item/extinguisher/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
||||
if (!istype(tool, /obj/item/bodypart/arm/left/robot) && !istype(tool, /obj/item/bodypart/arm/right/robot))
|
||||
return NONE
|
||||
|
||||
to_chat(user, span_notice("You add [tool] to [src]."))
|
||||
qdel(tool)
|
||||
var/obj/item/bot_assembly/firebot/assembly = new(drop_location())
|
||||
var/held_index = user.is_holding(src)
|
||||
qdel(src)
|
||||
if (held_index)
|
||||
user.put_in_hand(assembly, held_index)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/obj/item/extinguisher/anti
|
||||
name = "fire extender"
|
||||
desc = "A traditional red fire extinguisher. Made in Britain... wait, what?"
|
||||
chem = /datum/reagent/fuel
|
||||
tanktypes = list(
|
||||
/obj/structure/reagent_dispensers/fueltank,
|
||||
/obj/structure/reagent_dispensers/plumbed
|
||||
)
|
||||
cooling_power = 0
|
||||
@@ -0,0 +1,273 @@
|
||||
/obj/item/inducer
|
||||
name = "inducer"
|
||||
desc = "A tool for inductively charging internal power cells and batteries."
|
||||
icon = 'icons/obj/tools.dmi'
|
||||
icon_state = "inducer-engi"
|
||||
inhand_icon_state = "inducer-engi"
|
||||
lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
|
||||
force = 7
|
||||
|
||||
/// Multiplier that determines the speed at which this inducer works at.
|
||||
var/power_transfer_multiplier = 1
|
||||
/// Is the battery hatch opened
|
||||
var/opened = FALSE
|
||||
/// The cell for used in recharging cycles
|
||||
var/obj/item/stock_parts/power_store/powerdevice = /obj/item/stock_parts/power_store/battery/high
|
||||
/// Are we in the process of recharging something
|
||||
var/recharging = FALSE
|
||||
|
||||
/obj/item/inducer/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
if(ispath(powerdevice))
|
||||
powerdevice = new powerdevice(src)
|
||||
|
||||
register_context()
|
||||
|
||||
update_appearance(UPDATE_OVERLAYS)
|
||||
|
||||
/obj/item/inducer/Destroy(force)
|
||||
QDEL_NULL(powerdevice)
|
||||
. = ..()
|
||||
|
||||
/obj/item/inducer/Exited(atom/movable/gone, direction)
|
||||
. = ..()
|
||||
if(gone == powerdevice)
|
||||
powerdevice = null
|
||||
|
||||
/obj/item/inducer/add_context(atom/source, list/context, obj/item/held_item, mob/user)
|
||||
. = NONE
|
||||
|
||||
if(isnull(held_item))
|
||||
if(opened && !QDELETED(powerdevice))
|
||||
context[SCREENTIP_CONTEXT_LMB] = "Remove Cell"
|
||||
. = CONTEXTUAL_SCREENTIP_SET
|
||||
return
|
||||
|
||||
if(opened)
|
||||
if(istype(held_item, /obj/item/stock_parts/power_store) && QDELETED(powerdevice))
|
||||
context[SCREENTIP_CONTEXT_LMB] = "Insert cell"
|
||||
return CONTEXTUAL_SCREENTIP_SET
|
||||
|
||||
if(istype(held_item, /obj/item/stack/sheet/mineral/plasma) && !QDELETED(powerdevice))
|
||||
context[SCREENTIP_CONTEXT_LMB] = "Charge cell"
|
||||
return CONTEXTUAL_SCREENTIP_SET
|
||||
|
||||
if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
|
||||
context[SCREENTIP_CONTEXT_LMB] = "[opened ? "Close" : "Open"] Panel"
|
||||
return CONTEXTUAL_SCREENTIP_SET
|
||||
|
||||
/obj/item/inducer/examine(mob/living/user)
|
||||
. = ..()
|
||||
|
||||
. += examine_hints(user)
|
||||
|
||||
/**
|
||||
* Gives description for this inducer
|
||||
* Arguments
|
||||
*
|
||||
* * mob/living/user - the mob we are returning the description to
|
||||
*/
|
||||
/obj/item/inducer/proc/examine_hints(mob/living/user)
|
||||
PROTECTED_PROC(TRUE)
|
||||
SHOULD_BE_PURE(TRUE)
|
||||
|
||||
. = list()
|
||||
|
||||
var/obj/item/stock_parts/power_store/our_cell = get_cell(src, user)
|
||||
if(!QDELETED(our_cell))
|
||||
. += span_notice("Its display shows: [display_energy(our_cell.charge)].")
|
||||
if(opened)
|
||||
. += span_notice("The cell can be removed with an empty hand.")
|
||||
. += span_notice("Plasma sheets can be used to recharge the cell.")
|
||||
else
|
||||
. += span_warning("It's missing a power cell.")
|
||||
|
||||
. += span_notice("Its battery compartment can be [EXAMINE_HINT("screwed")] [opened ? "shut" : "open"].")
|
||||
|
||||
/obj/item/inducer/update_overlays()
|
||||
. = ..()
|
||||
if(!opened)
|
||||
return
|
||||
. += "inducer-[!QDELETED(powerdevice) ? "bat" : "nobat"]"
|
||||
|
||||
/obj/item/inducer/get_cell()
|
||||
return powerdevice
|
||||
|
||||
/obj/item/inducer/emp_act(severity)
|
||||
. = ..()
|
||||
if(!QDELETED(powerdevice) && !(. & EMP_PROTECT_CONTENTS))
|
||||
powerdevice.emp_act(severity)
|
||||
|
||||
/obj/item/inducer/screwdriver_act(mob/living/user, obj/item/tool)
|
||||
. = NONE
|
||||
|
||||
if(!tool.use_tool(src, user, delay = 0))
|
||||
return
|
||||
|
||||
opened = !opened
|
||||
to_chat(user, span_notice("You [opened ? "open" : "close"] the battery compartment."))
|
||||
update_appearance(UPDATE_OVERLAYS)
|
||||
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/obj/item/inducer/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
||||
. = NONE
|
||||
|
||||
if(user.combat_mode || !istype(tool) || tool.flags_1 & HOLOGRAM_1 || tool.item_flags & ABSTRACT)
|
||||
return ITEM_INTERACT_SKIP_TO_ATTACK
|
||||
|
||||
if(istype(tool, /obj/item/stock_parts/power_store))
|
||||
if(!opened)
|
||||
balloon_alert(user, "open first!")
|
||||
return ITEM_INTERACT_FAILURE
|
||||
|
||||
if(!QDELETED(powerdevice))
|
||||
balloon_alert(user, "cell already installed!")
|
||||
return ITEM_INTERACT_FAILURE
|
||||
|
||||
if(!user.transferItemToLoc(tool, src))
|
||||
balloon_alert(user, "stuck in hand!")
|
||||
return ITEM_INTERACT_FAILURE
|
||||
|
||||
powerdevice = tool
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
else if(istype(tool, /obj/item/stack/sheet/mineral/plasma) && !QDELETED(powerdevice))
|
||||
if(!powerdevice.used_charge())
|
||||
balloon_alert(user, "fully charged!")
|
||||
return ITEM_INTERACT_FAILURE
|
||||
|
||||
tool.use(1)
|
||||
powerdevice.give(1.5 * STANDARD_CELL_CHARGE)
|
||||
balloon_alert(user, "cell recharged")
|
||||
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/obj/item/inducer/interact_with_atom(atom/movable/interacting_with, mob/living/user, list/modifiers)
|
||||
. = NONE
|
||||
|
||||
if(HAS_TRAIT(interacting_with, TRAIT_COMBAT_MODE_SKIP_INTERACTION))
|
||||
return
|
||||
|
||||
if(user.combat_mode || !istype(interacting_with) || interacting_with.flags_1 & HOLOGRAM_1)
|
||||
return ITEM_INTERACT_SKIP_TO_ATTACK
|
||||
|
||||
//basic checks
|
||||
if(opened)
|
||||
balloon_alert(user, "close first!")
|
||||
return ITEM_INTERACT_FAILURE
|
||||
|
||||
if(recharging || (!isturf(interacting_with) && user.loc == interacting_with))
|
||||
return ITEM_INTERACT_FAILURE
|
||||
|
||||
if(!ISADVANCEDTOOLUSER(user))
|
||||
to_chat(user, span_warning("You don't have the dexterity to use [src]!"))
|
||||
return ITEM_INTERACT_FAILURE
|
||||
|
||||
var/obj/item/stock_parts/power_store/our_cell = get_cell(src, user)
|
||||
|
||||
if(QDELETED(our_cell))
|
||||
balloon_alert(user, "no cell installed!")
|
||||
return ITEM_INTERACT_FAILURE
|
||||
|
||||
if(!our_cell.charge)
|
||||
balloon_alert(user, "no charge!")
|
||||
return ITEM_INTERACT_FAILURE
|
||||
|
||||
var/obj/item/stock_parts/power_store/target_cell = interacting_with.get_cell(src, user)
|
||||
|
||||
if(QDELETED(target_cell))
|
||||
return ITEM_INTERACT_FAILURE
|
||||
|
||||
if(!target_cell.used_charge())
|
||||
balloon_alert(user, "fully charged!")
|
||||
return ITEM_INTERACT_FAILURE
|
||||
|
||||
//begin recharging
|
||||
recharging = TRUE
|
||||
user.visible_message(span_notice("[user] starts recharging [interacting_with] with [src]."), span_notice("You start recharging [interacting_with] with [src]."))
|
||||
|
||||
var/done_any = FALSE
|
||||
while(target_cell.used_charge())
|
||||
if(!do_after(user, 1 SECONDS, target = user))
|
||||
break
|
||||
|
||||
//transfer of charge
|
||||
var/transferred = min(our_cell.charge, target_cell.used_charge(), target_cell.rating_base * target_cell.rating * power_transfer_multiplier)
|
||||
if(!transferred)
|
||||
break
|
||||
our_cell.use(target_cell.give(transferred))
|
||||
|
||||
//update all appearances
|
||||
our_cell.update_appearance()
|
||||
target_cell.update_appearance()
|
||||
interacting_with.update_appearance()
|
||||
|
||||
//sparks & update
|
||||
do_sparks(1, FALSE, interacting_with)
|
||||
done_any = TRUE
|
||||
|
||||
recharging = FALSE
|
||||
|
||||
// Only show a message if we succeeded at least once
|
||||
if(done_any)
|
||||
user.visible_message(span_notice("[user] recharges [interacting_with]!"), span_notice("You recharge [interacting_with]!"))
|
||||
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/obj/item/inducer/attack_self(mob/user)
|
||||
if(opened && !QDELETED(powerdevice))
|
||||
user.visible_message(span_notice("[user] removes [powerdevice] from [src]!"), span_notice("You remove [powerdevice]."))
|
||||
powerdevice.update_appearance()
|
||||
user.put_in_hands(powerdevice)
|
||||
update_appearance(UPDATE_OVERLAYS)
|
||||
|
||||
/obj/item/inducer/empty
|
||||
powerdevice = null
|
||||
opened = TRUE
|
||||
|
||||
/obj/item/inducer/orderable
|
||||
powerdevice = /obj/item/stock_parts/power_store/battery/upgraded
|
||||
opened = FALSE
|
||||
|
||||
/obj/item/inducer/sci
|
||||
icon_state = "inducer-sci"
|
||||
inhand_icon_state = "inducer-sci"
|
||||
desc = "A tool for inductively charging internal power cells. This one has a science color scheme, and is less potent than its engineering counterpart."
|
||||
powerdevice = null
|
||||
opened = TRUE
|
||||
|
||||
/obj/item/inducer/syndicate
|
||||
icon_state = "inducer-syndi"
|
||||
inhand_icon_state = "inducer-syndi"
|
||||
desc = "A tool for inductively charging internal power cells. This one has a suspicious colour scheme, and seems to be rigged to transfer charge at a much faster rate."
|
||||
power_transfer_multiplier = 2 // 2x the base speed
|
||||
powerdevice = /obj/item/stock_parts/power_store/battery/super
|
||||
|
||||
/obj/item/inducer/cyborg
|
||||
name = "modular inducer"
|
||||
icon = 'icons/obj/tools.dmi'
|
||||
icon_state = "inducer-engi"
|
||||
|
||||
/obj/item/inducer/cyborg/examine_hints(mob/living/user)
|
||||
. = list()
|
||||
|
||||
var/obj/item/stock_parts/power_store/our_cell = get_cell(src, user)
|
||||
if(!QDELETED(our_cell))
|
||||
. += span_notice("Its display shows: [display_energy(our_cell.charge)].")
|
||||
if(opened)
|
||||
. += span_notice("Plasma sheets can be used to recharge the cell.")
|
||||
else
|
||||
. += span_warning("It's missing a power cell.")
|
||||
. += span_notice("Its battery compartment can be [EXAMINE_HINT("screwed")] [opened ? "shut" : "open"].")
|
||||
|
||||
/obj/item/inducer/cyborg/add_context(atom/source, list/context, obj/item/held_item, mob/user)
|
||||
return NONE
|
||||
|
||||
/obj/item/inducer/cyborg/interact_with_atom(atom/movable/interacting_with, mob/living/user, list/modifiers)
|
||||
if(iscyborg(user) && iscyborg(interacting_with))
|
||||
balloon_alert(user, "can't charge this!")
|
||||
return ITEM_INTERACT_FAILURE
|
||||
return ..()
|
||||
@@ -0,0 +1,108 @@
|
||||
/// Max number of atoms a broom can sweep at once
|
||||
#define BROOM_PUSH_LIMIT 20
|
||||
|
||||
/obj/item/pushbroom
|
||||
name = "push broom"
|
||||
desc = "This is my BROOMSTICK! It can be used manually or braced with two hands to sweep items as you move. It has a telescopic handle for compact storage."
|
||||
icon = 'icons/obj/service/janitor.dmi'
|
||||
icon_state = "broom0"
|
||||
base_icon_state = "broom"
|
||||
icon_angle = 135
|
||||
lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi'
|
||||
force = 8
|
||||
throwforce = 10
|
||||
throw_speed = 3
|
||||
throw_range = 7
|
||||
w_class = WEIGHT_CLASS_NORMAL
|
||||
attack_verb_continuous = list("sweeps", "brushes off", "bludgeons", "whacks")
|
||||
attack_verb_simple = list("sweep", "brush off", "bludgeon", "whack")
|
||||
resistance_flags = FLAMMABLE
|
||||
|
||||
/obj/item/pushbroom/Initialize(mapload)
|
||||
. = ..()
|
||||
AddComponent(/datum/component/jousting, damage_boost_per_tile = 1)
|
||||
AddComponent(/datum/component/two_handed, \
|
||||
force_unwielded = 8, \
|
||||
force_wielded = 12, \
|
||||
icon_wielded = "[base_icon_state]1", \
|
||||
wield_callback = CALLBACK(src, PROC_REF(on_wield)), \
|
||||
unwield_callback = CALLBACK(src, PROC_REF(on_unwield)), \
|
||||
)
|
||||
|
||||
/obj/item/pushbroom/update_icon_state()
|
||||
icon_state = "[base_icon_state]0"
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Handles registering the sweep proc when the broom is wielded
|
||||
*
|
||||
* Arguments:
|
||||
* * source - The source of the on_wield proc call
|
||||
* * user - The user which is wielding the broom
|
||||
*/
|
||||
/obj/item/pushbroom/proc/on_wield(obj/item/source, mob/user)
|
||||
to_chat(user, span_notice("You brace the [src] against the ground in a firm sweeping stance."))
|
||||
RegisterSignal(user, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(sweep))
|
||||
|
||||
/**
|
||||
* Handles unregistering the sweep proc when the broom is unwielded
|
||||
*
|
||||
* Arguments:
|
||||
* * source - The source of the on_unwield proc call
|
||||
* * user - The user which is unwielding the broom
|
||||
*/
|
||||
/obj/item/pushbroom/proc/on_unwield(obj/item/source, mob/user)
|
||||
UnregisterSignal(user, COMSIG_MOVABLE_PRE_MOVE)
|
||||
|
||||
/obj/item/pushbroom/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
|
||||
sweep(user, interacting_with)
|
||||
return NONE // I guess
|
||||
|
||||
/**
|
||||
* Attempts to push up to BROOM_PUSH_LIMIT atoms from a given location the user's faced direction
|
||||
*
|
||||
* Arguments:
|
||||
* * user - The user of the pushbroom
|
||||
* * A - The atom which is located at the location to push atoms from
|
||||
*/
|
||||
/obj/item/pushbroom/proc/sweep(mob/user, atom/atom)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
do_sweep(src, user, atom, user.dir)
|
||||
|
||||
/**
|
||||
* Sweep objects in the direction we're facing towards our direction
|
||||
* Arguments
|
||||
* * broomer - The object being used for brooming
|
||||
* * user - The person who is brooming
|
||||
* * target - The object or tile that's target of a broom click or being moved into
|
||||
* * sweep_dir - The directions in which we sweep objects
|
||||
*/
|
||||
/proc/do_sweep(obj/broomer, mob/user, atom/target, sweep_dir)
|
||||
var/turf/current_item_loc = isturf(target) ? target : target.loc
|
||||
if (!isturf(current_item_loc))
|
||||
return
|
||||
var/turf/new_item_loc = get_step(current_item_loc, sweep_dir)
|
||||
|
||||
var/list/items_to_sweep = list()
|
||||
var/i = 1
|
||||
for (var/obj/item/garbage in current_item_loc.contents)
|
||||
if(garbage.anchored)
|
||||
continue
|
||||
items_to_sweep += garbage
|
||||
i++
|
||||
if(i > BROOM_PUSH_LIMIT)
|
||||
break
|
||||
|
||||
SEND_SIGNAL(new_item_loc, COMSIG_TURF_RECEIVE_SWEEPED_ITEMS, broomer, user, items_to_sweep)
|
||||
|
||||
if(!length(items_to_sweep))
|
||||
return
|
||||
|
||||
for (var/obj/item/garbage in items_to_sweep)
|
||||
garbage.Move(new_item_loc, sweep_dir)
|
||||
|
||||
playsound(current_item_loc, 'sound/items/weapons/thudswoosh.ogg', 30, TRUE, -1)
|
||||
|
||||
#undef BROOM_PUSH_LIMIT
|
||||
@@ -0,0 +1,87 @@
|
||||
///The time limit on the keys before the access it's been given clears itself.
|
||||
#define ACCESS_TIMER_LIMIT (10 MINUTES)
|
||||
|
||||
/obj/item/access_key
|
||||
name = "access key ring"
|
||||
desc = "A key ring with a beeper, allowing the keys to change shape depending on which department it has access to."
|
||||
icon_state = "access_key"
|
||||
inhand_icon_state = "access_key"
|
||||
icon = 'icons/obj/service/janitor.dmi'
|
||||
lefthand_file = 'icons/mob/inhands/items/keys_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/items/keys_righthand.dmi'
|
||||
hitsound = 'sound/items/rattling_keys_attack.ogg'
|
||||
force = 2
|
||||
verb_say = "beeps" //it has a beeper
|
||||
verb_ask = "questionably beeps"
|
||||
verb_exclaim = "beeps loudly"
|
||||
w_class = WEIGHT_CLASS_TINY
|
||||
|
||||
///The departmental access given to the key.
|
||||
var/list/department_access
|
||||
|
||||
/obj/item/access_key/Initialize(mapload)
|
||||
. = ..()
|
||||
RegisterSignal(SSdcs, COMSIG_ON_DEPARTMENT_ACCESS, PROC_REF(department_access_given))
|
||||
GLOB.janitor_devices += src
|
||||
|
||||
/obj/item/access_key/Destroy()
|
||||
GLOB.janitor_devices -= src
|
||||
return ..()
|
||||
|
||||
/obj/item/access_key/examine(mob/user)
|
||||
. = ..()
|
||||
if(department_access)
|
||||
. += "It currently holds access to the [department_access] region."
|
||||
|
||||
/obj/item/access_key/examine_more(mob/user)
|
||||
. = ..()
|
||||
. += span_notice("Access can be granted through a Keycard Authentication Device.")
|
||||
. += span_notice("This access is limited to one department at a time.")
|
||||
|
||||
/**
|
||||
* Called when attempting to open an airlock.
|
||||
* Checks if the keys have access, returns try_to_activate_door if it does, returns FALSE otherwise.
|
||||
* Args:
|
||||
* user - The person attempting to open the door
|
||||
* airlock - The door we're attempting to open
|
||||
*/
|
||||
/obj/item/access_key/proc/attempt_open_door(mob/living/user, obj/machinery/door/airlock)
|
||||
if(DOING_INTERACTION_WITH_TARGET(user, airlock))
|
||||
return
|
||||
user.balloon_alert_to_viewers("fumbles with keys...", "finding key...")
|
||||
user.playsound_local(src, 'sound/items/rattling_keys.ogg', 25, TRUE)
|
||||
if(!do_after(user, 3 SECONDS, airlock))
|
||||
return FALSE
|
||||
if(!department_access || !airlock.check_access_list(SSid_access.accesses_by_region[department_access]))
|
||||
airlock.balloon_alert(user, "no access!")
|
||||
return FALSE
|
||||
return airlock.try_to_activate_door(user, access_bypass = TRUE)
|
||||
|
||||
/**
|
||||
* Called when a keycard authenticator sends region access
|
||||
* Stores it on the key to use for access, then sets a timer to clear it.
|
||||
* Args:
|
||||
* source - the keycard authenticator giving us the access
|
||||
* region_access - the list of access we're being sent, we only take the first entry in the list as there should only have one department at a time.
|
||||
*/
|
||||
/obj/item/access_key/proc/department_access_given(obj/machinery/keycard_auth/source, list/region_access)
|
||||
SIGNAL_HANDLER
|
||||
department_access = region_access[1]
|
||||
say("Access granted to [department_access] area.")
|
||||
playsound(src, 'sound/machines/ding.ogg', 25, TRUE)
|
||||
addtimer(CALLBACK(src, PROC_REF(clear_access)), ACCESS_TIMER_LIMIT, TIMER_UNIQUE|TIMER_OVERRIDE)
|
||||
log_game("Access to the [department_access] department was given to [src] [(ismob(loc)) ? "held by [loc]" : "which is not being held"]")
|
||||
investigate_log("Access to the [department_access] department was given to [src] [(ismob(loc)) ? "held by [loc]" : "which is not being held"]", INVESTIGATE_ACCESSCHANGES)
|
||||
|
||||
/**
|
||||
* Called when a keycard authenticator runs out of time
|
||||
* Clears the department access and alerts nearby people of such.
|
||||
*/
|
||||
/obj/item/access_key/proc/clear_access()
|
||||
log_game("Access to the [department_access] department on [src] has expired.")
|
||||
investigate_log("Access to the [department_access] department on [src] has expired.]", INVESTIGATE_ACCESSCHANGES)
|
||||
department_access = null
|
||||
say("Access revoked, time ran out.")
|
||||
playsound(src, 'sound/machines/scanner/scanbuzz.ogg', 25, TRUE)
|
||||
|
||||
#undef ACCESS_TIMER_LIMIT
|
||||
@@ -0,0 +1,139 @@
|
||||
|
||||
/obj/item/bodybag
|
||||
name = "body bag"
|
||||
desc = "A folded bag designed for the storage and transportation of cadavers."
|
||||
icon = 'icons/obj/medical/bodybag.dmi'
|
||||
icon_state = "bodybag_folded"
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
///Stored path we use for spawning a new body bag entity when unfolded.
|
||||
var/unfoldedbag_path = /obj/structure/closet/body_bag
|
||||
|
||||
/obj/item/bodybag/attack_self(mob/user)
|
||||
if(user.is_holding(src))
|
||||
deploy_bodybag(user, get_turf(user))
|
||||
else
|
||||
deploy_bodybag(user, get_turf(src))
|
||||
|
||||
/obj/item/bodybag/interact_with_atom(atom/interacting_with, mob/living/user, flags)
|
||||
if(isopenturf(interacting_with))
|
||||
deploy_bodybag(user, interacting_with)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
return NONE
|
||||
|
||||
/obj/item/bodybag/attempt_pickup(mob/user)
|
||||
// can't pick ourselves up if we are inside of the bodybag, else very weird things may happen
|
||||
if(contains(user))
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Creates a new body bag item when unfolded, at the provided location, replacing the body bag item.
|
||||
* * mob/user: User opening the body bag.
|
||||
* * atom/location: the place/entity/mob where the body bag is being deployed from.
|
||||
*/
|
||||
/obj/item/bodybag/proc/deploy_bodybag(mob/user, atom/location)
|
||||
var/obj/structure/closet/body_bag/item_bag = new unfoldedbag_path(location)
|
||||
item_bag.open(user)
|
||||
item_bag.add_fingerprint(user)
|
||||
item_bag.foldedbag_instance = src
|
||||
moveToNullspace()
|
||||
return item_bag
|
||||
|
||||
/obj/item/bodybag/suicide_act(mob/living/user)
|
||||
if(isopenturf(user.loc))
|
||||
user.visible_message(span_suicide("[user] is crawling into [src]! It looks like [user.p_theyre()] trying to commit suicide!"))
|
||||
var/obj/structure/closet/body_bag/R = new unfoldedbag_path(user.loc)
|
||||
R.add_fingerprint(user)
|
||||
qdel(src)
|
||||
user.forceMove(R)
|
||||
playsound(src, 'sound/items/zip/zip.ogg', 15, TRUE, -3)
|
||||
return OXYLOSS
|
||||
|
||||
// Bluespace bodybag
|
||||
|
||||
/obj/item/bodybag/bluespace
|
||||
name = "bluespace body bag"
|
||||
desc = "A folded bluespace body bag designed for the storage and transportation of cadavers."
|
||||
icon = 'icons/obj/medical/bodybag.dmi'
|
||||
icon_state = "bluebodybag_folded"
|
||||
unfoldedbag_path = /obj/structure/closet/body_bag/bluespace
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
item_flags = NO_MAT_REDEMPTION
|
||||
|
||||
/obj/item/bodybag/bluespace/examine(mob/user)
|
||||
. = ..()
|
||||
if(contents.len)
|
||||
var/s = contents.len == 1 ? "" : "s"
|
||||
. += span_notice("You can make out the shape[s] of [contents.len] object[s] through the fabric.")
|
||||
|
||||
/obj/item/bodybag/bluespace/Destroy()
|
||||
for(var/atom/movable/A in contents)
|
||||
A.forceMove(get_turf(src))
|
||||
if(isliving(A))
|
||||
to_chat(A, span_notice("You suddenly feel the space around you torn apart! You're free!"))
|
||||
return ..()
|
||||
|
||||
/obj/item/bodybag/bluespace/deploy_bodybag(mob/user, atom/location)
|
||||
var/obj/structure/closet/body_bag/item_bag = new unfoldedbag_path(location)
|
||||
for(var/atom/movable/inside in contents)
|
||||
inside.forceMove(item_bag)
|
||||
if(isliving(inside))
|
||||
to_chat(inside, span_notice("You suddenly feel air around you! You're free!"))
|
||||
item_bag.open(user)
|
||||
item_bag.add_fingerprint(user)
|
||||
item_bag.foldedbag_instance = src
|
||||
moveToNullspace()
|
||||
return item_bag
|
||||
|
||||
/obj/item/bodybag/bluespace/container_resist_act(mob/living/user)
|
||||
if(user.incapacitated)
|
||||
to_chat(user, span_warning("You can't get out while you're restrained like this!"))
|
||||
return
|
||||
user.changeNext_move(CLICK_CD_BREAKOUT)
|
||||
user.last_special = world.time + CLICK_CD_BREAKOUT
|
||||
to_chat(user, span_notice("You claw at the fabric of [src], trying to tear it open..."))
|
||||
to_chat(loc, span_warning("Someone starts trying to break free of [src]!"))
|
||||
if(!do_after(user, 12 SECONDS, src, timed_action_flags = (IGNORE_TARGET_LOC_CHANGE|IGNORE_HELD_ITEM)))
|
||||
return
|
||||
// you are still in the bag? time to go unless you KO'd, honey!
|
||||
// if they escape during this time and you rebag them the timer is still clocking down and does NOT reset so they can very easily get out.
|
||||
if(user.incapacitated)
|
||||
to_chat(loc, span_warning("The pressure subsides. It seems that they've stopped resisting..."))
|
||||
return
|
||||
loc.visible_message(span_warning("[user] suddenly appears in front of [loc]!"), span_userdanger("[user] breaks free of [src]!"))
|
||||
qdel(src)
|
||||
|
||||
/obj/item/bodybag/environmental
|
||||
name = "environmental protection bag"
|
||||
desc = "A folded, reinforced bag designed to protect against exoplanetary environmental storms."
|
||||
icon = 'icons/obj/medical/bodybag.dmi'
|
||||
icon_state = "envirobag_folded"
|
||||
unfoldedbag_path = /obj/structure/closet/body_bag/environmental
|
||||
w_class = WEIGHT_CLASS_NORMAL //It's reinforced and insulated, like a beefed-up sleeping bag, so it has a higher bulkiness than regular bodybag
|
||||
resistance_flags = ACID_PROOF | FIRE_PROOF | FREEZE_PROOF
|
||||
|
||||
/obj/item/bodybag/environmental/nanotrasen
|
||||
name = "elite environmental protection bag"
|
||||
desc = "A folded, heavily reinforced, and insulated bag, capable of fully isolating its contents from external factors."
|
||||
icon_state = "ntenvirobag_folded"
|
||||
unfoldedbag_path = /obj/structure/closet/body_bag/environmental/nanotrasen
|
||||
resistance_flags = ACID_PROOF | FIRE_PROOF | FREEZE_PROOF | LAVA_PROOF
|
||||
|
||||
/obj/item/bodybag/environmental/prisoner
|
||||
name = "prisoner transport bag"
|
||||
desc = "Intended for transport of prisoners through hazardous environments, this folded environmental protection bag comes with straps to keep an occupant secure."
|
||||
icon = 'icons/obj/medical/bodybag.dmi'
|
||||
icon_state = "prisonerenvirobag_folded"
|
||||
unfoldedbag_path = /obj/structure/closet/body_bag/environmental/prisoner
|
||||
|
||||
/obj/item/bodybag/environmental/prisoner/pressurized
|
||||
name = "pressurized prisoner transport bag"
|
||||
unfoldedbag_path = /obj/structure/closet/body_bag/environmental/prisoner/pressurized
|
||||
|
||||
/obj/item/bodybag/environmental/prisoner/syndicate
|
||||
name = "syndicate prisoner transport bag"
|
||||
desc = "An alteration of Nanotrasen's environmental protection bag which has been used in several high-profile kidnappings. Designed to keep a victim unconscious, alive, and secured until they are transported to a required location."
|
||||
icon = 'icons/obj/medical/bodybag.dmi'
|
||||
icon_state = "syndieenvirobag_folded"
|
||||
unfoldedbag_path = /obj/structure/closet/body_bag/environmental/prisoner/pressurized/syndicate
|
||||
resistance_flags = ACID_PROOF | FIRE_PROOF | FREEZE_PROOF | LAVA_PROOF
|
||||
@@ -0,0 +1,147 @@
|
||||
/obj/item/cane
|
||||
name = "cane"
|
||||
desc = "A cane used by a true gentleman. Or a clown."
|
||||
icon = 'icons/obj/weapons/staff.dmi'
|
||||
icon_state = "cane"
|
||||
inhand_icon_state = "stick"
|
||||
icon_angle = 135
|
||||
lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
|
||||
force = 5
|
||||
throwforce = 5
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
custom_materials = list(/datum/material/iron= SMALL_MATERIAL_AMOUNT * 0.5)
|
||||
attack_verb_continuous = list("bludgeons", "whacks", "disciplines", "thrashes")
|
||||
attack_verb_simple = list("bludgeon", "whack", "discipline", "thrash")
|
||||
|
||||
/obj/item/cane/examine(mob/user, thats)
|
||||
. = ..()
|
||||
. += span_notice("This item can be used to support your weight, preventing limping from any broken bones on your legs you may have.")
|
||||
|
||||
/obj/item/cane/equipped(mob/living/user, slot, initial)
|
||||
..()
|
||||
if(!(slot & ITEM_SLOT_HANDS))
|
||||
return
|
||||
movement_support_add(user)
|
||||
|
||||
/obj/item/cane/dropped(mob/living/user, silent = FALSE)
|
||||
. = ..()
|
||||
movement_support_del(user)
|
||||
|
||||
/obj/item/cane/proc/movement_support_add(mob/living/user)
|
||||
RegisterSignal(user, COMSIG_CARBON_LIMPING, PROC_REF(handle_limping))
|
||||
return TRUE
|
||||
|
||||
/obj/item/cane/proc/movement_support_del(mob/living/user)
|
||||
UnregisterSignal(user, list(COMSIG_CARBON_LIMPING))
|
||||
return TRUE
|
||||
|
||||
/obj/item/cane/proc/handle_limping(mob/living/user)
|
||||
SIGNAL_HANDLER
|
||||
return COMPONENT_CANCEL_LIMP
|
||||
|
||||
/obj/item/cane/crutch
|
||||
name = "medical crutch"
|
||||
desc = "A medical crutch used by people missing a leg. Not all that useful if you're missing both of them, though."
|
||||
icon = 'icons/obj/weapons/staff.dmi'
|
||||
icon_state = "crutch_med"
|
||||
inhand_icon_state = "crutch_med"
|
||||
icon_angle = 45
|
||||
lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
|
||||
force = 12
|
||||
throwforce = 8
|
||||
w_class = WEIGHT_CLASS_BULKY
|
||||
custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 0.5)
|
||||
attack_verb_continuous = list("bludgeons", "whacks", "thrashes")
|
||||
attack_verb_simple = list("bludgeon", "whack", "thrash")
|
||||
|
||||
/obj/item/cane/crutch/Initialize(mapload)
|
||||
. = ..()
|
||||
AddElement(/datum/element/cuffable_item)
|
||||
|
||||
/obj/item/cane/crutch/examine(mob/user, thats)
|
||||
. = ..()
|
||||
// tacked on after the cane string
|
||||
. += span_notice("As a crutch, it can also help lessen the slowdown incurred by missing a leg.")
|
||||
|
||||
/obj/item/cane/crutch/movement_support_add(mob/living/user)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
RegisterSignal(user, COMSIG_LIVING_LIMBLESS_SLOWDOWN, PROC_REF(handle_slowdown))
|
||||
user.update_usable_leg_status()
|
||||
user.AddElementTrait(TRAIT_WADDLING, REF(src), /datum/element/waddling)
|
||||
|
||||
/obj/item/cane/crutch/movement_support_del(mob/living/user)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
UnregisterSignal(user, list(COMSIG_LIVING_LIMBLESS_SLOWDOWN, COMSIG_CARBON_LIMPING))
|
||||
user.update_usable_leg_status()
|
||||
REMOVE_TRAIT(user, TRAIT_WADDLING, REF(src))
|
||||
|
||||
/obj/item/cane/crutch/proc/handle_slowdown(mob/living/user, limbless_slowdown, list/slowdown_mods)
|
||||
SIGNAL_HANDLER
|
||||
var/leg_amount = user.usable_legs
|
||||
// Don't do anything if the number is equal (or higher) to the usual.
|
||||
if(leg_amount >= user.default_num_legs)
|
||||
return
|
||||
// If we have at least one leg and it's less than the default, reduce slowdown by 60%.
|
||||
if(leg_amount && (leg_amount < user.default_num_legs))
|
||||
slowdown_mods += 0.4
|
||||
|
||||
/obj/item/cane/crutch/wood
|
||||
name = "wooden crutch"
|
||||
desc = "A handmade crutch. Also makes a decent bludgeon if you need it."
|
||||
icon_state = "crutch_wood"
|
||||
inhand_icon_state = "crutch_wood"
|
||||
custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT * 5)
|
||||
|
||||
/obj/item/cane/white
|
||||
name = "white cane"
|
||||
desc = "Traditionally used by the blind to help them see. Folds down to be easier to transport."
|
||||
icon_state = "cane_white"
|
||||
inhand_icon_state = "cane_white"
|
||||
icon_angle = 45
|
||||
lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
|
||||
force = 1
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 6)
|
||||
|
||||
/obj/item/cane/white/Initialize(mapload)
|
||||
. = ..()
|
||||
AddElement(/datum/element/cuffable_item)
|
||||
AddComponent( \
|
||||
/datum/component/transforming, \
|
||||
force_on = 7, \
|
||||
hitsound_on = hitsound, \
|
||||
w_class_on = WEIGHT_CLASS_BULKY, \
|
||||
clumsy_check = FALSE, \
|
||||
attack_verb_continuous_on = list("smacks", "strikes", "cracks", "beats"), \
|
||||
attack_verb_simple_on = list("smack", "strike", "crack", "beat"), \
|
||||
)
|
||||
RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform))
|
||||
|
||||
/obj/item/cane/white/handle_limping(mob/living/user)
|
||||
return HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE) ? COMPONENT_CANCEL_LIMP : NONE
|
||||
|
||||
/*
|
||||
* Signal proc for [COMSIG_TRANSFORMING_ON_TRANSFORM].
|
||||
*
|
||||
* Gives feedback to the user and makes it show up inhand.
|
||||
*/
|
||||
/obj/item/cane/white/proc/on_transform(obj/item/source, mob/user, active)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(user)
|
||||
balloon_alert(user, active ? "extended" : "collapsed")
|
||||
|
||||
if(!HAS_TRAIT(src, TRAIT_BLIND_TOOL))
|
||||
ADD_TRAIT(src, TRAIT_BLIND_TOOL, INNATE_TRAIT)
|
||||
else
|
||||
REMOVE_TRAIT(src, TRAIT_BLIND_TOOL, INNATE_TRAIT)
|
||||
|
||||
playsound(src, 'sound/items/weapons/batonextend.ogg', 50, TRUE)
|
||||
return COMPONENT_NO_DEFAULT_MESSAGE
|
||||
@@ -0,0 +1,720 @@
|
||||
//backpack item
|
||||
#define HALFWAYCRITDEATH ((HEALTH_THRESHOLD_CRIT + HEALTH_THRESHOLD_DEAD) * 0.5)
|
||||
#define DEFIB_CAN_HURT(source) (source.combat || (source.req_defib && !source.defib.safety))
|
||||
|
||||
/obj/item/defibrillator
|
||||
name = "defibrillator"
|
||||
desc = "A device that delivers powerful shocks to detachable paddles that resuscitate incapacitated patients. \
|
||||
Has a rear bracket for attachments to wall mounts and medical cyborgs."
|
||||
icon = 'icons/obj/medical/defib.dmi'
|
||||
icon_state = "defibunit"
|
||||
inhand_icon_state = "defibunit"
|
||||
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
|
||||
slot_flags = ITEM_SLOT_BACK
|
||||
force = 5
|
||||
throwforce = 6
|
||||
w_class = WEIGHT_CLASS_BULKY
|
||||
actions_types = list(/datum/action/item_action/toggle_paddles)
|
||||
armor_type = /datum/armor/item_defibrillator
|
||||
|
||||
var/obj/item/shockpaddles/paddle_type = /obj/item/shockpaddles
|
||||
/// If the paddles are equipped (1) or on the defib (0)
|
||||
var/on = FALSE
|
||||
/// If you can zap people with the defibs on harm mode
|
||||
var/safety = TRUE
|
||||
/// If there's a cell in the defib with enough power for a revive, blocks paddles from reviving otherwise
|
||||
var/powered = FALSE
|
||||
/// If the cell can be removed via screwdriver
|
||||
var/cell_removable = TRUE
|
||||
var/obj/item/shockpaddles/paddles
|
||||
var/obj/item/stock_parts/power_store/cell/cell
|
||||
/// If true, revive through space suits, allow for combat shocking
|
||||
var/combat = FALSE
|
||||
/// How long does it take to recharge
|
||||
var/cooldown_duration = 5 SECONDS
|
||||
/// The icon state for the paddle overlay, not applied if null
|
||||
var/paddle_state = "defibunit-paddles"
|
||||
/// The icon state for the powered on overlay, not applied if null
|
||||
var/powered_state = "defibunit-powered"
|
||||
/// The icon state for the charge bar overlay, not applied if null
|
||||
var/charge_state = "defibunit-charge"
|
||||
/// The icon state for the missing cell overlay, not applied if null
|
||||
var/nocell_state = "defibunit-nocell"
|
||||
/// The icon state for the emagged overlay, not applied if null
|
||||
var/emagged_state = "defibunit-emagged"
|
||||
|
||||
/datum/armor/item_defibrillator
|
||||
fire = 50
|
||||
acid = 50
|
||||
|
||||
/obj/item/defibrillator/get_cell()
|
||||
return cell
|
||||
|
||||
/obj/item/defibrillator/Initialize(mapload) //starts without a cell for rnd
|
||||
. = ..()
|
||||
paddles = new paddle_type(src)
|
||||
update_power()
|
||||
RegisterSignal(paddles, COMSIG_DEFIBRILLATOR_SUCCESS, PROC_REF(on_defib_success))
|
||||
AddElement(/datum/element/drag_pickup)
|
||||
|
||||
/obj/item/defibrillator/loaded/Initialize(mapload) //starts with hicap
|
||||
. = ..()
|
||||
cell = new(src)
|
||||
update_power()
|
||||
|
||||
/obj/item/defibrillator/examine(mob/user)
|
||||
. = ..()
|
||||
if(!cell_removable)
|
||||
return
|
||||
if(cell)
|
||||
. += span_notice("Use a screwdriver to remove the cell.")
|
||||
else
|
||||
. += span_warning("It has no power cell!")
|
||||
|
||||
/obj/item/defibrillator/fire_act(exposed_temperature, exposed_volume)
|
||||
. = ..()
|
||||
if(paddles?.loc == src)
|
||||
paddles.fire_act(exposed_temperature, exposed_volume)
|
||||
|
||||
/obj/item/defibrillator/extinguish()
|
||||
. = ..()
|
||||
if(paddles?.loc == src)
|
||||
paddles.extinguish()
|
||||
|
||||
/obj/item/defibrillator/proc/update_power()
|
||||
if(!QDELETED(cell))
|
||||
if(QDELETED(paddles) || cell.charge < paddles.revivecost)
|
||||
powered = FALSE
|
||||
else
|
||||
powered = TRUE
|
||||
else
|
||||
powered = FALSE
|
||||
update_appearance()
|
||||
if(istype(loc, /obj/machinery/defibrillator_mount))
|
||||
loc.update_appearance()
|
||||
|
||||
/obj/item/defibrillator/update_overlays()
|
||||
. = ..()
|
||||
|
||||
if(!on && paddle_state)
|
||||
. += paddle_state
|
||||
if(powered && powered_state)
|
||||
. += powered_state
|
||||
if(!QDELETED(cell) && charge_state)
|
||||
var/ratio = cell.charge / cell.maxcharge
|
||||
ratio = CEILING(ratio*4, 1) * 25
|
||||
. += "[charge_state][ratio]"
|
||||
if(!cell && nocell_state)
|
||||
. += "[nocell_state]"
|
||||
if(!safety && emagged_state)
|
||||
. += emagged_state
|
||||
|
||||
/obj/item/defibrillator/on_craft_completion(list/components, datum/crafting_recipe/current_recipe, atom/crafter)
|
||||
. = ..()
|
||||
cell = locate(/obj/item/stock_parts/power_store) in contents
|
||||
update_power()
|
||||
|
||||
/obj/item/defibrillator/ui_action_click()
|
||||
INVOKE_ASYNC(src, PROC_REF(toggle_paddles))
|
||||
|
||||
//ATTACK HAND IGNORING PARENT RETURN VALUE
|
||||
/obj/item/defibrillator/attack_hand(mob/user, list/modifiers)
|
||||
if(loc == user)
|
||||
if(user.get_slot_by_item(src) & slot_flags)
|
||||
ui_action_click()
|
||||
else
|
||||
balloon_alert(user, "equip the unit first!")
|
||||
return
|
||||
else if(istype(loc, /obj/machinery/defibrillator_mount))
|
||||
ui_action_click() //checks for this are handled in defibrillator.mount.dm
|
||||
return ..()
|
||||
|
||||
/obj/item/defibrillator/screwdriver_act(mob/living/user, obj/item/tool)
|
||||
if(!cell || !cell_removable)
|
||||
return FALSE
|
||||
|
||||
cell.forceMove(get_turf(src))
|
||||
balloon_alert(user, "removed [cell]")
|
||||
cell = null
|
||||
tool.play_tool_sound(src, 50)
|
||||
update_power()
|
||||
return TRUE
|
||||
|
||||
/obj/item/defibrillator/item_interaction(mob/living/user, obj/item/item, list/modifiers)
|
||||
if(item == paddles)
|
||||
toggle_paddles()
|
||||
return NONE
|
||||
if(!istype(item, /obj/item/stock_parts/power_store/cell))
|
||||
return NONE
|
||||
|
||||
var/obj/item/stock_parts/power_store/cell/new_cell = item
|
||||
if(!isnull(cell))
|
||||
to_chat(user, span_warning("[src] already has a cell!"))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
|
||||
if(new_cell.maxcharge < paddles.revivecost)
|
||||
to_chat(user, span_notice("[src] requires a higher capacity cell."))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
if(!user.transferItemToLoc(new_cell, src))
|
||||
return NONE
|
||||
cell = new_cell
|
||||
to_chat(user, span_notice("You install a cell in [src]."))
|
||||
update_power()
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/obj/item/defibrillator/emag_act(mob/user, obj/item/card/emag/emag_card)
|
||||
|
||||
safety = !safety
|
||||
|
||||
var/enabled_or_disabled = (safety ? "enabled" : "disabled")
|
||||
balloon_alert(user, "safety protocols [enabled_or_disabled]")
|
||||
|
||||
return TRUE
|
||||
|
||||
/obj/item/defibrillator/emp_act(severity)
|
||||
. = ..()
|
||||
if(cell && !(. & EMP_PROTECT_CONTENTS))
|
||||
deductcharge(STANDARD_CELL_CHARGE / severity)
|
||||
if (. & EMP_PROTECT_SELF)
|
||||
return
|
||||
|
||||
update_power()
|
||||
|
||||
/obj/item/defibrillator/proc/toggle_paddles()
|
||||
set name = "Toggle Paddles"
|
||||
set category = "Object"
|
||||
on = !on
|
||||
|
||||
var/mob/living/carbon/user = usr
|
||||
if(on)
|
||||
//Detach the paddles into the user's hands
|
||||
if(!usr.put_in_hands(paddles))
|
||||
on = FALSE
|
||||
to_chat(user, span_warning("You need a free hand to hold the paddles!"))
|
||||
update_power()
|
||||
return
|
||||
else
|
||||
//Remove from their hands and back onto the defib unit
|
||||
remove_paddles(user)
|
||||
|
||||
update_power()
|
||||
update_item_action_buttons()
|
||||
|
||||
|
||||
/obj/item/defibrillator/equipped(mob/user, slot)
|
||||
..()
|
||||
if(!(slot_flags & slot))
|
||||
remove_paddles(user)
|
||||
update_power()
|
||||
|
||||
/obj/item/defibrillator/proc/remove_paddles(mob/user) //this fox the bug with the paddles when other player stole you the defib when you have the paddles equiped
|
||||
if(ismob(paddles.loc))
|
||||
var/mob/M = paddles.loc
|
||||
M.dropItemToGround(paddles, TRUE)
|
||||
return
|
||||
|
||||
/obj/item/defibrillator/Destroy()
|
||||
if(on)
|
||||
var/M = get(paddles, /mob)
|
||||
remove_paddles(M)
|
||||
QDEL_NULL(paddles)
|
||||
QDEL_NULL(cell)
|
||||
return ..()
|
||||
|
||||
/obj/item/defibrillator/proc/deductcharge(chrgdeductamt)
|
||||
if(QDELETED(cell))
|
||||
return
|
||||
|
||||
if(cell.charge < (paddles.revivecost + chrgdeductamt))
|
||||
powered = FALSE
|
||||
if(!cell.use(chrgdeductamt))
|
||||
powered = FALSE
|
||||
|
||||
update_power()
|
||||
|
||||
/obj/item/defibrillator/proc/cooldowncheck()
|
||||
addtimer(CALLBACK(src, PROC_REF(finish_charging)), cooldown_duration)
|
||||
|
||||
/obj/item/defibrillator/proc/finish_charging()
|
||||
if(cell)
|
||||
if(cell.charge >= paddles.revivecost)
|
||||
visible_message(span_notice("[src] beeps: Unit ready."))
|
||||
playsound(src, 'sound/machines/defib/defib_ready.ogg', 50, FALSE)
|
||||
else
|
||||
visible_message(span_notice("[src] beeps: Charge depleted."))
|
||||
playsound(src, 'sound/machines/defib/defib_failed.ogg', 50, FALSE)
|
||||
paddles.cooldown = FALSE
|
||||
paddles.update_appearance()
|
||||
update_power()
|
||||
|
||||
/obj/item/defibrillator/proc/on_defib_success(obj/item/shockpaddles/source)
|
||||
deductcharge(source.revivecost)
|
||||
source.cooldown = TRUE
|
||||
cooldowncheck()
|
||||
return COMPONENT_DEFIB_STOP
|
||||
|
||||
/obj/item/defibrillator/compact
|
||||
name = "compact defibrillator"
|
||||
desc = "A belt-equipped defibrillator that can be rapidly deployed."
|
||||
icon_state = "defibcompact"
|
||||
inhand_icon_state = null
|
||||
slot_flags = ITEM_SLOT_BELT|ITEM_SLOT_SUITSTORE|ITEM_SLOT_DEX_STORAGE
|
||||
worn_icon = 'icons/mob/clothing/belt.dmi'
|
||||
worn_icon_state = "defibcompact"
|
||||
w_class = WEIGHT_CLASS_NORMAL
|
||||
paddle_state = "defibcompact-paddles"
|
||||
powered_state = "defibcompact-powered"
|
||||
charge_state = "defibcompact-charge"
|
||||
nocell_state = "defibcompact-nocell"
|
||||
emagged_state = "defibcompact-emagged"
|
||||
|
||||
/obj/item/defibrillator/compact/loaded/Initialize(mapload)
|
||||
. = ..()
|
||||
cell = new(src)
|
||||
update_power()
|
||||
|
||||
/obj/item/defibrillator/compact/loaded/cmo // subtype for the spy steal objective
|
||||
name = "chief medical officer's compact defibrillator"
|
||||
icon_state = "defibcmo"
|
||||
resistance_flags = INDESTRUCTIBLE // So no cheesy getting rid of like other steal/head items
|
||||
|
||||
/obj/item/defibrillator/compact/combat
|
||||
name = "combat defibrillator"
|
||||
desc = "A belt-equipped blood-red defibrillator. Can revive through thick clothing, has an experimental self-recharging battery, and can be utilized as a weapon via applying the paddles while in a combat stance."
|
||||
icon_state = "defibcombat" //needs defib inhand sprites
|
||||
inhand_icon_state = null
|
||||
worn_icon_state = "defibcombat"
|
||||
combat = TRUE
|
||||
safety = FALSE
|
||||
cooldown_duration = 2.5 SECONDS
|
||||
paddle_type = /obj/item/shockpaddles/syndicate
|
||||
paddle_state = "defibcombat-paddles"
|
||||
powered_state = null
|
||||
emagged_state = null
|
||||
|
||||
/obj/item/defibrillator/compact/combat/loaded
|
||||
cell_removable = FALSE // Don't let people just have an infinite power cell
|
||||
|
||||
/obj/item/defibrillator/compact/combat/loaded/Initialize(mapload)
|
||||
. = ..()
|
||||
cell = new /obj/item/stock_parts/power_store/cell/infinite(src)
|
||||
update_power()
|
||||
|
||||
/obj/item/defibrillator/compact/combat/loaded/nanotrasen
|
||||
name = "elite Nanotrasen defibrillator"
|
||||
desc = "A belt-equipped state-of-the-art defibrillator. Can revive through thick clothing, has an experimental self-recharging battery, and can be utilized as a weapon via applying the paddles while in a combat stance."
|
||||
icon_state = "defibnt" //needs defib inhand sprites
|
||||
inhand_icon_state = null
|
||||
worn_icon_state = "defibnt"
|
||||
paddle_type = /obj/item/shockpaddles/syndicate/nanotrasen
|
||||
paddle_state = "defibnt-paddles"
|
||||
|
||||
//paddles
|
||||
|
||||
/obj/item/shockpaddles
|
||||
name = "defibrillator paddles"
|
||||
desc = "A pair of plastic-gripped paddles with flat metal surfaces that are used to deliver powerful electric shocks."
|
||||
icon = 'icons/obj/medical/defib.dmi'
|
||||
icon_state = "defibpaddles0"
|
||||
inhand_icon_state = "defibpaddles0"
|
||||
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
|
||||
spawn_blacklisted = TRUE
|
||||
|
||||
force = 0
|
||||
throwforce = 6
|
||||
w_class = WEIGHT_CLASS_BULKY
|
||||
resistance_flags = INDESTRUCTIBLE
|
||||
base_icon_state = "defibpaddles"
|
||||
|
||||
var/revivecost = STANDARD_CELL_CHARGE * 0.1
|
||||
var/cooldown = FALSE
|
||||
var/busy = FALSE
|
||||
var/obj/item/defibrillator/defib
|
||||
var/req_defib = TRUE // Whether or not the paddles require a defibrilator object
|
||||
var/recharge_time = 6 SECONDS // Only applies to defibs that do not require a defibrilator. See: do_success()
|
||||
var/combat = FALSE //If it penetrates armor and gives additional functionality
|
||||
|
||||
/obj/item/shockpaddles/Initialize(mapload)
|
||||
. = ..()
|
||||
AddElement(/datum/element/update_icon_updates_onmob, ITEM_SLOT_BACK)
|
||||
AddComponent(/datum/component/two_handed, force_unwielded=8, force_wielded=12)
|
||||
RegisterSignal(src, COMSIG_HUMAN_NON_STORAGE_HOTKEY, PROC_REF(on_non_storage_hotkey))
|
||||
|
||||
/obj/item/shockpaddles/Destroy()
|
||||
defib = null
|
||||
return ..()
|
||||
|
||||
/obj/item/shockpaddles/equipped(mob/user, slot)
|
||||
. = ..()
|
||||
if(!req_defib)
|
||||
return
|
||||
RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(check_range))
|
||||
RegisterSignal(defib, COMSIG_MOVABLE_MOVED, PROC_REF(check_range))
|
||||
|
||||
/obj/item/shockpaddles/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
|
||||
. = ..()
|
||||
check_range()
|
||||
|
||||
/obj/item/shockpaddles/fire_act(exposed_temperature, exposed_volume)
|
||||
. = ..()
|
||||
if((req_defib && defib) && loc != defib)
|
||||
defib.fire_act(exposed_temperature, exposed_volume)
|
||||
|
||||
/obj/item/shockpaddles/proc/check_range()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!req_defib || !defib)
|
||||
return
|
||||
if(!in_range(src,defib))
|
||||
if(isliving(loc))
|
||||
var/mob/living/user = loc
|
||||
to_chat(user, span_warning("[defib]'s paddles overextend and come out of your hands!"))
|
||||
else
|
||||
visible_message(span_notice("[src] snap back into [defib]."))
|
||||
snap_back()
|
||||
|
||||
/obj/item/shockpaddles/proc/recharge(time = 0)
|
||||
if(req_defib)
|
||||
return
|
||||
cooldown = TRUE
|
||||
update_appearance()
|
||||
addtimer(CALLBACK(src, PROC_REF(finish_recharge)), time)
|
||||
|
||||
/obj/item/shockpaddles/proc/finish_recharge()
|
||||
var/turf/current_turf = get_turf(src)
|
||||
current_turf.audible_message(span_notice("[src] beeps: Unit is recharged."))
|
||||
playsound(src, 'sound/machines/defib/defib_ready.ogg', 50, FALSE)
|
||||
cooldown = FALSE
|
||||
update_appearance()
|
||||
|
||||
/obj/item/shockpaddles/Initialize(mapload)
|
||||
. = ..()
|
||||
ADD_TRAIT(src, TRAIT_NO_STORAGE_INSERT, TRAIT_GENERIC) //stops shockpaddles from being inserted in BoH
|
||||
if(!req_defib)
|
||||
return //If it doesn't need a defib, just say it exists
|
||||
if (!loc || !istype(loc, /obj/item/defibrillator)) //To avoid weird issues from admin spawns
|
||||
return INITIALIZE_HINT_QDEL
|
||||
defib = loc
|
||||
busy = FALSE
|
||||
update_appearance()
|
||||
|
||||
/obj/item/shockpaddles/suicide_act(mob/living/user)
|
||||
user.visible_message(span_danger("[user] is putting the live paddles on [user.p_their()] chest! It looks like [user.p_theyre()] trying to commit suicide!"))
|
||||
if(req_defib)
|
||||
defib.deductcharge(revivecost)
|
||||
playsound(src, 'sound/machines/defib/defib_zap.ogg', 50, TRUE, -1)
|
||||
return OXYLOSS
|
||||
|
||||
/obj/item/shockpaddles/update_icon_state()
|
||||
icon_state = "[base_icon_state][HAS_TRAIT(src, TRAIT_WIELDED)]"
|
||||
inhand_icon_state = icon_state
|
||||
if(cooldown)
|
||||
icon_state = "[base_icon_state][HAS_TRAIT(src, TRAIT_WIELDED)]_cooldown"
|
||||
return ..()
|
||||
|
||||
/obj/item/shockpaddles/dropped(mob/user)
|
||||
if(!req_defib)
|
||||
return ..()
|
||||
UnregisterSignal(defib, COMSIG_MOVABLE_MOVED)
|
||||
if(user)
|
||||
UnregisterSignal(user, COMSIG_MOVABLE_MOVED)
|
||||
to_chat(user, span_notice("The paddles snap back into the main unit."))
|
||||
snap_back()
|
||||
return ..()
|
||||
|
||||
/obj/item/shockpaddles/proc/snap_back()
|
||||
if(!defib)
|
||||
return
|
||||
defib.on = FALSE
|
||||
forceMove(defib)
|
||||
defib.update_power()
|
||||
|
||||
/obj/item/shockpaddles/attack(mob/M, mob/living/user, list/modifiers, list/attack_modifiers)
|
||||
if(busy)
|
||||
return
|
||||
defib?.update_power()
|
||||
if(req_defib && !defib.powered)
|
||||
user.visible_message(span_warning("[defib] beeps: Not enough charge!"))
|
||||
playsound(src, 'sound/machines/defib/defib_failed.ogg', 50, FALSE)
|
||||
return
|
||||
if(!HAS_TRAIT(src, TRAIT_WIELDED))
|
||||
if(iscyborg(user))
|
||||
to_chat(user, span_warning("You must activate the paddles in your active module before you can use them on someone!"))
|
||||
else
|
||||
to_chat(user, span_warning("You need to wield the paddles in both hands before you can use them on someone!"))
|
||||
return
|
||||
if(cooldown)
|
||||
if(req_defib)
|
||||
to_chat(user, span_warning("[defib] is recharging!"))
|
||||
else
|
||||
to_chat(user, span_warning("[src] are recharging!"))
|
||||
return
|
||||
|
||||
if(LAZYACCESS(modifiers, RIGHT_CLICK))
|
||||
do_disarm(M, user)
|
||||
return
|
||||
|
||||
if(!iscarbon(M))
|
||||
if(req_defib)
|
||||
to_chat(user, span_warning("The instructions on [defib] don't mention how to revive that..."))
|
||||
else
|
||||
to_chat(user, span_warning("You aren't sure how to revive that..."))
|
||||
return
|
||||
var/mob/living/carbon/H = M
|
||||
|
||||
if(user.zone_selected != BODY_ZONE_CHEST)
|
||||
to_chat(user, span_warning("You need to target your patient's chest with [src]!"))
|
||||
return
|
||||
|
||||
if(user.combat_mode)
|
||||
do_harm(H, user)
|
||||
return
|
||||
|
||||
if(H.can_defib() == DEFIB_POSSIBLE)
|
||||
H.notify_revival("Your heart is being defibrillated!")
|
||||
H.grab_ghost() // Shove them back in their body.
|
||||
|
||||
do_help(H, user)
|
||||
|
||||
/// Called whenever the paddles successfully shock something
|
||||
/obj/item/shockpaddles/proc/do_success()
|
||||
if(busy)
|
||||
busy = FALSE
|
||||
|
||||
update_appearance()
|
||||
if(SEND_SIGNAL(src, COMSIG_DEFIBRILLATOR_SUCCESS) & COMPONENT_DEFIB_STOP)
|
||||
return
|
||||
recharge(recharge_time)
|
||||
|
||||
/// Called whenever the paddles fail to shock something after a do_x proc
|
||||
/obj/item/shockpaddles/proc/do_cancel()
|
||||
if(busy)
|
||||
busy = FALSE
|
||||
|
||||
update_appearance()
|
||||
|
||||
/obj/item/shockpaddles/proc/shock_pulling(dmg, mob/H)
|
||||
if(isliving(H.pulledby)) //CLEAR!
|
||||
var/mob/living/M = H.pulledby
|
||||
if(M.electrocute_act(dmg, H))
|
||||
M.visible_message(span_danger("[M] is electrocuted by [M.p_their()] contact with [H]!"))
|
||||
M.emote("scream")
|
||||
|
||||
/obj/item/shockpaddles/proc/do_disarm(mob/living/M, mob/living/user)
|
||||
if(!DEFIB_CAN_HURT(src))
|
||||
return
|
||||
busy = TRUE
|
||||
M.visible_message(span_danger("[user] touches [M] with [src]!"), \
|
||||
span_userdanger("[user] touches [M] with [src]!"))
|
||||
M.adjust_stamina_loss(60)
|
||||
M.Knockdown(75)
|
||||
M.set_jitter_if_lower(100 SECONDS)
|
||||
M.apply_status_effect(/datum/status_effect/convulsing)
|
||||
playsound(src, 'sound/machines/defib/defib_zap.ogg', 50, TRUE, -1)
|
||||
if(HAS_TRAIT(M,MOB_ORGANIC))
|
||||
M.emote("gasp")
|
||||
log_combat(user, M, "zapped", src)
|
||||
do_success()
|
||||
|
||||
/obj/item/shockpaddles/proc/do_harm(mob/living/carbon/H, mob/living/user)
|
||||
if(!DEFIB_CAN_HURT(src))
|
||||
return
|
||||
user.visible_message(span_warning("[user] begins to place [src] on [H]'s chest."),
|
||||
span_warning("You overcharge the paddles and begin to place them onto [H]'s chest..."))
|
||||
busy = TRUE
|
||||
update_appearance()
|
||||
if(do_after(user, 1.5 SECONDS, H, extra_checks = CALLBACK(src, PROC_REF(is_wielded))))
|
||||
user.visible_message(span_notice("[user] places [src] on [H]'s chest."),
|
||||
span_warning("You place [src] on [H]'s chest and begin to charge them."))
|
||||
var/turf/T = get_turf(defib)
|
||||
playsound(src, 'sound/machines/defib/defib_charge.ogg', 50, FALSE)
|
||||
if(req_defib)
|
||||
T.audible_message(span_warning("\The [defib] lets out an urgent beep and lets out a steadily rising hum..."))
|
||||
else
|
||||
user.audible_message(span_warning("[src] let out an urgent beep."))
|
||||
if(do_after(user, 1.5 SECONDS, H, extra_checks = CALLBACK(src, PROC_REF(is_wielded)))) //Takes longer due to overcharging
|
||||
if(!H)
|
||||
do_cancel()
|
||||
return
|
||||
if(H && H.stat == DEAD)
|
||||
to_chat(user, span_warning("[H] is dead."))
|
||||
playsound(src, 'sound/machines/defib/defib_failed.ogg', 50, FALSE)
|
||||
do_cancel()
|
||||
return
|
||||
user.visible_message(span_bolddanger("<i>[user] shocks [H] with \the [src]!"), span_warning("You shock [H] with \the [src]!"))
|
||||
playsound(src, 'sound/machines/defib/defib_zap.ogg', 100, TRUE, -1)
|
||||
playsound(src, 'sound/items/weapons/egloves.ogg', 100, TRUE, -1)
|
||||
H.emote("scream")
|
||||
shock_pulling(45, H)
|
||||
if(H.can_heartattack() && !H.undergoing_cardiac_arrest())
|
||||
if(!H.stat)
|
||||
H.visible_message(span_warning("[H] thrashes wildly, clutching at [H.p_their()] chest!"),
|
||||
span_userdanger("You feel a horrible agony in your chest!"))
|
||||
H.set_heartattack(TRUE)
|
||||
H.apply_damage(50, BURN, BODY_ZONE_CHEST)
|
||||
log_combat(user, H, "overloaded the heart of", defib)
|
||||
H.Paralyze(100)
|
||||
H.set_jitter_if_lower(200 SECONDS)
|
||||
do_success()
|
||||
return
|
||||
do_cancel()
|
||||
|
||||
/obj/item/shockpaddles/proc/do_help(mob/living/carbon/H, mob/living/user)
|
||||
user.visible_message(span_warning("[user] begins to place [src] on [H]'s chest."), span_warning("You begin to place [src] on [H]'s chest..."))
|
||||
busy = TRUE
|
||||
update_appearance()
|
||||
if(do_after(user, 3 SECONDS, H, extra_checks = CALLBACK(src, PROC_REF(is_wielded)))) //beginning to place the paddles on patient's chest to allow some time for people to move away to stop the process
|
||||
user.visible_message(span_notice("[user] places [src] on [H]'s chest."), span_warning("You place [src] on [H]'s chest."))
|
||||
playsound(src, 'sound/machines/defib/defib_charge.ogg', 75, FALSE)
|
||||
var/obj/item/organ/heart = H.get_organ_by_type(/obj/item/organ/heart)
|
||||
if(do_after(user, 2 SECONDS, H, extra_checks = CALLBACK(src, PROC_REF(is_wielded)))) //placed on chest and short delay to shock for dramatic effect, revive time is 5sec total
|
||||
if((!combat && !req_defib) || (req_defib && !defib.combat))
|
||||
for(var/obj/item/clothing/C in H.get_equipped_items())
|
||||
if((C.body_parts_covered & CHEST) && (C.clothing_flags & THICKMATERIAL)) //check to see if something is obscuring their chest.
|
||||
user.audible_message(span_warning("[req_defib ? "[defib]" : "[src]"] buzzes: Patient's chest is obscured. Operation aborted."))
|
||||
playsound(src, 'sound/machines/defib/defib_failed.ogg', 50, FALSE)
|
||||
do_cancel()
|
||||
return
|
||||
if(SEND_SIGNAL(H, COMSIG_DEFIBRILLATOR_PRE_HELP_ZAP, user, src) & COMPONENT_DEFIB_STOP)
|
||||
do_cancel()
|
||||
return
|
||||
if(H.stat == DEAD)
|
||||
H.visible_message(span_warning("[H]'s body convulses a bit."))
|
||||
playsound(src, SFX_BODYFALL, 50, TRUE)
|
||||
playsound(src, 'sound/machines/defib/defib_zap.ogg', 75, TRUE, -1)
|
||||
shock_pulling(30, H)
|
||||
|
||||
var/defib_result = H.can_defib()
|
||||
var/fail_reason
|
||||
|
||||
switch (defib_result)
|
||||
if (DEFIB_FAIL_SUICIDE)
|
||||
fail_reason = "Recovery of patient impossible. Further attempts futile."
|
||||
if (DEFIB_FAIL_NO_HEART)
|
||||
fail_reason = "Patient's heart is missing."
|
||||
if (DEFIB_FAIL_FAILING_HEART)
|
||||
fail_reason = "Patient's heart too damaged, replace or repair and try again."
|
||||
if (DEFIB_FAIL_TISSUE_DAMAGE)
|
||||
fail_reason = "Tissue damage too severe, repair and try again."
|
||||
if (DEFIB_FAIL_HUSK)
|
||||
fail_reason = "Patient's body is a mere husk, repair and try again."
|
||||
if (DEFIB_FAIL_FAILING_BRAIN)
|
||||
fail_reason = "Patient's brain is too damaged, repair and try again."
|
||||
if (DEFIB_FAIL_NO_INTELLIGENCE)
|
||||
fail_reason = "No intelligence pattern can be detected in patient's brain. Further attempts futile."
|
||||
if (DEFIB_FAIL_NO_BRAIN)
|
||||
fail_reason = "Patient's brain is missing. Further attempts futile."
|
||||
if (DEFIB_FAIL_BLACKLISTED)
|
||||
fail_reason = "Patient has been blacklisted from revival. Further attempts futile."
|
||||
if (DEFIB_FAIL_GOLEM)
|
||||
fail_reason = "Patient is constructed from inorganic materials. Further attempts futile, though manual reconstruction is possible."
|
||||
|
||||
if(fail_reason)
|
||||
user.visible_message(span_warning("[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed - [fail_reason]"))
|
||||
playsound(src, 'sound/machines/defib/defib_failed.ogg', 50, FALSE)
|
||||
else
|
||||
var/total_brute = H.get_brute_loss()
|
||||
var/total_burn = H.get_fire_loss()
|
||||
|
||||
var/need_mob_update = FALSE
|
||||
//If the body has been fixed so that they would not be in crit when defibbed, give them oxyloss to put them back into crit
|
||||
if (H.health > HALFWAYCRITDEATH)
|
||||
need_mob_update += H.adjust_oxy_loss(H.health - HALFWAYCRITDEATH, updating_health = FALSE)
|
||||
else
|
||||
var/overall_damage = total_brute + total_burn + H.get_tox_loss() + H.get_oxy_loss()
|
||||
var/mobhealth = H.health
|
||||
need_mob_update += H.adjust_oxy_loss((mobhealth - HALFWAYCRITDEATH) * (H.get_oxy_loss() / overall_damage), updating_health = FALSE)
|
||||
need_mob_update += H.adjust_tox_loss((mobhealth - HALFWAYCRITDEATH) * (H.get_tox_loss() / overall_damage), updating_health = FALSE, forced = TRUE) // force tox heal for toxin lovers too
|
||||
need_mob_update += H.adjust_fire_loss((mobhealth - HALFWAYCRITDEATH) * (total_burn / overall_damage), updating_health = FALSE)
|
||||
need_mob_update += H.adjust_brute_loss((mobhealth - HALFWAYCRITDEATH) * (total_brute / overall_damage), updating_health = FALSE)
|
||||
if(need_mob_update)
|
||||
H.updatehealth() // Previous "adjust" procs don't update health, so we do it manually.
|
||||
user.visible_message(span_notice("[req_defib ? "[defib]" : "[src]"] pings: Resuscitation successful."))
|
||||
playsound(src, 'sound/machines/defib/defib_success.ogg', 50, FALSE)
|
||||
H.set_heartattack(FALSE)
|
||||
if(defib_result == DEFIB_POSSIBLE)
|
||||
H.grab_ghost()
|
||||
H.revive()
|
||||
H.emote("gasp")
|
||||
H.set_jitter_if_lower(200 SECONDS)
|
||||
SEND_SIGNAL(H, COMSIG_LIVING_MINOR_SHOCK)
|
||||
if(HAS_MIND_TRAIT(user, TRAIT_MORBID))
|
||||
user.add_mood_event("morbid_saved_life", /datum/mood_event/morbid_saved_life)
|
||||
else
|
||||
user.add_mood_event("saved_life", /datum/mood_event/saved_life)
|
||||
log_combat(user, H, "revived", defib)
|
||||
do_success()
|
||||
return
|
||||
else if (!H.get_organ_by_type(/obj/item/organ/heart))
|
||||
user.visible_message(span_warning("[req_defib ? "[defib]" : "[src]"] buzzes: Patient's heart is missing. Operation aborted."))
|
||||
playsound(src, 'sound/machines/defib/defib_failed.ogg', 50, FALSE)
|
||||
else if(H.undergoing_cardiac_arrest())
|
||||
playsound(src, 'sound/machines/defib/defib_zap.ogg', 50, TRUE, -1)
|
||||
if(!(heart.organ_flags & ORGAN_FAILING))
|
||||
H.set_heartattack(FALSE)
|
||||
do_success()
|
||||
user.visible_message(span_notice("[req_defib ? "[defib]" : "[src]"] pings: Patient's heart is now beating again."))
|
||||
else
|
||||
user.visible_message(span_warning("[req_defib ? "[defib]" : "[src]"] buzzes: Resuscitation failed, heart damage detected."))
|
||||
else if(H.has_status_effect(/datum/status_effect/heart_attack))
|
||||
user.visible_message(span_notice("[req_defib ? "[defib]" : "[src]"] pings: Patient's heart has stabilized, further applications may be necessary."))
|
||||
SEND_SIGNAL(H, COMSIG_HEARTATTACK_DEFIB)
|
||||
playsound(src, 'sound/machines/defib/defib_zap.ogg', 50, TRUE, -1)
|
||||
do_success()
|
||||
else
|
||||
user.visible_message(span_warning("[req_defib ? "[defib]" : "[src]"] buzzes: Patient is not in a valid state. Operation aborted."))
|
||||
playsound(src, 'sound/machines/defib/defib_failed.ogg', 50, FALSE)
|
||||
do_cancel()
|
||||
|
||||
/obj/item/shockpaddles/proc/is_wielded()
|
||||
return HAS_TRAIT(src, TRAIT_WIELDED)
|
||||
|
||||
/obj/item/shockpaddles/proc/on_non_storage_hotkey(datum/source, mob/living/carbon/human/user, obj/item/possible_storage)
|
||||
SIGNAL_HANDLER
|
||||
if(possible_storage == defib)
|
||||
user.dropItemToGround(src)
|
||||
return COMPONENT_STORAGE_HOTKEY_HANDLED
|
||||
return NONE
|
||||
|
||||
/obj/item/shockpaddles/cyborg
|
||||
name = "cyborg defibrillator paddles"
|
||||
icon = 'icons/obj/medical/defib.dmi'
|
||||
icon_state = "defibpaddles0"
|
||||
inhand_icon_state = "defibpaddles0"
|
||||
req_defib = FALSE
|
||||
|
||||
/obj/item/shockpaddles/cyborg/attack(mob/M, mob/user)
|
||||
if(iscyborg(user))
|
||||
var/mob/living/silicon/robot/R = user
|
||||
if(R.emagged)
|
||||
combat = TRUE
|
||||
else
|
||||
combat = FALSE
|
||||
else
|
||||
combat = FALSE
|
||||
|
||||
. = ..()
|
||||
|
||||
/obj/item/shockpaddles/syndicate
|
||||
name = "syndicate defibrillator paddles"
|
||||
desc = "A pair of paddles used to revive deceased operatives. They possess both the ability to penetrate armor and to deliver powerful or disabling shocks offensively."
|
||||
combat = TRUE
|
||||
icon = 'icons/obj/medical/defib.dmi'
|
||||
icon_state = "syndiepaddles0"
|
||||
inhand_icon_state = "syndiepaddles0"
|
||||
base_icon_state = "syndiepaddles"
|
||||
|
||||
/obj/item/shockpaddles/syndicate/nanotrasen
|
||||
name = "elite Nanotrasen defibrillator paddles"
|
||||
desc = "A pair of paddles used to revive deceased ERT members. They possess both the ability to penetrate armor and to deliver powerful or disabling shocks offensively."
|
||||
icon_state = "ntpaddles0"
|
||||
inhand_icon_state = "ntpaddles0"
|
||||
base_icon_state = "ntpaddles"
|
||||
|
||||
/obj/item/shockpaddles/syndicate/cyborg
|
||||
req_defib = FALSE
|
||||
|
||||
#undef HALFWAYCRITDEATH
|
||||
#undef DEFIB_CAN_HURT
|
||||
@@ -0,0 +1,21 @@
|
||||
/obj/item/reflexhammer
|
||||
name = "reflex hammer"
|
||||
desc = "A small plastic headed hammer, used to test for neurological damage."
|
||||
icon = 'icons/obj/weapons/hammer.dmi'
|
||||
icon_state = "reflex_hammer"
|
||||
icon_angle = -135
|
||||
force = 1
|
||||
throwforce = 1
|
||||
custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*0.75, /datum/material/plastic=SMALL_MATERIAL_AMOUNT*0.5)
|
||||
w_class = WEIGHT_CLASS_TINY
|
||||
attack_verb_continuous = list("tests", "jerks", "bonks", "taps")
|
||||
attack_verb_simple = list("test", "jerk", "bonk", "tap")
|
||||
|
||||
/obj/item/reflexhammer/Initialize(mapload)
|
||||
. = ..()
|
||||
AddElement(/datum/element/kneejerk)
|
||||
|
||||
/obj/item/reflexhammer/suicide_act(mob/living/user)
|
||||
user.visible_message(span_suicide("[user] is beating [user.p_them()]self to death with [src]! Unfortunately, it's not a real hammer! It looks like [user.p_theyre()] trying to commit suicide!"))
|
||||
playsound(loc, 'sound/items/gavel.ogg', 50, TRUE, -1)
|
||||
return STAMINALOSS | SHAME
|
||||
@@ -0,0 +1,348 @@
|
||||
//Items for nuke theft, supermatter theft traitor objective
|
||||
|
||||
|
||||
// STEALING THE NUKE
|
||||
|
||||
//the nuke core - objective item
|
||||
/obj/item/nuke_core
|
||||
name = "plutonium core"
|
||||
desc = "Extremely radioactive. Wear goggles."
|
||||
icon = 'icons/obj/antags/syndicate_tools.dmi'
|
||||
icon_state = "plutonium_core"
|
||||
inhand_icon_state = "plutoniumcore"
|
||||
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
|
||||
var/pulse = 0
|
||||
var/cooldown = 0
|
||||
var/pulseicon = "plutonium_core_pulse"
|
||||
|
||||
/obj/item/nuke_core/Initialize(mapload)
|
||||
. = ..()
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
/obj/item/nuke_core/Destroy()
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
return ..()
|
||||
|
||||
/obj/item/nuke_core/attackby(obj/item/nuke_core_container/container, mob/user)
|
||||
if(istype(container))
|
||||
container.load(src, user)
|
||||
else
|
||||
return ..()
|
||||
|
||||
/obj/item/nuke_core/process()
|
||||
if(cooldown < world.time - 60)
|
||||
cooldown = world.time
|
||||
flick(pulseicon, src)
|
||||
radiation_pulse(src, max_range = 2, threshold = RAD_EXTREME_INSULATION)
|
||||
|
||||
/obj/item/nuke_core/suicide_act(mob/living/user)
|
||||
user.visible_message(span_suicide("[user] is rubbing [src] against [user.p_them()]self! It looks like [user.p_theyre()] trying to commit suicide!"))
|
||||
return TOXLOSS
|
||||
|
||||
//nuke core box, for carrying the core
|
||||
/obj/item/nuke_core_container
|
||||
name = "nuke core container"
|
||||
desc = "Solid container for radioactive objects."
|
||||
icon = 'icons/obj/antags/syndicate_tools.dmi'
|
||||
icon_state = "core_container_empty"
|
||||
inhand_icon_state = "tile"
|
||||
lefthand_file = 'icons/mob/inhands/items/tiles_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/items/tiles_righthand.dmi'
|
||||
var/obj/item/nuke_core/core
|
||||
|
||||
/obj/item/nuke_core_container/Destroy()
|
||||
QDEL_NULL(core)
|
||||
return ..()
|
||||
|
||||
/obj/item/nuke_core_container/proc/load(obj/item/nuke_core/ncore, mob/user)
|
||||
if(core || !istype(ncore))
|
||||
return FALSE
|
||||
ncore.forceMove(src)
|
||||
core = ncore
|
||||
icon_state = "core_container_loaded"
|
||||
to_chat(user, span_warning("Container is sealing..."))
|
||||
addtimer(CALLBACK(src, PROC_REF(seal)), 5 SECONDS)
|
||||
return TRUE
|
||||
|
||||
/obj/item/nuke_core_container/proc/seal()
|
||||
if(istype(core))
|
||||
STOP_PROCESSING(SSobj, core)
|
||||
icon_state = "core_container_sealed"
|
||||
playsound(src, 'sound/items/deconstruct.ogg', 60, TRUE)
|
||||
if(ismob(loc))
|
||||
to_chat(loc, span_warning("[src] is permanently sealed, [core]'s radiation is contained."))
|
||||
|
||||
/obj/item/nuke_core_container/attackby(obj/item/nuke_core/core, mob/user)
|
||||
if(istype(core))
|
||||
if(!user.temporarilyRemoveItemFromInventory(core))
|
||||
to_chat(user, span_warning("The [core] is stuck to your hand!"))
|
||||
return
|
||||
else
|
||||
load(core, user)
|
||||
else
|
||||
return ..()
|
||||
|
||||
//snowflake screwdriver, works as a key to start nuke theft, traitor only
|
||||
/obj/item/screwdriver/nuke
|
||||
name = "screwdriver"
|
||||
desc = "A screwdriver with an ultra thin tip that's carefully designed to boost screwing speed."
|
||||
icon = 'icons/obj/antags/syndicate_tools.dmi'
|
||||
icon_state = "screwdriver_nuke"
|
||||
post_init_icon_state = null
|
||||
inhand_icon_state = "screwdriver_nuke"
|
||||
toolspeed = 0.5
|
||||
random_color = FALSE
|
||||
greyscale_config_inhand_left = null
|
||||
greyscale_config_inhand_right = null
|
||||
greyscale_colors = null
|
||||
|
||||
/obj/item/screwdriver/nuke/get_belt_overlay()
|
||||
return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "screwdriver_nuke")
|
||||
|
||||
/obj/item/paper/guides/antag/nuke_instructions
|
||||
default_raw_text = "How to break into a Nanotrasen self-destruct terminal and remove its plutonium core:<br>\
|
||||
<ul>\
|
||||
<li>Use a screwdriver with a very thin tip (provided) to unscrew the terminal's front panel</li>\
|
||||
<li>Dislodge and remove the front panel with a crowbar</li>\
|
||||
<li>Cut the inner metal plate with a welding tool</li>\
|
||||
<li>Pry off the inner plate with a crowbar to expose the radioactive core</li>\
|
||||
<li>Use the core container to remove the plutonium core; the container will take some time to seal</li>\
|
||||
<li>???</li>\
|
||||
</ul>"
|
||||
|
||||
/obj/item/paper/guides/antag/hdd_extraction
|
||||
default_raw_text = "<h1>Source Code Theft & You - The Idiot's Guide to Crippling Nanotrasen Research & Development</h1><br>\
|
||||
Rumour has it that Nanotrasen are using their R&D Servers to create something awful! They've codenamed it Project Goon, whatever that means.<br>\
|
||||
This cannot be allowed to stand. Below is all our intel for stealing their source code and crippling their research efforts:<br>\
|
||||
<ul>\
|
||||
<li>Locate the physical R&D Server mainframes. Intel suggests these are stored in specially cooled rooms deep within their Science department.</li>\
|
||||
<li>Nanotrasen is a corporation not known for subtlety in design. You should be able to identify the master server by any distinctive markings.</li>\
|
||||
<li>Tools are on-site procurement. Screwdriver, crowbar and wirecutters should be all you need to do the job.<li>\
|
||||
<li>The front panel screws are hidden in recesses behind stickers. Easily removed once you know they're there.</li>\
|
||||
<li>You'll probably find the hard drive in secure housing. You may need to pry it loose with a crowbar, shouldn't do too much damage.</li>\
|
||||
<li>Finally, carefully cut all of the hard drive's connecting wires. Don't rush this, snipping the wrong wire could wipe all data!</li>\
|
||||
</ul>\
|
||||
Removing this drive is guaranteed to cripple research efforts regardless of what data is contained on it.<br>\
|
||||
The thing's probably hardwired. No putting it back once you've extracted it. The crew are likely to be as mad as bees if they find out!<br>\
|
||||
Survive the shift and extract the hard drive safely.<br>\
|
||||
Succeed and you will receive a coveted green highlight on your record for this assignment. Fail us and red's the last colour you'll ever see.<br>\
|
||||
Do not disappoint us.<br>"
|
||||
|
||||
/obj/item/disk/computer/hdd_theft
|
||||
name = "r&d server hard disk drive"
|
||||
desc = "For some reason, people really seem to want to steal this. The source code on this drive is probably used for something awful!"
|
||||
max_capacity = 512
|
||||
w_class = WEIGHT_CLASS_NORMAL
|
||||
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
|
||||
sticker_icon_state = "o_nt"
|
||||
|
||||
// STEALING SUPERMATTER
|
||||
|
||||
/obj/item/paper/guides/antag/supermatter_sliver
|
||||
default_raw_text = "How to safely extract a supermatter sliver:<br>\
|
||||
<ul>\
|
||||
<li>You must have active magnetic anchoring at all times near an active supermatter crystal.</li>\
|
||||
<li>Approach an active supermatter crystal with radiation shielded personal protective equipment. DO NOT MAKE PHYSICAL CONTACT.</li>\
|
||||
<li>Use a supermatter scalpel (provided) to slice off a sliver of the crystal.</li>\
|
||||
<li>Use supermatter extraction tongs (also provided) to safely pick up the sliver you sliced off.</li>\
|
||||
<li>Physical contact of any object with the sliver will dust the object, as well as yourself.</li>\
|
||||
<li>Use the tongs to place the sliver into the provided container, which will take some time to seal.</li>\
|
||||
<li>Get the hell out before the crystal delaminates.</li>\
|
||||
<li>???</li>\
|
||||
</ul>"
|
||||
|
||||
/obj/item/nuke_core/supermatter_sliver
|
||||
name = "supermatter sliver"
|
||||
desc = "A tiny, highly volatile sliver of a supermatter crystal. Do not handle without protection!"
|
||||
icon_state = "supermatter_sliver"
|
||||
inhand_icon_state = null //touching it dusts you, so no need for an inhand icon.
|
||||
pulseicon = "supermatter_sliver_pulse"
|
||||
layer = ABOVE_MOB_LAYER
|
||||
|
||||
/obj/item/nuke_core/supermatter_sliver/Initialize(mapload)
|
||||
. = ..()
|
||||
RegisterSignal(src, COMSIG_FISHING_ROD_CAST, PROC_REF(on_hook))
|
||||
|
||||
/obj/item/nuke_core/supermatter_sliver/proc/on_hook(obj/item/nuke_core/supermatter_sliver/source, obj/item/fishing_rod/rod, mob/user)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
//hook gets dusted but the rod remains intact
|
||||
attackby(rod.hook, user)
|
||||
|
||||
return FISHING_ROD_CAST_HANDLED
|
||||
|
||||
/obj/item/nuke_core/supermatter_sliver/attack_tk(mob/user) // no TK dusting memes
|
||||
return
|
||||
|
||||
/obj/item/nuke_core/supermatter_sliver/can_be_pulled(user, force) // no drag memes
|
||||
return FALSE
|
||||
|
||||
/obj/item/nuke_core/supermatter_sliver/attackby(obj/item/W, mob/living/user, list/modifiers, list/attack_modifiers)
|
||||
if(istype(W, /obj/item/hemostat/supermatter))
|
||||
var/obj/item/hemostat/supermatter/tongs = W
|
||||
if (tongs.sliver)
|
||||
to_chat(user, span_warning("\The [tongs] is already holding a supermatter sliver!"))
|
||||
return FALSE
|
||||
forceMove(tongs)
|
||||
tongs.sliver = src
|
||||
tongs.update_appearance()
|
||||
to_chat(user, span_notice("You carefully pick up [src] with [tongs]."))
|
||||
else if(istype(W, /obj/item/scalpel/supermatter) || istype(W, /obj/item/nuke_core_container/supermatter/)) // we don't want it to dust
|
||||
return
|
||||
else
|
||||
to_chat(user, span_notice("As it touches \the [src], both \the [src] and \the [W] burst into dust!"))
|
||||
radiation_pulse(user, max_range = 2, threshold = RAD_EXTREME_INSULATION, chance = 40)
|
||||
playsound(src, 'sound/effects/supermatter.ogg', 50, TRUE)
|
||||
qdel(W)
|
||||
qdel(src)
|
||||
|
||||
/obj/item/nuke_core/supermatter_sliver/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
|
||||
if(!isliving(hit_atom))
|
||||
return ..()
|
||||
var/mob/living/victim = hit_atom
|
||||
if(victim.incorporeal_move || HAS_TRAIT(victim, TRAIT_GODMODE)) //try to keep this in sync with supermatter's consume fail conditions
|
||||
return ..()
|
||||
var/mob/thrower = throwingdatum?.get_thrower()
|
||||
if(istype(thrower))
|
||||
log_combat(thrower, hit_atom, "consumed", src)
|
||||
message_admins("[src] has consumed [key_name_admin(victim)] [ADMIN_JMP(src)], thrown by [key_name_admin(thrower)].")
|
||||
investigate_log("has consumed [key_name(victim)], thrown by [key_name(thrower)]", INVESTIGATE_ENGINE)
|
||||
else
|
||||
message_admins("[src] has consumed [key_name_admin(victim)] [ADMIN_JMP(src)] via throw impact.")
|
||||
investigate_log("has consumed [key_name(victim)] via throw impact.", INVESTIGATE_ENGINE)
|
||||
victim.visible_message(span_danger("As [victim] is hit by [src], both flash into dust and silence fills the room..."),\
|
||||
span_userdanger("You're hit by [src] and everything suddenly goes silent.\n[src] flashes into dust, and soon as you can register this, you do as well."),\
|
||||
span_hear("Everything suddenly goes silent."))
|
||||
victim.investigate_log("has been dusted by [src].", INVESTIGATE_DEATHS)
|
||||
victim.dust()
|
||||
radiation_pulse(src, max_range = 2, threshold = RAD_EXTREME_INSULATION, chance = 40)
|
||||
playsound(src, 'sound/effects/supermatter.ogg', 50, TRUE)
|
||||
qdel(src)
|
||||
|
||||
/obj/item/nuke_core/supermatter_sliver/pickup(mob/living/user)
|
||||
..()
|
||||
if(!isliving(user) || HAS_TRAIT(user, TRAIT_GODMODE)) //try to keep this in sync with supermatter's consume fail conditions
|
||||
return FALSE
|
||||
user.visible_message(span_danger("[user] reaches out and tries to pick up [src]. [user.p_their()] body starts to glow and bursts into flames before flashing into dust!"),\
|
||||
span_userdanger("You reach for [src] with your hands. That was dumb."),\
|
||||
span_hear("Everything suddenly goes silent."))
|
||||
radiation_pulse(user, max_range = 2, threshold = RAD_EXTREME_INSULATION, chance = 40)
|
||||
playsound(src, 'sound/effects/supermatter.ogg', 50, TRUE)
|
||||
user.investigate_log("has been dusted by [src].", INVESTIGATE_DEATHS)
|
||||
user.dust()
|
||||
|
||||
/obj/item/nuke_core_container/supermatter
|
||||
name = "supermatter bin"
|
||||
desc = "A tiny receptacle that releases an inert hyper-noblium mix upon sealing, allowing a sliver of a supermatter crystal to be safely stored."
|
||||
var/obj/item/nuke_core/supermatter_sliver/sliver
|
||||
|
||||
/obj/item/nuke_core_container/supermatter/Destroy()
|
||||
QDEL_NULL(sliver)
|
||||
return ..()
|
||||
|
||||
/obj/item/nuke_core_container/supermatter/load(obj/item/hemostat/supermatter/T, mob/user)
|
||||
if(!istype(T) || !T.sliver)
|
||||
return FALSE
|
||||
T.sliver.forceMove(src)
|
||||
sliver = T.sliver
|
||||
T.sliver = null
|
||||
T.icon_state = "supermatter_tongs"
|
||||
icon_state = "core_container_loaded"
|
||||
to_chat(user, span_warning("Container is sealing..."))
|
||||
addtimer(CALLBACK(src, PROC_REF(seal)), 5 SECONDS)
|
||||
return TRUE
|
||||
|
||||
/obj/item/nuke_core_container/supermatter/seal()
|
||||
if(istype(sliver))
|
||||
STOP_PROCESSING(SSobj, sliver)
|
||||
icon_state = "core_container_sealed"
|
||||
playsound(src, 'sound/items/Deconstruct.ogg', 60, TRUE)
|
||||
if(ismob(loc))
|
||||
to_chat(loc, span_warning("[src] is permanently sealed, [sliver] is safely contained."))
|
||||
|
||||
/obj/item/scalpel/supermatter
|
||||
name = "supermatter scalpel"
|
||||
desc = "A scalpel with a fragile tip of condensed hyper-noblium gas, searingly cold to the touch, that can safely shave a sliver off a supermatter crystal."
|
||||
icon = 'icons/obj/antags/syndicate_tools.dmi'
|
||||
icon_state = "supermatter_scalpel"
|
||||
toolspeed = 0.5
|
||||
damtype = BURN
|
||||
usesound = 'sound/items/weapons/bladeslice.ogg'
|
||||
var/usesLeft
|
||||
|
||||
/obj/item/scalpel/supermatter/Initialize(mapload)
|
||||
. = ..()
|
||||
usesLeft = rand(2, 4)
|
||||
|
||||
/obj/item/hemostat/supermatter
|
||||
name = "supermatter extraction tongs"
|
||||
desc = "A pair of tongs made from condensed hyper-noblium gas, searingly cold to the touch, that can safely grip a supermatter sliver."
|
||||
icon = 'icons/obj/antags/syndicate_tools.dmi'
|
||||
icon_state = "supermatter_tongs"
|
||||
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
|
||||
inhand_icon_state = "supermatter_tongs"
|
||||
toolspeed = 0.75
|
||||
damtype = BURN
|
||||
var/obj/item/nuke_core/supermatter_sliver/sliver
|
||||
|
||||
/obj/item/hemostat/supermatter/Initialize(mapload)
|
||||
. = ..()
|
||||
AddElement(/datum/element/update_icon_updates_onmob)
|
||||
|
||||
/obj/item/hemostat/supermatter/Destroy()
|
||||
QDEL_NULL(sliver)
|
||||
return ..()
|
||||
|
||||
/obj/item/hemostat/supermatter/update_icon_state()
|
||||
icon_state = "supermatter_tongs[sliver ? "_loaded" : null]"
|
||||
inhand_icon_state = "supermatter_tongs[sliver ? "_loaded" : null]"
|
||||
return ..()
|
||||
|
||||
/obj/item/hemostat/supermatter/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
|
||||
if(!sliver)
|
||||
return ..()
|
||||
if (istype(interacting_with, /obj/item/nuke_core_container/supermatter))
|
||||
var/obj/item/nuke_core_container/supermatter/container = interacting_with
|
||||
container.load(src, user)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
if(ismovable(interacting_with) && interacting_with != sliver)
|
||||
Consume(interacting_with, user)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
return ..()
|
||||
|
||||
/obj/item/hemostat/supermatter/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) // no instakill supermatter javelins
|
||||
if(sliver)
|
||||
sliver.forceMove(loc)
|
||||
visible_message(span_notice("\The [sliver] falls out of \the [src] as it hits the ground."))
|
||||
sliver = null
|
||||
update_appearance()
|
||||
return ..()
|
||||
|
||||
/obj/item/hemostat/supermatter/proc/Consume(atom/movable/AM, mob/living/user)
|
||||
if(ismob(AM))
|
||||
if(!isliving(AM))
|
||||
return
|
||||
var/mob/living/victim = AM
|
||||
if(victim.incorporeal_move || HAS_TRAIT(victim, TRAIT_GODMODE)) //try to keep this in sync with supermatter's consume fail conditions
|
||||
return
|
||||
victim.investigate_log("has been dusted by [src].", INVESTIGATE_DEATHS)
|
||||
victim.dust()
|
||||
message_admins("[src] has consumed [key_name_admin(victim)] [ADMIN_JMP(src)].")
|
||||
investigate_log("has consumed [key_name(victim)].", INVESTIGATE_ENGINE)
|
||||
else if(istype(AM, /obj/singularity))
|
||||
return
|
||||
else
|
||||
investigate_log("has consumed [AM].", INVESTIGATE_ENGINE)
|
||||
qdel(AM)
|
||||
if (user)
|
||||
log_combat(user, AM, "consumed", sliver, "via [src]")
|
||||
user.visible_message(span_danger("As [user] touches [AM] with \the [src], both flash into dust and silence fills the room..."),\
|
||||
span_userdanger("You touch [AM] with [src], and everything suddenly goes silent.\n[AM] and [sliver] flash into dust, and soon as you can register this, you do as well."),\
|
||||
span_hear("Everything suddenly goes silent."))
|
||||
user.investigate_log("has been dusted by [src].", INVESTIGATE_DEATHS)
|
||||
user.dust()
|
||||
radiation_pulse(src, max_range = 2, threshold = RAD_EXTREME_INSULATION, chance = 40)
|
||||
playsound(src, 'sound/effects/supermatter.ogg', 50, TRUE)
|
||||
QDEL_NULL(sliver)
|
||||
update_appearance()
|
||||
Reference in New Issue
Block a user