mirror of
https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13.git
synced 2025-12-10 09:54:52 +00:00
949 lines
34 KiB
Plaintext
949 lines
34 KiB
Plaintext
GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/effects/fire.dmi', "fire"))
|
|
|
|
GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
|
|
// if true, everyone item when created will have its name changed to be
|
|
// more... RPG-like.
|
|
|
|
GLOBAL_VAR_INIT(stickpocalypse, FALSE) // if true, all non-embeddable items will be able to harmlessly stick to people when thrown
|
|
GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to embed in people, takes precedence over stickpocalypse
|
|
|
|
/obj/item
|
|
name = "item"
|
|
icon = 'icons/obj/items_and_weapons.dmi'
|
|
blocks_emissive = EMISSIVE_BLOCK_GENERIC
|
|
|
|
///icon state name for inhand overlays
|
|
var/item_state = null
|
|
///Icon file for left hand inhand overlays
|
|
var/lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
|
|
///Icon file for right inhand overlays
|
|
var/righthand_file = 'icons/mob/inhands/items_righthand.dmi'
|
|
|
|
///Icon file for mob worn overlays.
|
|
///no var for state because it should *always* be the same as icon_state
|
|
var/icon/mob_overlay_icon
|
|
//Forced mob worn layer instead of the standard preferred size.
|
|
var/alternate_worn_layer
|
|
|
|
var/icon/anthro_mob_worn_overlay //Version of the above dedicated to muzzles/digitigrade
|
|
var/icon/taur_mob_worn_overlay // Idem but for taurs. Currently only used by suits.
|
|
|
|
var/list/alternate_screams = list() //REEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
|
|
|
|
//Dimensions of the icon file used when this item is worn, eg: hats.dmi
|
|
//eg: 32x32 sprite, 64x64 sprite, etc.
|
|
//allows inhands/worn sprites to be of any size, but still centered on a mob properly
|
|
var/worn_x_dimension = 32
|
|
var/worn_y_dimension = 32
|
|
//Same as above but for inhands, uses the lefthand_ and righthand_ file vars
|
|
var/inhand_x_dimension = 32
|
|
var/inhand_y_dimension = 32
|
|
|
|
max_integrity = 200
|
|
|
|
obj_flags = NONE
|
|
var/item_flags = NONE
|
|
|
|
var/hitsound = null
|
|
var/usesound = null
|
|
var/throwhitsound = null
|
|
|
|
/// Weight class for how much storage capacity it uses and how big it physically is meaning storages can't hold it if their maximum weight class isn't as high as it.
|
|
var/w_class = WEIGHT_CLASS_NORMAL
|
|
/// Volume override for the item, otherwise automatically calculated from w_class.
|
|
var/w_volume
|
|
|
|
/// The amount of stamina it takes to swing an item in a normal melee attack do not lie to me and say it's for realism because it ain't. If null it will autocalculate from w_class.
|
|
var/total_mass //Total mass in arbitrary pound-like values. If there's no balance reasons for an item to have otherwise, this var should be the item's weight in pounds.
|
|
/// How long, in deciseconds, this staggers for, if null it will autocalculate from w_class and force. Unlike total mass this supports 0 and negatives.
|
|
var/stagger_force
|
|
|
|
/**
|
|
* Set FALSE and then checked at the end of on mob/living/attackby(), set TRUE on living/pre_attacked_by().
|
|
* Should it be FALSE by the end of the item/attack(), that means the item overrode the standard attack behaviour
|
|
* and the user still needs the delay applied. We can't be using return values since that'll stop afterattack() from being triggered.
|
|
*/
|
|
var/attack_delay_done = FALSE
|
|
///next_move click/attack delay of this item.
|
|
var/click_delay = CLICK_CD_MELEE
|
|
|
|
var/slot_flags = 0 //This is used to determine on which slots an item can fit.
|
|
pass_flags = PASSTABLE
|
|
pressure_resistance = 4
|
|
var/obj/item/master = null
|
|
|
|
var/heat_protection = 0 //flags which determine which body parts are protected from heat. Use the HEAD, CHEST, GROIN, etc. flags. See setup.dm
|
|
var/cold_protection = 0 //flags which determine which body parts are protected from cold. Use the HEAD, CHEST, GROIN, etc. flags. See setup.dm
|
|
var/max_heat_protection_temperature //Set this variable to determine up to which temperature (IN KELVIN) the item protects against heat damage. Keep at null to disable protection. Only protects areas set by heat_protection flags
|
|
var/min_cold_protection_temperature //Set this variable to determine down to which temperature (IN KELVIN) the item protects against cold damage. 0 is NOT an acceptable number due to if(varname) tests!! Keep at null to disable protection. Only protects areas set by cold_protection flags
|
|
|
|
var/list/actions //list of /datum/action's that this item has.
|
|
var/list/actions_types //list of paths of action datums to give to the item on New().
|
|
|
|
//Since any item can now be a piece of clothing, this has to be put here so all items share it.
|
|
var/flags_inv //This flag is used to determine when items in someone's inventory cover others. IE helmets making it so you can't see glasses, etc.
|
|
|
|
var/interaction_flags_item = INTERACT_ITEM_ATTACK_HAND_PICKUP
|
|
//Citadel Edit for digitigrade stuff
|
|
var/mutantrace_variation = NONE //Are there special sprites for specific situations? Don't use this unless you need to.
|
|
|
|
var/body_parts_covered = 0 //see setup.dm for appropriate bit flags
|
|
var/gas_transfer_coefficient = 1 // for leaking gas from turf to mask and vice-versa (for masks right now, but at some point, i'd like to include space helmets)
|
|
var/permeability_coefficient = 1 // for chemicals/diseases
|
|
var/siemens_coefficient = 1 // for electrical admittance/conductance (electrocution checks and shit)
|
|
var/slowdown = 0 // How much clothing is slowing you down. Negative values speeds you up
|
|
var/armour_penetration = 0 //percentage of armour effectiveness to remove
|
|
var/list/allowed = null //suit storage stuff.
|
|
var/equip_delay_self = 0 //In deciseconds, how long an item takes to equip; counts only for normal clothing slots, not pockets etc.
|
|
var/equip_delay_other = 20 //In deciseconds, how long an item takes to put on another person
|
|
var/strip_delay = 40 //In deciseconds, how long an item takes to remove from another person
|
|
var/breakouttime = 0
|
|
var/reskinned = FALSE
|
|
|
|
var/list/attack_verb //Used in attackby() to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]"
|
|
var/list/species_exception = null // list() of species types, if a species cannot put items in a certain slot, but species type is in list, it will be able to wear that item
|
|
|
|
var/mob/thrownby = null
|
|
|
|
mouse_drag_pointer = MOUSE_ACTIVE_POINTER //the icon to indicate this object is being dragged
|
|
|
|
var/list/embedding = NONE
|
|
|
|
var/flags_cover = 0 //for flags such as GLASSESCOVERSEYES
|
|
var/heat = 0
|
|
///All items with sharpness of IS_SHARP or higher will automatically get the butchering component.
|
|
var/sharpness = IS_BLUNT
|
|
|
|
var/tool_behaviour = NONE
|
|
var/toolspeed = 1
|
|
|
|
var/reach = 1 //In tiles, how far this weapon can reach; 1 for adjacent, which is default
|
|
|
|
//The list of slots by priority. equip_to_appropriate_slot() uses this list. Doesn't matter if a mob type doesn't have a slot.
|
|
var/list/slot_equipment_priority = null // for default list, see /mob/proc/equip_to_appropriate_slot()
|
|
|
|
// Needs to be in /obj/item because corgis can wear a lot of
|
|
// non-clothing items
|
|
var/datum/dog_fashion/dog_fashion = null
|
|
|
|
//Tooltip vars
|
|
var/force_string //string form of an item's force. Edit this var only to set a custom force string
|
|
var/last_force_string_check = 0
|
|
var/tip_timer
|
|
|
|
var/trigger_guard = TRIGGER_GUARD_NONE
|
|
|
|
///Used as the dye color source in the washing machine only (at the moment). Can be a hex color or a key corresponding to a registry entry, see washing_machine.dm
|
|
var/dye_color
|
|
///Whether the item is unaffected by standard dying.
|
|
var/undyeable = FALSE
|
|
///What dye registry should be looked at when dying this item; see washing_machine.dm
|
|
var/dying_key
|
|
|
|
//Grinder vars
|
|
var/list/grind_results //A reagent list containing the reagents this item produces when ground up in a grinder - this can be an empty list to allow for reagent transferring only
|
|
var/list/juice_results //A reagent list containing blah blah... but when JUICED in a grinder!
|
|
|
|
///Skills vars
|
|
//list of skill PATHS exercised when using this item. An associated bitfield can be set to indicate additional ways the skill is used by this specific item.
|
|
var/list/datum/skill/used_skills
|
|
var/skill_difficulty = THRESHOLD_UNTRAINED //how difficult it's to use this item in general.
|
|
var/skill_gain = DEF_SKILL_GAIN //base skill value gain from using this item.
|
|
|
|
var/canMouseDown = FALSE
|
|
|
|
|
|
/obj/item/Initialize()
|
|
|
|
if(attack_verb)
|
|
attack_verb = typelist("attack_verb", attack_verb)
|
|
|
|
. = ..()
|
|
for(var/path in actions_types)
|
|
new path(src)
|
|
actions_types = null
|
|
|
|
if(force_string)
|
|
item_flags |= FORCE_STRING_OVERRIDE
|
|
|
|
if(!hitsound)
|
|
if(damtype == "fire")
|
|
hitsound = 'sound/items/welder.ogg'
|
|
if(damtype == "brute")
|
|
hitsound = "swing_hit"
|
|
|
|
/obj/item/Destroy()
|
|
item_flags &= ~DROPDEL //prevent reqdels
|
|
if(ismob(loc))
|
|
var/mob/m = loc
|
|
m.temporarilyRemoveItemFromInventory(src, TRUE)
|
|
for(var/X in actions)
|
|
qdel(X)
|
|
return ..()
|
|
|
|
/obj/item/ComponentInitialize()
|
|
. = ..()
|
|
|
|
// this proc says it's for initializing components, but we're initializing elements too because it's you and me against the world >:)
|
|
if(embedding)
|
|
AddElement(/datum/element/embed, embedding)
|
|
else if(GLOB.embedpocalypse)
|
|
embedding = EMBED_POINTY
|
|
AddElement(/datum/element/embed, embedding)
|
|
name = "pointy [name]"
|
|
else if(GLOB.stickpocalypse)
|
|
embedding = EMBED_HARMLESS
|
|
AddElement(/datum/element/embed, embedding)
|
|
name = "sticky [name]"
|
|
|
|
if(GLOB.rpg_loot_items)
|
|
AddComponent(/datum/component/fantasy)
|
|
|
|
if(sharpness) //give sharp objects butchering functionality, for consistency
|
|
AddComponent(/datum/component/butchering, 80 * toolspeed)
|
|
|
|
/obj/item/proc/check_allowed_items(atom/target, not_inside, target_self)
|
|
if(((src in target) && !target_self) || (!isturf(target.loc) && !isturf(target) && not_inside))
|
|
return 0
|
|
else
|
|
return 1
|
|
|
|
/obj/item/blob_act(obj/structure/blob/B)
|
|
if(B && B.loc == loc)
|
|
qdel(src)
|
|
|
|
//user: The mob that is suiciding
|
|
//damagetype: The type of damage the item will inflict on the user
|
|
//BRUTELOSS = 1
|
|
//FIRELOSS = 2
|
|
//TOXLOSS = 4
|
|
//OXYLOSS = 8
|
|
//Output a creative message and then return the damagetype done
|
|
/obj/item/proc/suicide_act(mob/user)
|
|
return
|
|
|
|
/obj/item/verb/move_to_top()
|
|
set name = "Move To Top"
|
|
set category = "Object"
|
|
set src in oview(1)
|
|
|
|
var/mob/living/L = usr
|
|
if(!istype(L) || !isturf(loc) || !CHECK_MOBILITY(L, MOBILITY_USE))
|
|
return
|
|
|
|
var/turf/T = loc
|
|
loc = null
|
|
loc = T
|
|
|
|
/obj/item/examine(mob/user) //This might be spammy. Remove?
|
|
. = ..()
|
|
|
|
. += "[gender == PLURAL ? "They are" : "It is"] a [weightclass2text(w_class)] item."
|
|
|
|
if(resistance_flags & INDESTRUCTIBLE)
|
|
. += "[src] seems extremely robust! It'll probably withstand anything that could happen to it!"
|
|
else
|
|
if(resistance_flags & LAVA_PROOF)
|
|
. += "[src] is made of an extremely heat-resistant material, it'd probably be able to withstand lava!"
|
|
if(resistance_flags & (ACID_PROOF | UNACIDABLE))
|
|
. += "[src] looks pretty robust! It'd probably be able to withstand acid!"
|
|
if(resistance_flags & FREEZE_PROOF)
|
|
. += "[src] is made of cold-resistant materials."
|
|
if(resistance_flags & FIRE_PROOF)
|
|
. += "[src] is made of fire-retardant materials."
|
|
|
|
|
|
|
|
|
|
if(!user.research_scanner)
|
|
return
|
|
|
|
// Research prospects, including boostable nodes and point values.
|
|
// Deliver to a console to know whether the boosts have already been used.
|
|
var/list/research_msg = list("<font color='purple'>Research prospects:</font> ")
|
|
var/sep = ""
|
|
var/list/boostable_nodes = techweb_item_boost_check(src)
|
|
if (boostable_nodes)
|
|
for(var/id in boostable_nodes)
|
|
var/datum/techweb_node/node = SSresearch.techweb_node_by_id(id)
|
|
if(!node)
|
|
continue
|
|
research_msg += sep
|
|
research_msg += node.display_name
|
|
sep = ", "
|
|
var/list/points = techweb_item_point_check(src)
|
|
if (length(points))
|
|
sep = ", "
|
|
research_msg += techweb_point_display_generic(points)
|
|
|
|
if (!sep) // nothing was shown
|
|
research_msg += "None"
|
|
|
|
// Extractable materials. Only shows the names, not the amounts.
|
|
research_msg += ".<br><font color='purple'>Extractable materials:</font> "
|
|
if (length(custom_materials))
|
|
sep = ""
|
|
for(var/mat in custom_materials)
|
|
research_msg += sep
|
|
research_msg += CallMaterialName(mat)
|
|
sep = ", "
|
|
else
|
|
research_msg += "None"
|
|
research_msg += "."
|
|
. += research_msg.Join()
|
|
|
|
/obj/item/interact(mob/user)
|
|
add_fingerprint(user)
|
|
ui_interact(user)
|
|
|
|
/obj/item/ui_act(action, params)
|
|
add_fingerprint(usr)
|
|
return ..()
|
|
|
|
/obj/item/attack_hand(mob/user)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
if(!user)
|
|
return
|
|
if(anchored)
|
|
return
|
|
|
|
if(resistance_flags & ON_FIRE)
|
|
var/mob/living/carbon/C = user
|
|
var/can_handle_hot = FALSE
|
|
if(!istype(C))
|
|
can_handle_hot = TRUE
|
|
else if(C.gloves && (C.gloves.max_heat_protection_temperature > 360))
|
|
can_handle_hot = TRUE
|
|
else if(HAS_TRAIT(C, TRAIT_RESISTHEAT) || HAS_TRAIT(C, TRAIT_RESISTHEATHANDS))
|
|
can_handle_hot = TRUE
|
|
|
|
if(can_handle_hot)
|
|
extinguish()
|
|
to_chat(user, "<span class='notice'>You put out the fire on [src].</span>")
|
|
else
|
|
to_chat(user, "<span class='warning'>You burn your hand on [src]!</span>")
|
|
var/obj/item/bodypart/affecting = C.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm")
|
|
if(affecting && affecting.receive_damage( 0, 5 )) // 5 burn damage
|
|
C.update_damage_overlays()
|
|
return
|
|
|
|
if(acid_level > 20 && !ismob(loc))// so we can still remove the clothes on us that have acid.
|
|
var/mob/living/carbon/C = user
|
|
if(istype(C))
|
|
if(!C.gloves || (!(C.gloves.resistance_flags & (UNACIDABLE|ACID_PROOF))))
|
|
to_chat(user, "<span class='warning'>The acid on [src] burns your hand!</span>")
|
|
var/obj/item/bodypart/affecting = C.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm")
|
|
if(affecting && affecting.receive_damage( 0, 5 )) // 5 burn damage
|
|
C.update_damage_overlays()
|
|
|
|
if(!(interaction_flags_item & INTERACT_ITEM_ATTACK_HAND_PICKUP)) //See if we're supposed to auto pickup.
|
|
return
|
|
|
|
//Heavy gravity makes picking up things very slow.
|
|
var/grav = user.has_gravity()
|
|
if(grav > STANDARD_GRAVITY)
|
|
var/grav_power = min(3,grav - STANDARD_GRAVITY)
|
|
to_chat(user,"<span class='notice'>You start picking up [src]...</span>")
|
|
if(!do_mob(user,src,30*grav_power))
|
|
return
|
|
|
|
|
|
//If the item is in a storage item, take it out
|
|
SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, user.loc, TRUE)
|
|
|
|
if(throwing)
|
|
throwing.finalize(FALSE)
|
|
if(loc == user)
|
|
if(!allow_attack_hand_drop(user) || !user.temporarilyRemoveItemFromInventory(src))
|
|
return
|
|
|
|
pickup(user)
|
|
add_fingerprint(user)
|
|
if(!user.put_in_active_hand(src, FALSE, FALSE))
|
|
user.dropItemToGround(src)
|
|
|
|
/obj/item/proc/allow_attack_hand_drop(mob/user)
|
|
return TRUE
|
|
|
|
/obj/item/attack_paw(mob/user)
|
|
if(!user)
|
|
return
|
|
if(anchored)
|
|
return
|
|
|
|
SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, user.loc, TRUE)
|
|
|
|
if(throwing)
|
|
throwing.finalize(FALSE)
|
|
if(loc == user)
|
|
if(!user.temporarilyRemoveItemFromInventory(src))
|
|
return
|
|
|
|
pickup(user)
|
|
add_fingerprint(user)
|
|
if(!user.put_in_active_hand(src, FALSE, FALSE))
|
|
user.dropItemToGround(src)
|
|
|
|
/obj/item/attack_alien(mob/user)
|
|
var/mob/living/carbon/alien/A = user
|
|
|
|
if(!A.has_fine_manipulation)
|
|
if(src in A.contents) // To stop Aliens having items stuck in their pockets
|
|
A.dropItemToGround(src)
|
|
to_chat(user, "<span class='warning'>Your claws aren't capable of such fine manipulation!</span>")
|
|
return
|
|
attack_paw(A)
|
|
|
|
/obj/item/attack_ai(mob/user)
|
|
if(istype(src.loc, /obj/item/robot_module))
|
|
//If the item is part of a cyborg module, equip it
|
|
if(!iscyborg(user))
|
|
return
|
|
var/mob/living/silicon/robot/R = user
|
|
if(!R.low_power_mode) //can't equip modules with an empty cell.
|
|
R.activate_module(src)
|
|
R.hud_used.update_robot_modules_display()
|
|
|
|
/obj/item/proc/GetDeconstructableContents()
|
|
return GetAllContents() - src
|
|
|
|
// afterattack() and attack() prototypes moved to _onclick/item_attack.dm for consistency
|
|
|
|
/obj/item/proc/talk_into(mob/M, input, channel, spans, datum/language/language)
|
|
return ITALICS | REDUCE_RANGE
|
|
|
|
/obj/item/proc/dropped(mob/user)
|
|
for(var/X in actions)
|
|
var/datum/action/A = X
|
|
A.Remove(user)
|
|
if(item_flags & DROPDEL)
|
|
qdel(src)
|
|
item_flags &= ~IN_INVENTORY
|
|
if(SEND_SIGNAL(src, COMSIG_ITEM_DROPPED,user) & COMPONENT_DROPPED_RELOCATION)
|
|
. = ITEM_RELOCATED_BY_DROPPED
|
|
user.update_equipment_speed_mods()
|
|
|
|
// called just as an item is picked up (loc is not yet changed)
|
|
/obj/item/proc/pickup(mob/user)
|
|
SEND_SIGNAL(src, COMSIG_ITEM_PICKUP, user)
|
|
item_flags |= IN_INVENTORY
|
|
|
|
// called when "found" in pockets and storage items. Returns 1 if the search should end.
|
|
/obj/item/proc/on_found(mob/finder)
|
|
return
|
|
|
|
/obj/item/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params) //Copypaste of /atom/MouseDrop() since this requires code in a very specific spot
|
|
if(!usr || !over)
|
|
return
|
|
if(SEND_SIGNAL(src, COMSIG_MOUSEDROP_ONTO, over, usr) & COMPONENT_NO_MOUSEDROP) //Whatever is receiving will verify themselves for adjacency.
|
|
return
|
|
if(over == src)
|
|
return usr.client.Click(src, src_location, src_control, params)
|
|
var/list/directaccess = usr.DirectAccess() //This, specifically, is what requires the copypaste. If this were after the adjacency check, then it'd be impossible to use items in your inventory, among other things.
|
|
//If this were before the above checks, then trying to click on items would act a little funky and signal overrides wouldn't work.
|
|
if(SEND_SIGNAL(usr, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE) && ((usr.CanReach(src) || (src in directaccess)) && (usr.CanReach(over) || (over in directaccess))))
|
|
if(!usr.get_active_held_item())
|
|
usr.UnarmedAttack(src, TRUE)
|
|
if(usr.get_active_held_item() == src)
|
|
melee_attack_chain(usr, over)
|
|
return TRUE //returning TRUE as a "is this overridden?" flag
|
|
if(!Adjacent(usr) || !over.Adjacent(usr))
|
|
return // should stop you from dragging through windows
|
|
|
|
over.MouseDrop_T(src,usr)
|
|
return
|
|
|
|
// called after an item is placed in an equipment slot
|
|
// user is mob that equipped it
|
|
// slot uses the slot_X defines found in setup.dm
|
|
// for items that can be placed in multiple slots
|
|
// note this isn't called during the initial dressing of a player
|
|
/obj/item/proc/equipped(mob/user, slot)
|
|
. = SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot)
|
|
if(!(. & COMPONENT_NO_GRANT_ACTIONS))
|
|
for(var/X in actions)
|
|
var/datum/action/A = X
|
|
if(item_action_slot_check(slot, user, A)) //some items only give their actions buttons when in a specific slot.
|
|
A.Grant(user)
|
|
item_flags |= IN_INVENTORY
|
|
user.update_equipment_speed_mods()
|
|
|
|
//Overlays for the worn overlay so you can overlay while you overlay
|
|
//eg: ammo counters, primed grenade flashing, etc.
|
|
//"icon_file" is used automatically for inhands etc. to make sure it gets the right inhand file
|
|
/obj/item/proc/worn_overlays(isinhands = FALSE, icon_file, used_state, style_flags = NONE)
|
|
. = list()
|
|
SEND_SIGNAL(src, COMSIG_ITEM_WORN_OVERLAYS, isinhands, icon_file, used_state, style_flags, .)
|
|
|
|
//sometimes we only want to grant the item's action if it's equipped in a specific slot.
|
|
/obj/item/proc/item_action_slot_check(slot, mob/user, datum/action/A)
|
|
if(slot == SLOT_IN_BACKPACK || slot == SLOT_LEGCUFFED) //these aren't true slots, so avoid granting actions there
|
|
return FALSE
|
|
return TRUE
|
|
|
|
//the mob M is attempting to equip this item into the slot passed through as 'slot'. Return 1 if it can do this and 0 if it can't.
|
|
//if this is being done by a mob other than M, it will include the mob equipper, who is trying to equip the item to mob M. equipper will be null otherwise.
|
|
//If you are making custom procs but would like to retain partial or complete functionality of this one, include a 'return ..()' to where you want this to happen.
|
|
//Set disable_warning to TRUE if you wish it to not give you outputs.
|
|
/obj/item/proc/mob_can_equip(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
|
|
if(!M)
|
|
return FALSE
|
|
|
|
return M.can_equip(src, slot, disable_warning, bypass_equip_delay_self)
|
|
|
|
/obj/item/verb/verb_pickup()
|
|
set src in oview(1)
|
|
set category = "Object"
|
|
set name = "Pick up"
|
|
|
|
if(usr.incapacitated() || !Adjacent(usr) || usr.lying)
|
|
return
|
|
|
|
if(usr.get_active_held_item() == null) // Let me know if this has any problems -Yota
|
|
usr.UnarmedAttack(src)
|
|
|
|
//This proc is executed when someone clicks the on-screen UI button.
|
|
//The default action is attack_self().
|
|
//Checks before we get to here are: mob is alive, mob is not restrained, stunned, asleep, resting, laying, item is on the mob.
|
|
/obj/item/proc/ui_action_click(mob/user, actiontype)
|
|
attack_self(user)
|
|
|
|
/obj/item/proc/eyestab(mob/living/carbon/M, mob/living/carbon/user)
|
|
if(HAS_TRAIT(user, TRAIT_PACIFISM))
|
|
to_chat(user, "<span class='warning'>You don't want to harm [M]!</span>")
|
|
return
|
|
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
|
|
M = user
|
|
var/is_human_victim = 0
|
|
var/obj/item/bodypart/affecting = M.get_bodypart(BODY_ZONE_HEAD)
|
|
if(ishuman(M))
|
|
if(!affecting) //no head!
|
|
return
|
|
is_human_victim = 1
|
|
var/mob/living/carbon/human/H = M
|
|
if((H.head && H.head.flags_cover & HEADCOVERSEYES) || \
|
|
(H.wear_mask && H.wear_mask.flags_cover & MASKCOVERSEYES) || \
|
|
(H.glasses && H.glasses.flags_cover & GLASSESCOVERSEYES))
|
|
// you can't stab someone in the eyes wearing a mask!
|
|
to_chat(user, "<span class='danger'>You're going to need to remove that mask/helmet/glasses first!</span>")
|
|
return
|
|
|
|
if(ismonkey(M))
|
|
var/mob/living/carbon/monkey/Mo = M
|
|
if(Mo.wear_mask && Mo.wear_mask.flags_cover & MASKCOVERSEYES)
|
|
// you can't stab someone in the eyes wearing a mask!
|
|
to_chat(user, "<span class='danger'>You're going to need to remove that mask/helmet/glasses first!</span>")
|
|
return
|
|
|
|
if(isalien(M))//Aliens don't have eyes./N slimes also don't have eyes!
|
|
to_chat(user, "<span class='warning'>You cannot locate any eyes on this creature!</span>")
|
|
return
|
|
|
|
if(isbrain(M))
|
|
to_chat(user, "<span class='danger'>You cannot locate any organic eyes on this brain!</span>")
|
|
return
|
|
|
|
if(IS_STAMCRIT(user))//CIT CHANGE - makes eyestabbing impossible if you're in stamina softcrit
|
|
to_chat(user, "<span class='danger'>You're too exhausted for that.</span>")//CIT CHANGE - ditto
|
|
return //CIT CHANGE - ditto
|
|
|
|
src.add_fingerprint(user)
|
|
|
|
playsound(loc, src.hitsound, 30, 1, -1)
|
|
|
|
user.do_attack_animation(M)
|
|
|
|
user.adjustStaminaLossBuffered(10)//CIT CHANGE - makes eyestabbing cost stamina
|
|
|
|
if(M != user)
|
|
M.visible_message("<span class='danger'>[user] has stabbed [M] in the eye with [src]!</span>", \
|
|
"<span class='userdanger'>[user] stabs you in the eye with [src]!</span>")
|
|
else
|
|
user.visible_message( \
|
|
"<span class='danger'>[user] has stabbed [user.p_them()]self in the eyes with [src]!</span>", \
|
|
"<span class='userdanger'>You stab yourself in the eyes with [src]!</span>" \
|
|
)
|
|
if(is_human_victim)
|
|
var/mob/living/carbon/human/U = M
|
|
U.apply_damage(7, BRUTE, affecting)
|
|
|
|
else
|
|
M.take_bodypart_damage(7)
|
|
|
|
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "eye_stab", /datum/mood_event/eye_stab)
|
|
|
|
log_combat(user, M, "attacked", "[src.name]", "(INTENT: [uppertext(user.a_intent)])")
|
|
|
|
var/obj/item/organ/eyes/eyes = M.getorganslot(ORGAN_SLOT_EYES)
|
|
if (!eyes)
|
|
return
|
|
M.adjust_blurriness(3)
|
|
eyes.applyOrganDamage(rand(2,4))
|
|
if(eyes.damage >= 10)
|
|
M.adjust_blurriness(15)
|
|
if(M.stat != DEAD)
|
|
to_chat(M, "<span class='danger'>Your eyes start to bleed profusely!</span>")
|
|
if(!(HAS_TRAIT(M, TRAIT_BLIND) || HAS_TRAIT(M, TRAIT_NEARSIGHT)))
|
|
to_chat(M, "<span class='danger'>You become nearsighted!</span>")
|
|
M.become_nearsighted(EYE_DAMAGE)
|
|
if(prob(50))
|
|
if(M.stat != DEAD)
|
|
if(M.drop_all_held_items())
|
|
to_chat(M, "<span class='danger'>You drop what you're holding and clutch at your eyes!</span>")
|
|
M.adjust_blurriness(10)
|
|
M.Unconscious(20)
|
|
M.DefaultCombatKnockdown(40)
|
|
if (prob(eyes.damage - 10 + 1))
|
|
M.become_blind(EYE_DAMAGE)
|
|
to_chat(M, "<span class='danger'>You go blind!</span>")
|
|
|
|
/obj/item/clean_blood()
|
|
. = ..()
|
|
if(.)
|
|
if(blood_splatter_icon)
|
|
cut_overlay(blood_splatter_icon)
|
|
|
|
/obj/item/clothing/gloves/clean_blood()
|
|
. = ..()
|
|
if(.)
|
|
transfer_blood = 0
|
|
|
|
/obj/item/singularity_pull(S, current_size)
|
|
..()
|
|
if(current_size >= STAGE_FOUR)
|
|
throw_at(S,14,3, spin=0)
|
|
else
|
|
return
|
|
|
|
/obj/item/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
|
|
if(hit_atom && !QDELETED(hit_atom))
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum)
|
|
if(get_temperature() && isliving(hit_atom))
|
|
var/mob/living/L = hit_atom
|
|
L.IgniteMob()
|
|
var/itempush = 1
|
|
if(w_class < 4)
|
|
itempush = 0 //too light to push anything
|
|
return hit_atom.hitby(src, 0, itempush, throwingdatum=throwingdatum)
|
|
|
|
/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, messy_throw = TRUE)
|
|
thrownby = thrower
|
|
callback = CALLBACK(src, .proc/after_throw, callback, (spin && messy_throw)) //replace their callback with our own
|
|
. = ..(target, range, speed, thrower, spin, diagonals_first, callback, force)
|
|
|
|
/obj/item/proc/after_throw(datum/callback/callback, messy_throw)
|
|
if (callback) //call the original callback
|
|
. = callback.Invoke()
|
|
throw_speed = initial(throw_speed) //explosions change this.
|
|
item_flags &= ~IN_INVENTORY
|
|
if(messy_throw)
|
|
var/matrix/M = matrix(transform)
|
|
M.Turn(rand(-170, 170))
|
|
transform = M
|
|
pixel_x = rand(-8, 8)
|
|
pixel_y = rand(-8, 8)
|
|
|
|
/obj/item/proc/remove_item_from_storage(atom/newLoc) //please use this if you're going to snowflake an item out of a obj/item/storage
|
|
if(!newLoc)
|
|
return FALSE
|
|
if(SEND_SIGNAL(loc, COMSIG_CONTAINS_STORAGE))
|
|
return SEND_SIGNAL(loc, COMSIG_TRY_STORAGE_TAKE, src, newLoc, TRUE)
|
|
return FALSE
|
|
|
|
/obj/item/proc/get_belt_overlay() //Returns the icon used for overlaying the object on a belt
|
|
return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', icon_state)
|
|
|
|
/obj/item/proc/get_worn_belt_overlay(icon_file)
|
|
return
|
|
|
|
/obj/item/proc/update_slot_icon()
|
|
if(!ismob(loc))
|
|
return
|
|
var/mob/owner = loc
|
|
var/flags = slot_flags
|
|
if(flags & ITEM_SLOT_OCLOTHING)
|
|
owner.update_inv_wear_suit()
|
|
if(flags & ITEM_SLOT_ICLOTHING)
|
|
owner.update_inv_w_uniform()
|
|
if(flags & ITEM_SLOT_GLOVES)
|
|
owner.update_inv_gloves()
|
|
if(flags & ITEM_SLOT_EYES)
|
|
owner.update_inv_glasses()
|
|
if(flags & ITEM_SLOT_EARS)
|
|
owner.update_inv_ears()
|
|
if(flags & ITEM_SLOT_MASK)
|
|
owner.update_inv_wear_mask()
|
|
if(flags & ITEM_SLOT_HEAD)
|
|
owner.update_inv_head()
|
|
if(flags & ITEM_SLOT_FEET)
|
|
owner.update_inv_shoes()
|
|
if(flags & ITEM_SLOT_ID)
|
|
owner.update_inv_wear_id()
|
|
if(flags & ITEM_SLOT_BELT)
|
|
owner.update_inv_belt()
|
|
if(flags & ITEM_SLOT_BACK)
|
|
owner.update_inv_back()
|
|
if(flags & ITEM_SLOT_NECK)
|
|
owner.update_inv_neck()
|
|
|
|
/obj/item/proc/get_temperature()
|
|
return heat
|
|
|
|
/obj/item/proc/get_sharpness()
|
|
return sharpness
|
|
|
|
/obj/item/proc/get_dismemberment_chance(obj/item/bodypart/affecting)
|
|
if(affecting.can_dismember(src))
|
|
if((sharpness || damtype == BURN) && w_class >= WEIGHT_CLASS_NORMAL && force >= 10)
|
|
. = force * (affecting.get_damage() / affecting.max_damage)
|
|
|
|
/obj/item/proc/get_dismember_sound()
|
|
if(damtype == BURN)
|
|
. = 'sound/weapons/sear.ogg'
|
|
else
|
|
. = pick('sound/misc/desceration-01.ogg', 'sound/misc/desceration-02.ogg', 'sound/misc/desceration-03.ogg')
|
|
|
|
/obj/item/proc/open_flame(flame_heat=700)
|
|
var/turf/location = loc
|
|
if(ismob(location))
|
|
var/mob/M = location
|
|
var/success = FALSE
|
|
if(src == M.get_item_by_slot(SLOT_WEAR_MASK))
|
|
success = TRUE
|
|
if(success)
|
|
location = get_turf(M)
|
|
if(isturf(location))
|
|
location.hotspot_expose(flame_heat, 1)
|
|
|
|
/obj/item/proc/ignition_effect(atom/A, mob/user)
|
|
if(get_temperature())
|
|
. = "<span class='notice'>[user] lights [A] with [src].</span>"
|
|
else
|
|
. = ""
|
|
|
|
/obj/item/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
|
|
return
|
|
|
|
/obj/item/attack_hulk(mob/living/carbon/human/user)
|
|
return 0
|
|
|
|
/obj/item/attack_animal(mob/living/simple_animal/M)
|
|
if (obj_flags & CAN_BE_HIT)
|
|
return ..()
|
|
return 0
|
|
|
|
/obj/item/mech_melee_attack(obj/mecha/M)
|
|
return 0
|
|
|
|
/obj/item/burn()
|
|
if(!QDELETED(src))
|
|
var/turf/T = get_turf(src)
|
|
var/ash_type = /obj/effect/decal/cleanable/ash
|
|
if(w_class == WEIGHT_CLASS_HUGE || w_class == WEIGHT_CLASS_GIGANTIC)
|
|
ash_type = /obj/effect/decal/cleanable/ash/large
|
|
var/obj/effect/decal/cleanable/ash/A = new ash_type(T)
|
|
A.desc += "\nLooks like this used to be \an [name] some time ago."
|
|
..()
|
|
|
|
/obj/item/acid_melt()
|
|
if(!QDELETED(src))
|
|
var/turf/T = get_turf(src)
|
|
var/obj/effect/decal/cleanable/molten_object/MO = new(T)
|
|
MO.pixel_x = rand(-16,16)
|
|
MO.pixel_y = rand(-16,16)
|
|
MO.desc = "Looks like this was \an [src] some time ago."
|
|
..()
|
|
|
|
/obj/item/proc/microwave_act(obj/machinery/microwave/M)
|
|
if(istype(M) && M.dirty < 100)
|
|
M.dirty++
|
|
|
|
/obj/item/proc/on_mob_death(mob/living/L, gibbed)
|
|
|
|
/obj/item/proc/grind_requirements(obj/machinery/reagentgrinder/R) //Used to check for extra requirements for grinding an object
|
|
return TRUE
|
|
|
|
//Called BEFORE the object is ground up - use this to change grind results based on conditions
|
|
//Use "return -1" to prevent the grinding from occurring
|
|
/obj/item/proc/on_grind()
|
|
|
|
/obj/item/proc/on_juice()
|
|
|
|
/obj/item/proc/set_force_string()
|
|
switch(force)
|
|
if(0 to 4)
|
|
force_string = "very low"
|
|
if(4 to 7)
|
|
force_string = "low"
|
|
if(7 to 10)
|
|
force_string = "medium"
|
|
if(10 to 11)
|
|
force_string = "high"
|
|
if(11 to 20) //12 is the force of a toolbox
|
|
force_string = "robust"
|
|
if(20 to 25)
|
|
force_string = "very robust"
|
|
else
|
|
force_string = "exceptionally robust"
|
|
last_force_string_check = force
|
|
|
|
/obj/item/proc/openTip(location, control, params, user)
|
|
if(last_force_string_check != force && !(item_flags & FORCE_STRING_OVERRIDE))
|
|
set_force_string()
|
|
if(!(item_flags & FORCE_STRING_OVERRIDE))
|
|
openToolTip(user,src,params,title = name,content = "[desc]<br>[force ? "<b>Force:</b> [force_string]" : ""]",theme = "")
|
|
else
|
|
openToolTip(user,src,params,title = name,content = "[desc]<br><b>Force:</b> [force_string]",theme = "")
|
|
|
|
/obj/item/MouseEntered(location, control, params)
|
|
if((item_flags & IN_INVENTORY) && usr.client.prefs.enable_tips && !QDELETED(src))
|
|
var/timedelay = usr.client.prefs.tip_delay/100
|
|
var/user = usr
|
|
tip_timer = addtimer(CALLBACK(src, .proc/openTip, location, control, params, user), timedelay, TIMER_STOPPABLE)//timer takes delay in deciseconds, but the pref is in milliseconds. dividing by 100 converts it.
|
|
|
|
/obj/item/MouseExited(location,control,params)
|
|
SEND_SIGNAL(src, COMSIG_ITEM_MOUSE_EXIT, location, control, params)
|
|
deltimer(tip_timer)//delete any in-progress timer if the mouse is moved off the item before it finishes
|
|
closeToolTip(usr)
|
|
|
|
/obj/item/MouseEntered(location,control,params)
|
|
SEND_SIGNAL(src, COMSIG_ITEM_MOUSE_ENTER, location, control, params)
|
|
|
|
// Called when a mob tries to use the item as a tool.
|
|
// Handles most checks.
|
|
/obj/item/proc/use_tool(atom/target, mob/living/user, delay, amount=0, volume=0, datum/callback/extra_checks, skill_gain_mult = 1, max_level = INFINITY)
|
|
// No delay means there is no start message, and no reason to call tool_start_check before use_tool.
|
|
// Run the start check here so we wouldn't have to call it manually.
|
|
if(!delay && !tool_start_check(user, amount))
|
|
return
|
|
|
|
delay *= toolspeed
|
|
|
|
// Play tool sound at the beginning of tool usage.
|
|
play_tool_sound(target, volume)
|
|
|
|
if(delay)
|
|
if(user.mind && used_skills)
|
|
delay = user.mind.item_action_skills_mod(src, delay, skill_difficulty, SKILL_USE_TOOL, null, FALSE)
|
|
|
|
// Create a callback with checks that would be called every tick by do_after.
|
|
var/datum/callback/tool_check = CALLBACK(src, .proc/tool_check_callback, user, amount, extra_checks)
|
|
|
|
if(ismob(target))
|
|
if(!do_mob(user, target, delay, extra_checks=tool_check))
|
|
return
|
|
|
|
else
|
|
if(!do_after(user, delay, target=target, extra_checks=tool_check))
|
|
return
|
|
else
|
|
// Invoke the extra checks once, just in case.
|
|
if(extra_checks && !extra_checks.Invoke())
|
|
return
|
|
|
|
// Use tool's fuel, stack sheets or charges if amount is set.
|
|
if(amount && !use(amount))
|
|
return
|
|
|
|
// Play tool sound at the end of tool usage,
|
|
// but only if the delay between the beginning and the end is not too small
|
|
if(delay >= MIN_TOOL_SOUND_DELAY)
|
|
play_tool_sound(target, volume)
|
|
|
|
|
|
if(user.mind && used_skills && skill_gain_mult)
|
|
var/gain = skill_gain + delay/SKILL_GAIN_DELAY_DIVISOR
|
|
for(var/skill in used_skills)
|
|
if(!(SKILL_TRAINING_TOOL in used_skills[skill]))
|
|
continue
|
|
user.mind.auto_gain_experience(skill, gain*skill_gain_mult, GET_STANDARD_LVL(max_level))
|
|
|
|
return TRUE
|
|
|
|
// Called before use_tool if there is a delay, or by use_tool if there isn't.
|
|
// Only ever used by welding tools and stacks, so it's not added on any other use_tool checks.
|
|
/obj/item/proc/tool_start_check(mob/living/user, amount=0)
|
|
return tool_use_check(user, amount)
|
|
|
|
// A check called by tool_start_check once, and by use_tool on every tick of delay.
|
|
/obj/item/proc/tool_use_check(mob/living/user, amount)
|
|
return !amount
|
|
|
|
// Generic use proc. Depending on the item, it uses up fuel, charges, sheets, etc.
|
|
// Returns TRUE on success, FALSE on failure.
|
|
/obj/item/proc/use(used)
|
|
return !used
|
|
|
|
// Plays item's usesound, if any.
|
|
/obj/item/proc/play_tool_sound(atom/target, volume=50)
|
|
if(target && usesound && volume)
|
|
var/played_sound = usesound
|
|
|
|
if(islist(usesound))
|
|
played_sound = pick(usesound)
|
|
|
|
playsound(target, played_sound, volume, 1)
|
|
|
|
// Used in a callback that is passed by use_tool into do_after call. Do not override, do not call manually.
|
|
/obj/item/proc/tool_check_callback(mob/living/user, amount, datum/callback/extra_checks)
|
|
return tool_use_check(user, amount) && (!extra_checks || extra_checks.Invoke())
|
|
|
|
// Returns a numeric value for sorting items used as parts in machines, so they can be replaced by the rped
|
|
/obj/item/proc/get_part_rating()
|
|
return 0
|
|
|
|
/obj/item/doMove(atom/destination)
|
|
if (ismob(loc))
|
|
var/mob/M = loc
|
|
var/hand_index = M.get_held_index_of_item(src)
|
|
if(hand_index)
|
|
M.held_items[hand_index] = null
|
|
M.update_inv_hands()
|
|
if(M.client)
|
|
M.client.screen -= src
|
|
layer = initial(layer)
|
|
plane = initial(plane)
|
|
appearance_flags &= ~NO_CLIENT_COLOR
|
|
dropped(M)
|
|
return ..()
|
|
|
|
/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, var/datum/callback/callback)
|
|
if (HAS_TRAIT(src, TRAIT_NODROP))
|
|
return
|
|
return ..()
|
|
|
|
/// Get an item's volume that it uses when being stored.
|
|
/obj/item/proc/get_w_volume()
|
|
// if w_volume is 0 you fucked up anyways lol
|
|
return w_volume || AUTO_SCALE_VOLUME(w_class)
|
|
|
|
/obj/item/proc/embedded(mob/living/carbon/human/embedded_mob)
|
|
return
|
|
|
|
/obj/item/proc/unembedded()
|
|
return
|
|
|
|
/**
|
|
* Sets our slowdown and updates equipment slowdown of any mob we're equipped on.
|
|
*/
|
|
/obj/item/proc/set_slowdown(new_slowdown)
|
|
slowdown = new_slowdown
|
|
if(CHECK_BITFIELD(item_flags, IN_INVENTORY))
|
|
var/mob/living/L = loc
|
|
if(istype(L))
|
|
L.update_equipment_speed_mods()
|
|
|
|
/obj/item/vv_edit_var(var_name, var_value)
|
|
. = ..()
|
|
if(var_name == NAMEOF(src, slowdown))
|
|
set_slowdown(var_value) //don't care if it's a duplicate edit as slowdown'll be set, do it anyways to force normal behavior.
|
|
|
|
/**
|
|
* Does the current embedding var meet the criteria for being harmless? Namely, does it have a pain multiplier and jostle pain mult of 0? If so, return true.
|
|
*
|
|
*/
|
|
/obj/item/proc/is_embed_harmless()
|
|
if(embedding)
|
|
return (!embedding["pain_mult"] && !embedding["jostle_pain_mult"]) |