Files
Bubberstation/code/game/objects/items.dm
Ryll Ryll 5c174800fb Grenades and projectiles can have shrapnel and embed, all carbons can suffer embeds, some bullets can ricochet, sizable refactor of embedding (#49634)
About The Pull Request

It annoyed me that we have a perfectly good frag grenade item, and a perfectly good shrapnel component, but no crossover episode between the two. This remedies that, and does a lot, lot more.

dreamseeker_2020-03-30_05-01-13.png

dreamseeker_2020-03-30_05-01-26.png

Big points:

    Adds new component: pellet_cloud, which can be used by ammo casings, guns, and landmines to spray shrapnel and display aggregate hit messages ("You're hit by 6 buckshot pellets!" vs "You're hit by the buckshot pellet in the X" x6). All gun ammo that shoot multiple pellets now use this component on firing.
    Adds stingbangs, premium less-lethal grenades that shoot off lots of stinger pellets, to cargo. Frag grenades are also reworked to have smaller booms, but shoot off lots of shrapnel shards. You can jump on top of these grenades to absorb a portion of the shrapnel to save those around you! There's an achievement for dying this way, called "Look Out, Sir!"
    Projectiles can now embed items/shrapnel. Adds .38 DumDum ammo to cargo that does less damage and has negative armor pen, but can embed in people. This is the only ammo that currently embeds.
    Bullets can now ricochet off walls, structures, and machinery (harder surfaces are more likely to ricochet). Only standard .38 and Match Grade .38/.357/L6 ammo can ricochet, with Match Grade being much better at ricocheting. You can buy Match Grade .38 from cargo and Match Grade L6 ammo from the nuke uplink, while Match .357 is admin only.
    Armor now protects you from harmful embeds, taking the better of the bullet/bomb armor on the affected limb. Armor penetration can modify this of course, and many blunt embeds like stingbangs and DumDum bullets are significantly worse if you have even 1 armor.

Other misc fixes/changes

    Refactored the embed element a bunch and fixed it creating new elements for every instance rather than expected bespoke behavior. There are new /obj/item helpers for modifying and adding embedding.
    Fixes #49989: Spears can no longer embed in turfs cause their sprite is annoying to me, it's generally harder for most things to embed in turfs
    Fixes #49741: New carbon helpers for removing embedded objects
    Fixes #46416: Handles embedded objects getting qdel'd or moved while embedded
    Renamed the old shrapnel component for RPG loot to MIRV to avoid confusion
    Repathed frag grenades from under minibombs to under base grenades, and added explosion vars to base grenades

Why It's Good For The Game

Fixes a bunch of janky design with embeds, adds lots of new avenues for projectile and grenade variety, ricochets and collateral damage are fun!
Changelog

🆑 Ryll/Shaps
add: Adds stingbangs to cargo (and one in the sec vendor premium), premium less-lethal grenades that shoot off a giant swarm of stingball pellets to help incapacitate swarms of people in tight quarters. You can jump on top of a live one to be a hero and absorb a bunch of shrapnel, same with frag grenades. There's even an achievement for dying to a grenade you jumped on!
add: Projectiles can now embed in people! Or at least grenade shrapnel and the new .38 DumDum ammo, now available in cargo, can. DumDum rounds excel against unarmored targets, but are pricey and do poorly against armored targets.
add: Bullets can now ricochet! Or at least, standard .38 and the new .38/L6 Match Grade ammo can. Match Grade ammo is finely tuned to ricochet easier and seek targets off bounces better, and can be purchased from cargo (for the .38) or nuke ops uplink (for the L6), but standard .38 ammo has a chance to ricochet as well.
tweak: Frag grenades now have smaller explosions but shoot off a bunch of devastating shrapnel, excellent for soft targets!
tweak: Shotguns and other multi-pellet guns now print aggregate messages, so you'll get one "You've been hit by 6 buckshot pellets!" rather than 6 "You've been hit by the buckshot pellet in the X!" messages. Bye bye lag!
balance: Armor can now protect against embedding weapons, taking the best of either the bullet or bomb armor for the limb in question away from the embed chance. Some weapons are better at piercing armor than others!
/🆑
2020-04-03 16:58:38 +13:00

997 lines
37 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 ssize.
var/alternate_worn_layer
///Dimensions of the icon file used when this item is worn, eg: hats.dmi (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
///Dimensions of the icon file used when this item is worn, eg: hats.dmi (32x32 sprite, 64x64 sprite, etc.). Allows inhands/worn sprites to be of any size, but still centered on a mob properly
var/worn_y_dimension = 32
///Same as for [worn_x_dimension][/obj/item/var/worn_x_dimension] but for inhands, uses the lefthand_ and righthand_ file vars
var/inhand_x_dimension = 32
///Same as for [worn_y_dimension][/obj/item/var/worn_y_dimension] but for inhands, uses the lefthand_ and righthand_ file vars
var/inhand_y_dimension = 32
max_integrity = 200
obj_flags = NONE
///Item flags for the item
var/item_flags = NONE
///Sound played when you hit something with the item
var/hitsound
///Played when the item is used, for example tools
var/usesound
///Used when yate into a mob
var/mob_throw_hit_sound
///Sound used when equipping the item into a valid slot
var/equip_sound
///Sound uses when picking the item up (into your hands)
var/pickup_sound
///Sound uses when dropping the item, or when its thrown.
var/drop_sound
///How large is the object, used for stuff like whether it can fit in backpacks or not
var/w_class = WEIGHT_CLASS_NORMAL
///This is used to determine on which slots an item can fit.
var/slot_flags = 0
pass_flags = PASSTABLE
pressure_resistance = 4
var/obj/item/master = null
///flags which determine which body parts are protected from heat. [See here][HEAD]
var/heat_protection = 0
///flags which determine which body parts are protected from cold. [See here][HEAD]
var/cold_protection = 0
///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/max_heat_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/min_cold_protection_temperature
///list of /datum/action's that this item has.
var/list/actions
///list of paths of action datums to give to the item on New().
var/list/actions_types
//Since any item can now be a piece of clothing, this has to be put here so all items share it.
///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/flags_inv
///you can see someone's mask through their transparent visor, but you can't reach it
var/transparent_protection = NONE
///flags for what should be done when you click on the item, default is picking it up
var/interaction_flags_item = INTERACT_ITEM_ATTACK_HAND_PICKUP
///What body parts are covered by the clothing when you wear it
var/body_parts_covered = 0
///Literally does nothing right now
var/gas_transfer_coefficient = 1
/// How likely a disease or chemical is to get through a piece of clothing
var/permeability_coefficient = 1
/// for electrical admittance/conductance (electrocution checks and shit)
var/siemens_coefficient = 1
/// How much clothing is slowing you down. Negative values speeds you up
var/slowdown = 0
///percentage of armour effectiveness to remove
var/armour_penetration = 0
///What objects the suit storage can store
var/list/allowed = null
///In deciseconds, how long an item takes to equip; counts only for normal clothing slots, not pockets etc.
var/equip_delay_self = 0
///In deciseconds, how long an item takes to put on another person
var/equip_delay_other = 20
///In deciseconds, how long an item takes to remove from another person
var/strip_delay = 40
///How long it takes to resist out of the item (cuffs and such)
var/breakouttime = 0
///Used in [atom/proc/attackby] to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]"
var/list/attack_verb
///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/list/species_exception = null
///Who threw the item
var/mob/thrownby = null
///the icon to indicate this object is being dragged
mouse_drag_pointer = MOUSE_ACTIVE_POINTER
///Does it embed and if yes, what kind of embed
var/list/embedding = NONE
///for flags such as [GLASSESCOVERSEYES]
var/flags_cover = 0
var/heat = 0
///All items with sharpness of IS_SHARP or higher will automatically get the butchering component.
var/sharpness = IS_BLUNT
///How a tool acts when you use it on something, such as wirecutters cutting wires while multitools measure power
var/tool_behaviour = NONE
///How fast does the tool work
var/toolspeed = 1
var/block_chance = 0
var/hit_reaction_chance = 0 //If you want to have something unrelated to blocking/armour piercing etc. Maybe not needed, but trying to think ahead/allow more freedom
///In tiles, how far this weapon can reach; 1 for adjacent, which is default
var/reach = 1
///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. For default list, see [/mob/proc/equip_to_appropriate_slot]
var/list/slot_equipment_priority = null
///Reference to the datum that determines whether dogs can wear the item: 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
///string form of an item's force. Edit this var only to set a custom force string
var/force_string
var/last_force_string_check = 0
var/tip_timer
///Determines who can shoot this
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 var: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/grind_results
//Grinder var:A reagent list containing blah blah... but when JUICED in a grinder!
var/list/juice_results
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/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)
/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(!LAZYLEN(embedding))
if(GLOB.embedpocalypse)
embedding = EMBED_POINTY
name = "pointy [name]"
else if(GLOB.stickpocalypse)
embedding = EMBED_HARMLESS
name = "sticky [name]"
updateEmbedding()
if(GLOB.rpg_loot_items)
AddComponent(/datum/component/fantasy)
if(sharpness) //give sharp objects butchering functionality, for consistency
AddComponent(/datum/component/butchering, 80 * toolspeed)
/**Makes cool stuff happen when you suicide with an item
*
*Outputs a creative message and then return the damagetype done
* Arguments:
* * user: The mob that is suiciding
*/
/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)
if(!isturf(loc) || usr.stat || usr.restrained())
return
if(isliving(usr))
var/mob/living/L = usr
if(!(L.mobility_flags & MOBILITY_PICKUP))
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> ")
///Separator between the items on the list
var/sep = ""
///Nodes that can be boosted
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, list/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(QDELETED(src)) //moving it out of the storage to the floor destroyed it.
return
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/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, args)
if(prob(final_block_chance))
owner.visible_message("<span class='danger'>[owner] blocks [attack_text] with [src]!</span>")
return 1
return 0
/obj/item/proc/talk_into(mob/M, input, channel, spans, datum/language/language)
return ITALICS | REDUCE_RANGE
/obj/item/proc/dropped(mob/user, silent = FALSE)
SHOULD_CALL_PARENT(1)
for(var/X in actions)
var/datum/action/A = X
A.Remove(user)
if(item_flags & DROPDEL)
qdel(src)
item_flags &= ~IN_INVENTORY
SEND_SIGNAL(src, COMSIG_ITEM_DROPPED,user)
if(!silent)
playsound(src, drop_sound, DROP_SOUND_VOLUME, ignore_walls = FALSE)
user?.update_equipment_speed_mods()
/// called just as an item is picked up (loc is not yet changed)
/obj/item/proc/pickup(mob/user)
SHOULD_CALL_PARENT(1)
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
/**
*called after an item is placed in an equipment slot
* Arguments:
* * 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
* * Initial is used to indicate whether or not this is the initial equipment (job datums etc) or just a player doing it
*/
/obj/item/proc/equipped(mob/user, slot, initial = FALSE)
SHOULD_CALL_PARENT(1)
SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot)
for(var/X in actions)
var/datum/action/A = X
if(item_action_slot_check(slot, user)) //some items only give their actions buttons when in a specific slot.
A.Grant(user)
item_flags |= IN_INVENTORY
if(!initial)
if(equip_sound && (slot_flags & slot))
playsound(src, equip_sound, EQUIP_SOUND_VOLUME, TRUE, ignore_walls = FALSE)
else if(slot == ITEM_SLOT_HANDS)
playsound(src, pickup_sound, PICKUP_SOUND_VOLUME, ignore_walls = FALSE)
user.update_equipment_speed_mods()
///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)
if(slot == ITEM_SLOT_BACKPACK || slot == ITEM_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.
* Arguments:
* * disable_warning to TRUE if you wish it to not give you text outputs.
* * slot is the slot we are trying to equip to
* * equipper is the mob trying to equip the item
* * bypass_equip_delay_self for whether we want to bypass the equip delay
*/
/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))
return
if(isliving(usr))
var/mob/living/L = usr
if(!(L.mobility_flags & MOBILITY_PICKUP))
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)
///This proc determines if and at what an object will reflect energy projectiles if it's in l_hand,r_hand or wear_suit
/obj/item/proc/IsReflect(var/def_zone)
return FALSE
/obj/item/proc/eyestab(mob/living/carbon/M, mob/living/carbon/user)
var/is_human_victim
var/obj/item/bodypart/affecting = M.get_bodypart(BODY_ZONE_HEAD)
if(ishuman(M))
if(!affecting) //no head!
return
is_human_victim = TRUE
if(M.is_eyes_covered())
// you can't stab someone in the eyes wearing a mask!
to_chat(user, "<span class='warning'>You're going to need to remove [M.p_their()] eye protection 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='warning'>You cannot locate any organic eyes on this brain!</span>")
return
src.add_fingerprint(user)
playsound(loc, src.hitsound, 30, TRUE, -1)
user.do_attack_animation(M)
if(M != user)
M.visible_message("<span class='danger'>[user] stabs [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] stabs [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(!(M.is_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.Paralyze(40)
if (prob(eyes.damage - 10 + 1))
M.become_blind(EYE_DAMAGE)
to_chat(M, "<span class='danger'>You go blind!</span>")
/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
if(istype(hit_atom, /mob/living)) //Living mobs handle hit sounds differently.
var/volume = get_volume_by_throwforce_and_or_w_class()
if (throwforce > 0)
if (mob_throw_hit_sound)
playsound(hit_atom, mob_throw_hit_sound, volume, TRUE, -1)
else if(hitsound)
playsound(hit_atom, hitsound, volume, TRUE, -1)
else
playsound(hit_atom, 'sound/weapons/genhit.ogg',volume, TRUE, -1)
else
playsound(hit_atom, 'sound/weapons/throwtap.ogg', 1, volume, -1)
else
playsound(src, drop_sound, YEET_SOUND_VOLUME, ignore_walls = FALSE)
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, gentle = FALSE)
thrownby = thrower
callback = CALLBACK(src, .proc/after_throw, callback) //replace their callback with our own
. = ..(target, range, speed, thrower, spin, diagonals_first, callback, force, gentle)
/obj/item/proc/after_throw(datum/callback/callback)
if (callback) //call the original callback
. = callback.Invoke()
item_flags &= ~IN_INVENTORY
if(!pixel_y && !pixel_x)
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/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()
///Returns the temperature of src. If you want to know if an item is hot use this proc.
/obj/item/proc/get_temperature()
return heat
///Returns the sharpness of src. If you want to get the sharpness of an item use this.
/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
. = "desceration"
/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(ITEM_SLOT_MASK))
success = TRUE
if(success)
location = get_turf(M)
if(isturf(location))
location.hotspot_expose(flame_heat, 5)
/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 FALSE
/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)
SEND_SIGNAL(src, COMSIG_ITEM_MICROWAVE_ACT, 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 || item_flags & IN_STORAGE) && 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()
deltimer(tip_timer)//delete any in-progress timer if the mouse is moved off the item before it finishes
closeToolTip(usr)
/// 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)
// 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
var/skill_modifier = 1
if(tool_behaviour == TOOL_MINING && ishuman(user))
var/mob/living/carbon/human/H = user
skill_modifier = H.mind.get_skill_modifier(/datum/skill/mining, SKILL_SPEED_MODIFIER)
if(H.mind.get_skill_level(/datum/skill/mining) >= SKILL_LEVEL_JOURNEYMAN && prob(H.mind.get_skill_modifier(/datum/skill/mining, SKILL_PROBS_MODIFIER))) // we check if the skill level is greater than Journeyman and then we check for the probality for that specific level.
mineral_scan_pulse(get_turf(H), SKILL_LEVEL_JOURNEYMAN - 2) //SKILL_LEVEL_JOURNEYMAN = 3 So to get range of 1+ we have to subtract 2 from it,.
delay *= toolspeed * skill_modifier
// Play tool sound at the beginning of tool usage.
play_tool_sound(target, volume)
if(delay)
// 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)
return TRUE
/// Called before [obj/item/proc/use_tool] if there is a delay, or by [obj/item/proc/use_tool] if there isn't. Only ever used by welding tools and stacks, so it's not added on any other [obj/item/proc/use_tool] checks.
/obj/item/proc/tool_start_check(mob/living/user, amount=0)
. = tool_use_check(user, amount)
if(.)
SEND_SIGNAL(src, COMSIG_TOOL_START_USE, user)
/// A check called by [/obj/item/proc/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, TRUE)
/// 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)
SHOULD_NOT_OVERRIDE(TRUE)
. = tool_use_check(user, amount) && (!extra_checks || extra_checks.Invoke())
if(.)
SEND_SIGNAL(src, COMSIG_TOOL_IN_USE, user)
/// 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, FALSE)
return ..()
/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, datum/callback/callback, gentle = FALSE)
if(HAS_TRAIT(src, TRAIT_NODROP))
return
return ..()
/obj/item/proc/embedded(mob/living/carbon/human/embedded_mob)
return
/obj/item/proc/unembedded()
if(item_flags & DROPDEL)
QDEL_NULL(src)
return TRUE
/obj/item/proc/canStrip(mob/stripper, mob/owner)
SHOULD_BE_PURE(TRUE)
return !HAS_TRAIT(src, TRAIT_NODROP)
/obj/item/proc/doStrip(mob/stripper, mob/owner)
return owner.dropItemToGround(src)
///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/isEmbedHarmless()
if(embedding)
return !isnull(embedding["pain_mult"]) && !isnull(embedding["jostle_pain_mult"]) && embedding["pain_mult"] == 0 && embedding["jostle_pain_mult"] == 0
///In case we want to do something special (like self delete) upon failing to embed in something, return true
/obj/item/proc/failedEmbed()
if(item_flags & DROPDEL)
QDEL_NULL(src)
return TRUE
///Called by the carbon throw_item() proc. Returns null if the item negates the throw, or a reference to the thing to suffer the throw else.
/obj/item/proc/on_thrown(mob/living/carbon/user, atom/target)
if((item_flags & ABSTRACT) || HAS_TRAIT(src, TRAIT_NODROP))
return
user.dropItemToGround(src, silent = TRUE)
if(throwforce && HAS_TRAIT(user, TRAIT_PACIFISM))
to_chat(user, "<span class='notice'>You set [src] down gently on the ground.</span>")
return
return src
/**
* tryEmbed() is for when you want to try embedding something without dealing with the damage + hit messages of calling hitby() on the item while targetting the target.
*
* Really, this is used mostly with projectiles with shrapnel payloads, from [/datum/element/embed/proc/checkEmbedProjectile], and called on said shrapnel. Mostly acts as an intermediate between different embed elements.
*
* Arguments:
* * target- Either a body part, a carbon, or a closed turf. What are we hitting?
* * forced- Do we want this to go through 100%?
*/
/obj/item/proc/tryEmbed(atom/target, forced=FALSE, silent=FALSE)
if(!isbodypart(target) && !iscarbon(target) && !isclosedturf(target))
return
if(!forced && !LAZYLEN(embedding))
return
if(SEND_SIGNAL(src, COMSIG_EMBED_TRY_FORCE, target, forced, silent))
return TRUE
failedEmbed()
///For when you want to disable an item's embedding capabilities (like transforming weapons and such), this proc will detach any active embed elements from it.
/obj/item/proc/disableEmbedding()
SEND_SIGNAL(src, COMSIG_ITEM_DISABLE_EMBED)
return
///For when you want to add/update the embedding on an item. Uses the vars in [/obj/item/embedding], and defaults to config values for values that aren't set. Will automatically detach previous embed elements on this item.
/obj/item/proc/updateEmbedding()
if(!islist(embedding) || !LAZYLEN(embedding))
return
AddElement(/datum/element/embed,\
embed_chance = (!isnull(embedding["embed_chance"]) ? embedding["embed_chance"] : EMBED_CHANCE),\
fall_chance = (!isnull(embedding["fall_chance"]) ? embedding["fall_chance"] : EMBEDDED_ITEM_FALLOUT),\
pain_chance = (!isnull(embedding["pain_chance"]) ? embedding["pain_chance"] : EMBEDDED_PAIN_CHANCE),\
pain_mult = (!isnull(embedding["pain_mult"]) ? embedding["pain_mult"] : EMBEDDED_PAIN_MULTIPLIER),\
remove_pain_mult = (!isnull(embedding["remove_pain_mult"]) ? embedding["remove_pain_mult"] : EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER),\
rip_time = (!isnull(embedding["rip_time"]) ? embedding["rip_time"] : EMBEDDED_UNSAFE_REMOVAL_TIME),\
ignore_throwspeed_threshold = (!isnull(embedding["ignore_throwspeed_threshold"]) ? embedding["ignore_throwspeed_threshold"] : FALSE),\
impact_pain_mult = (!isnull(embedding["impact_pain_mult"]) ? embedding["impact_pain_mult"] : EMBEDDED_IMPACT_PAIN_MULTIPLIER),\
jostle_chance = (!isnull(embedding["jostle_chance"]) ? embedding["jostle_chance"] : EMBEDDED_JOSTLE_CHANCE),\
jostle_pain_mult = (!isnull(embedding["jostle_pain_mult"]) ? embedding["jostle_pain_mult"] : EMBEDDED_JOSTLE_PAIN_MULTIPLIER),\
pain_stam_pct = (!isnull(embedding["pain_stam_pct"]) ? embedding["pain_stam_pct"] : EMBEDDED_PAIN_STAM_PCT),\
embed_chance_turf_mod = (!isnull(embedding["embed_chance_turf_mod"]) ? embedding["embed_chance_turf_mod"] : EMBED_CHANCE_TURF_MOD))
return TRUE