mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-21 00:56:56 +01:00
1001 lines
33 KiB
Plaintext
1001 lines
33 KiB
Plaintext
/obj/item
|
|
name = "item"
|
|
icon = 'icons/obj/items.dmi'
|
|
w_class = WEIGHT_CLASS_NORMAL
|
|
// todo: better way, for now, block all rad contamination to interior
|
|
rad_flags = RAD_BLOCK_CONTENTS
|
|
obj_flags = OBJ_IGNORE_MOB_DEPTH | OBJ_RANGE_TARGETABLE
|
|
depth_level = 0
|
|
climb_allowed = FALSE
|
|
|
|
//* Actions *//
|
|
/// cached action descriptors
|
|
///
|
|
/// this can be:
|
|
/// * a /datum/action instance
|
|
/// * a /datum/action typepath
|
|
/// * a list of /datum/action instancse
|
|
/// * a list of /datum/action typepaths
|
|
///
|
|
/// typepaths get instanced on us entering inventory
|
|
var/item_actions
|
|
/// if [item_actions] is not set, and this is, we make a single action rendering ourselves
|
|
/// and set its name to this
|
|
var/item_action_name
|
|
/// if [item_actions] is not set, and [action_name] is set, this is the mobility flags
|
|
/// the action will check for
|
|
var/item_action_mobility_flags = MOBILITY_CAN_HOLD | MOBILITY_CAN_USE
|
|
|
|
//* Combat *//
|
|
/// Amount of damage we do on melee.
|
|
var/damage_force = 0
|
|
/// armor flag for melee attacks
|
|
var/damage_flag = ARMOR_MELEE
|
|
/// damage tier
|
|
var/damage_tier = 3
|
|
/// damage_mode bitfield - see [code/__DEFINES/combat/damage.dm]
|
|
var/damage_mode = NONE
|
|
/// DAMAGE_TYPE_* enum
|
|
///
|
|
/// * This is the primary damage type this object does on usage as a melee / thrown weapon.
|
|
var/damage_type = DAMAGE_TYPE_BRUTE
|
|
/// passive parry data / frame
|
|
///
|
|
/// * anonymous typepath is allowed
|
|
/// * typepath is allowed
|
|
/// * instance is allowed
|
|
///
|
|
/// note that the component will not be modified while held;
|
|
/// if this is changed, the component needs to be remade.
|
|
var/passive_parry
|
|
/// base melee click cooldown
|
|
var/melee_click_cd_base = 0.8 SECONDS
|
|
/// base melee click cooldown multiplier
|
|
var/melee_click_cd_multiply = 1
|
|
|
|
//* Economy
|
|
/// economic category for items
|
|
var/economic_category_item = ECONOMIC_CATEGORY_ITEM_DEFAULT
|
|
|
|
//* Flags *//
|
|
/// Item flags.
|
|
/// These flags are listed in [code/__DEFINES/inventory/item_flags.dm].
|
|
var/item_flags = ITEM_ENCUMBERS_WHILE_HELD
|
|
/// Miscellaneous flags pertaining to equippable objects.
|
|
/// These flags are listed in [code/__DEFINES/inventory/item_flags.dm].
|
|
var/clothing_flags = NONE
|
|
/// Flags for items (or in some cases mutant parts) hidden by this item when worn.
|
|
/// As of right now, some flags only work in some slots.
|
|
/// These flags are listed in [code/__DEFINES/inventory/item_flags.dm].
|
|
var/inv_hide_flags = NONE
|
|
/// Flags for the bodyparts this item covers when worn.
|
|
/// These flags are listed in [code/__DEFINES/inventory/item_flags.dm].
|
|
///
|
|
/// * Do not set these directly, use set_body_cover_flags()!
|
|
var/body_cover_flags = NONE
|
|
/// Flags which determine which body parts are protected from heat. Use the HEAD, UPPER_TORSO, LOWER_TORSO, etc. flags. See setup.dm
|
|
/// These flags are listed in [code/__DEFINES/inventory/item_flags.dm].
|
|
var/heat_protection_cover = NONE
|
|
/// Flags which determine which body parts are protected from cold. Use the HEAD, UPPER_TORSO, LOWER_TORSO, etc. flags. See setup.dm
|
|
/// These flags are listed in [code/__DEFINES/inventory/item_flags.dm].
|
|
var/cold_protection_cover = NONE
|
|
/// This is used to determine on which slots an item can fit, for inventory slots that use flags to determine this.
|
|
/// These flags are listed in [code/__DEFINES/inventory/slots.dm].
|
|
var/slot_flags = NONE
|
|
/// This is used to determine how we persist, in addition to potentially atom_persist_flags and obj_persist_flags (not yet made)
|
|
/// These flags are listed in [code/__DEFINES/inventory/item_flags.dm].
|
|
var/item_persist_flags = NONE
|
|
/// This is used to determine how default item-level interaction hooks are handled.
|
|
/// These flags are listed in [code/__DEFINES/_flags/interaction_flags.dm]
|
|
var/interaction_flags_item = INTERACT_ITEM_ATTACK_SELF
|
|
|
|
//* Inventory *//
|
|
/// Currently equipped slot ID or hand index if held in hand
|
|
var/inv_slot_or_index
|
|
/// The inventory datum we're in.
|
|
///
|
|
/// * This also doubles as an 'is in inventory' check, as this will always be set if we are in inventory.
|
|
/// * This also doubles as 'get worn mob' by doing `inv_inside?.owner`.
|
|
var/datum/inventory/inv_inside
|
|
/// currently equipped slot id
|
|
///
|
|
/// todo: `worn_slot_or_index`
|
|
var/worn_slot
|
|
/// current hand index, if held in hand
|
|
///
|
|
/// todo: `worn_slot_or_index`
|
|
var/held_index
|
|
/**
|
|
* current item we fitted over
|
|
* ! DANGER: While this is more or less bug-free for "won't lose the item when you unequip/won't get stuck", we
|
|
* ! do not promise anything for functionality - this is a SNOWFLAKE SYSTEM.
|
|
*/
|
|
var/obj/item/worn_over
|
|
/**
|
|
* current item we're fitted in.
|
|
*/
|
|
var/obj/item/worn_inside
|
|
/// suppress auto inventory hooks in forceMove
|
|
var/worn_hook_suppressed = FALSE
|
|
|
|
//* Environmentals *//
|
|
/// 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
|
|
|
|
/// Set this variable if the item protects its wearer against high pressures below an upper bound. Keep at null to disable protection.
|
|
var/max_pressure_protection
|
|
/// Set this variable if the item protects its wearer against low pressures above a lower bound. Keep at null to disable protection. 0 represents protection against hard vacuum.
|
|
var/min_pressure_protection
|
|
|
|
//* Carry Weight *//
|
|
/// encumberance.
|
|
/// calculated as max() of all encumbrance
|
|
/// result is calculated into slowdown value
|
|
/// and then max()'d with carry weight for the final slowdown used.
|
|
var/encumbrance = ITEM_ENCUMBRANCE_BASELINE
|
|
/// registered encumbrance - null if not in inventory
|
|
var/encumbrance_registered
|
|
/// carry weight in kgs. this might be generalized later so KEEP IT REALISTIC.
|
|
var/weight = ITEM_WEIGHT_BASELINE
|
|
/// registered carry weight - null if not in inventory.
|
|
var/weight_registered
|
|
/// flat encumbrance - while worn, you are treated as at **least** this encumbered
|
|
/// e.g. if someone is wearing a flat 50 encumbrance item, but their regular encumbrance tally is only 45, they still have 50 total.
|
|
var/flat_encumbrance = 0
|
|
/// Hard slowdown. Applied before carry weight.
|
|
/// This affects multiplicative movespeed.
|
|
var/slowdown = 0
|
|
|
|
//* Mounting *//
|
|
/// Base level item API for item mounts.
|
|
/// * This is manipulated from the mount side, not from the item side for now.
|
|
/// * Item side does have a Destroy() hook.
|
|
var/datum/item_mount/item_mount
|
|
|
|
//* Storage *//
|
|
/// storage cost for volumetric storage
|
|
/// null to default to weight class
|
|
var/weight_volume
|
|
|
|
//* Storage - Belts *//
|
|
/// Belt storage class
|
|
/// * IT IS NOT SAFE TO MODIFY THIS WITHOUT USING GETTER / SETTER PATTERN
|
|
/// TODO: getter / setter
|
|
var/belt_storage_class = BELT_CLASS_INVALID
|
|
/// Belt storage size
|
|
/// * IT IS NOT SAFE TO MODIFY THIS WITHOUT USING GETTER / SETTER PATTERN
|
|
/// TODO: getter / setter
|
|
var/belt_storage_size = BELT_SIZE_DEFAULT
|
|
|
|
//* Storage - Suit Storage *//
|
|
/// Suit storage classes
|
|
var/suit_storage_class = NONE
|
|
/// Suit storage classes to allow
|
|
var/suit_storage_class_allow = NONE
|
|
/// Suit storage classes to disallow
|
|
var/suit_storage_class_disallow = NONE
|
|
/// Suit storage override type-list
|
|
/// * only for adminbus really
|
|
VAR_PRIVATE/list/suit_storage_types_allow_override
|
|
/// Suit storage override type-list
|
|
/// * only for adminbus really
|
|
VAR_PRIVATE/list/suit_storage_types_disallow_override
|
|
|
|
//? unsorted / legacy
|
|
/// This saves our blood splatter overlay, which will be processed not to go over the edges of the sprite
|
|
var/image/blood_overlay = null
|
|
pass_flags = ATOM_PASS_TABLE
|
|
pressure_resistance = 5
|
|
var/obj/item/master = null
|
|
/// Used by R&D to determine what research bonuses it grants.
|
|
var/list/origin_tech = null
|
|
/**
|
|
* Used in attackby() to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]"
|
|
* Either a list() with equal chances or a single verb.
|
|
*/
|
|
var/list/attack_verb = "attacked"
|
|
|
|
/// 0 prevents all transfers, 1 is invisible
|
|
//var/heat_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/gas_transfer_coefficient = 1
|
|
/// For chemicals/diseases
|
|
var/permeability_coefficient = 1
|
|
/// For electrical admittance/conductance (electrocution checks and shit)
|
|
var/siemens_coefficient = 1
|
|
// todo: kill with fire
|
|
/// All items can have an uplink hidden inside, just remember to add the triggers.
|
|
var/obj/item/uplink/hidden/hidden_uplink = null
|
|
/// Name used for message when binoculars/scope is used
|
|
// todo: kill with fire
|
|
var/zoomdevicename = null
|
|
/// TRUE if item is actively being used to zoom. For scoped guns and binoculars.
|
|
// todo: kill with fire
|
|
var/zoom = FALSE
|
|
/// 0 won't embed, and 100 will always embed
|
|
var/embed_chance = EMBED_CHANCE_UNSET
|
|
|
|
/// Length of tiles it can reach, 1 is adjacent.
|
|
var/reach = 1
|
|
/// Icon overlay for ADD highlights when applicable.
|
|
var/addblends
|
|
|
|
//? Sounds
|
|
/// sound used when used in melee attacks. null for default for our damage tpye.
|
|
var/attack_sound
|
|
/// Used when thrown into a mob.
|
|
var/mob_throw_hit_sound
|
|
/// Sound used when equipping the item into a valid slot from hands or ground
|
|
var/equip_sound
|
|
/// Sound used when uneqiupping the item from a valid slot to hands or ground
|
|
var/unequip_sound
|
|
/// Pickup sound - played when picking something up off the floor.
|
|
var/pickup_sound = 'sound/items/pickup/device.ogg'
|
|
/// Drop sound - played when dropping something onto the floor.
|
|
var/drop_sound = 'sound/items/drop/device.ogg'
|
|
|
|
/// Whether or not we are heavy. Used for some species to determine if they can two-hand it.
|
|
var/heavy = FALSE
|
|
|
|
/// If true, a 'cleaving' attack will occur.
|
|
var/can_cleave = FALSE
|
|
/// Used to avoid infinite cleaving.
|
|
var/cleaving = FALSE
|
|
|
|
/obj/item/Initialize(mapload)
|
|
. = ..()
|
|
loc?.on_contents_item_new(src)
|
|
if(islist(origin_tech))
|
|
origin_tech = typelist(NAMEOF(src, origin_tech), origin_tech)
|
|
//Potential memory optimization: Making embed chance a getter if unset.
|
|
if(embed_chance == EMBED_CHANCE_UNSET)
|
|
if(damage_mode & DAMAGE_MODE_SHARP)
|
|
embed_chance = max(5, round(damage_force/w_class))
|
|
else
|
|
embed_chance = max(5, round(damage_force/(w_class*3)))
|
|
|
|
/obj/item/Destroy()
|
|
// run inventory hooks
|
|
if(worn_slot && !worn_hook_suppressed)
|
|
var/mob/M = get_worn_mob()
|
|
if(!ismob(M))
|
|
stack_trace("invalid current equipped slot [worn_slot] on an item not on a mob.")
|
|
return ..()
|
|
M.temporarily_remove_from_inventory(src, INV_OP_FORCE | INV_OP_DELETING)
|
|
// inform mount so it can unmount us
|
|
item_mount?.on_item_del(src)
|
|
return ..()
|
|
|
|
/// Check if target is reasonable for us to operate on.
|
|
/obj/item/proc/check_allowed_items(atom/target, not_inside, target_self)
|
|
if(((src in target) && !target_self) || ((!istype(target.loc, /turf)) && (!istype(target, /turf)) && (not_inside)))
|
|
return FALSE
|
|
else
|
|
return TRUE
|
|
|
|
/obj/item/verb/move_to_top()
|
|
set name = "Move To Top"
|
|
set category = VERB_CATEGORY_OBJECT
|
|
set src in oview(1)
|
|
|
|
if(!istype(src.loc, /turf) || usr.stat || usr.restrained() )
|
|
return
|
|
|
|
var/turf/T = src.loc
|
|
|
|
src.loc = null
|
|
src.loc = T
|
|
|
|
/// See inventory_sizes.dm for the defines.
|
|
/obj/item/examine(mob/user, dist)
|
|
. = ..()
|
|
. += "[gender == PLURAL ? "They are" : "It is"] a [weightclass2text(w_class)] item."
|
|
switch(get_encumbrance())
|
|
if(-INFINITY to 0.1)
|
|
. += "It looks effortless to carry around and wear."
|
|
if(0.1 to 0.75)
|
|
. += "It looks very easy to carry around and wear."
|
|
if(0.75 to 2)
|
|
. += "It looks decently able to be carried around and worn."
|
|
if(2 to 5)
|
|
. += "It looks somewhat unwieldly."
|
|
if(5 to 10)
|
|
. += "It looks quite unwieldly."
|
|
if(10 to 20)
|
|
. += "It looks very unwieldly. It would take a good effort to run around with it."
|
|
if(20 to 40)
|
|
. += "It looks extremely unwieldly. You probably will have a hard time running with it."
|
|
if(40 to INFINITY)
|
|
. += "It's so unwieldly that it's a surprise you can hold it at all. You really won't be doing much running with it."
|
|
switch(get_weight())
|
|
if(-INFINITY to 0.1)
|
|
// todo: put this in when we actually get weight
|
|
// . += "It looks like it weighs practically nothing."
|
|
if(0.1 to 0.75)
|
|
. += "It looks like it weighs very little."
|
|
if(0.75 to 2)
|
|
. += "It looks like it's decently lightweight."
|
|
if(2 to 5)
|
|
. += "It looks like it weighs a bit."
|
|
if(5 to 10)
|
|
. += "It looks like it weighs a good amount."
|
|
if(10 to 20)
|
|
. += "It looks like it is heavy. It would take a good effort to run around with it."
|
|
if(20 to 40)
|
|
. += "It looks like it weighs a lot. You probably will have a hard time running with it."
|
|
if(40 to INFINITY)
|
|
. += "It looks like it weighs a ton. You really won't be doing much running with it."
|
|
if(belt_storage_class != BELT_CLASS_INVALID)
|
|
. += "It looks like it can fit into a belt, and would use [belt_storage_size] [lowertext(global.belt_class_names[belt_storage_class])]-sized loop(s)."
|
|
|
|
// if(item_flags & (ITEM_CAN_BLOCK | ITEM_CAN_PARRY))
|
|
// var/datum/block_parry_data/data = return_block_parry_datum(block_parry_data)
|
|
// . += "[src] has the capacity to be used to block and/or parry. <a href='?src=[REF(data)];name=[name];block=[item_flags & ITEM_CAN_BLOCK];parry=[item_flags & ITEM_CAN_PARRY];render=1'>\[Show Stats\]</a>"
|
|
|
|
/proc/weightclass2text(w_class)
|
|
switch(w_class)
|
|
if(WEIGHT_CLASS_TINY, WEIGHT_CLASS_TINY)
|
|
. = "tiny"
|
|
if(WEIGHT_CLASS_SMALL, WEIGHT_CLASS_SMALL)
|
|
. = "small"
|
|
if(WEIGHT_CLASS_NORMAL, WEIGHT_CLASS_NORMAL)
|
|
. = "normal-sized"
|
|
if(WEIGHT_CLASS_BULKY, WEIGHT_CLASS_BULKY)
|
|
. = "bulky"
|
|
if(WEIGHT_CLASS_HUGE, WEIGHT_CLASS_HUGE)
|
|
. = "huge"
|
|
if(WEIGHT_CLASS_GIGANTIC)
|
|
. = "gigantic"
|
|
else
|
|
. = ""
|
|
|
|
/obj/item/proc/talk_into(mob/M as mob, text)
|
|
return
|
|
|
|
/obj/item/proc/get_volume_by_throwforce_and_or_w_class() // This is used for figuring out how loud our sounds are for throwing.
|
|
if(throw_force && w_class)
|
|
return clamp((throw_force + w_class) * 5, 30, 100)// Add the item's throw_force to its weight class and multiply by 5, then clamp the value between 30 and 100
|
|
else if(w_class)
|
|
return clamp(w_class * 8, 20, 100) // Multiply the item's weight class by 8, then clamp the value between 20 and 100
|
|
else
|
|
return 0
|
|
|
|
|
|
/obj/item/throw_impact(atom/A, datum/thrownthing/TT)
|
|
. = ..()
|
|
if(QDELETED(A))
|
|
return
|
|
if(isliving(A)) //Living mobs handle hit sounds differently.
|
|
var/volume = get_volume_by_throwforce_and_or_w_class()
|
|
if (throw_force > 0)
|
|
if (mob_throw_hit_sound)
|
|
playsound(A, mob_throw_hit_sound, volume, TRUE, -1)
|
|
else if(attack_sound)
|
|
playsound(A, attack_sound, volume, TRUE, -1)
|
|
else
|
|
playsound(A, 'sound/weapons/genhit.ogg', volume, TRUE, -1)
|
|
else
|
|
playsound(A, 'sound/weapons/throwtap.ogg', 1, volume, -1)
|
|
|
|
/obj/item/throw_land(atom/A, datum/thrownthing/TT)
|
|
. = ..()
|
|
// if we're landing from the impact we don't play a sound as we already played hitsound
|
|
if(drop_sound && (A != TT.landing_from_impact))
|
|
playsound(src, drop_sound, 50, TRUE)
|
|
if(TT.throw_flags & THROW_AT_IS_NEAT)
|
|
return
|
|
var/matrix/M = matrix(transform)
|
|
M.Turn(rand(-170, 170))
|
|
transform = M
|
|
set_pixel_offsets(rand(-8, 8), rand(-8, 8))
|
|
|
|
/obj/item/verb/verb_pickup()
|
|
set src in oview(1)
|
|
set category = VERB_CATEGORY_OBJECT
|
|
set name = "Pick up"
|
|
|
|
if(!(usr)) //BS12 EDIT
|
|
return
|
|
if(!CHECK_MOBILITY(usr, MOBILITY_CAN_PICKUP) || !Adjacent(usr))
|
|
return
|
|
if((!istype(usr, /mob/living/carbon)) || (istype(usr, /mob/living/carbon/brain)))//Is humanoid, and is not a brain
|
|
to_chat(usr, "<span class='warning'>You can't pick things up!</span>")
|
|
return
|
|
var/mob/living/carbon/C = usr
|
|
if( usr.stat || usr.restrained() )//Is not asleep/dead and is not restrained
|
|
to_chat(usr, "<span class='warning'>You can't pick things up!</span>")
|
|
return
|
|
if(src.anchored) //Object isn't anchored
|
|
to_chat(usr, "<span class='warning'>You can't pick that up!</span>")
|
|
return
|
|
if(C.get_active_held_item()) //Hand is not full
|
|
to_chat(usr, "<span class='warning'>Your hand is full.</span>")
|
|
return
|
|
if(!istype(src.loc, /turf)) //Object is on a turf
|
|
to_chat(usr, "<span class='warning'>You can't pick that up!</span>")
|
|
return
|
|
|
|
attempt_pickup(usr)
|
|
|
|
/obj/item/ui_action_click(datum/action/action, datum/event_args/actor/actor)
|
|
attack_self(usr)
|
|
return TRUE
|
|
|
|
/obj/item/proc/get_loc_turf()
|
|
var/atom/L = loc
|
|
while(L && !istype(L, /turf/))
|
|
L = L.loc
|
|
return loc
|
|
|
|
/obj/item/proc/eyestab(mob/living/carbon/M as mob, mob/living/carbon/user as mob)
|
|
|
|
var/mob/living/carbon/human/H = M
|
|
var/mob/living/carbon/human/U = user
|
|
if(istype(H))
|
|
for(var/obj/item/protection in list(H.head, H.wear_mask, H.glasses))
|
|
if(protection && (protection.body_cover_flags & EYES))
|
|
// you can't stab someone in the eyes wearing a mask!
|
|
to_chat(user, "<span class='warning'>You're going to need to remove the eye covering first.</span>")
|
|
return
|
|
|
|
if(!M.has_eyes())
|
|
to_chat(user, "<span class='warning'>You cannot locate any eyes on [M]!</span>")
|
|
return
|
|
|
|
//this should absolutely trigger even if not aim-impaired in some way
|
|
var/hit_zone = get_zone_with_miss_chance(U.zone_sel.selecting, M, U.get_accuracy_penalty(U))
|
|
if(!hit_zone)
|
|
U.do_attack_animation(M)
|
|
playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
|
|
//visible_message("<span class='danger'>[U] attempts to stab [M] in the eyes, but misses!</span>")
|
|
for(var/mob/V in viewers(M))
|
|
V.show_message("<span class='danger'>[U] attempts to stab [M] in the eyes, but misses!</span>")
|
|
return
|
|
|
|
add_attack_logs(user,M,"Attack eyes with [name]")
|
|
|
|
user.setClickCooldownLegacy(user.get_attack_speed_legacy())
|
|
user.do_attack_animation(M)
|
|
|
|
src.add_fingerprint(user)
|
|
if(istype(H))
|
|
|
|
var/obj/item/organ/internal/eyes/eyes = H.internal_organs_by_name[O_EYES]
|
|
|
|
if(H != user)
|
|
for(var/mob/O in (viewers(M) - user - M))
|
|
O.show_message("<span class='danger'>[M] has been stabbed in the eye with [src] by [user].</span>", 1)
|
|
to_chat(M, "<span class='danger'>[user] stabs you in the eye with [src]!</span>")
|
|
to_chat(user, "<span class='danger'>You stab [M] in the eye with [src]!</span>")
|
|
else
|
|
user.visible_message( \
|
|
"<span class='danger'>[user] has stabbed themself with [src]!</span>", \
|
|
"<span class='danger'>You stab yourself in the eyes with [src]!</span>" \
|
|
)
|
|
|
|
eyes.damage += rand(3,4)
|
|
if(eyes.damage >= eyes.min_bruised_damage)
|
|
if(M.stat != 2)
|
|
if(!(eyes.robotic >= ORGAN_ROBOT)) //robot eyes bleeding might be a bit silly
|
|
to_chat(M, "<span class='danger'>Your eyes start to bleed profusely!</span>")
|
|
if(prob(50))
|
|
if(M.stat != 2)
|
|
to_chat(M, "<span class='warning'>You drop what you're holding and clutch at your eyes!</span>")
|
|
M.drop_active_held_item()
|
|
M.eye_blurry += 10
|
|
M.afflict_unconscious(20 * 1)
|
|
M.afflict_paralyze(20 * 4)
|
|
if (eyes.damage >= eyes.min_broken_damage)
|
|
if(M.stat != 2)
|
|
to_chat(M, "<span class='warning'>You go blind!</span>")
|
|
var/obj/item/organ/external/affecting = H.get_organ(BP_HEAD)
|
|
affecting.inflict_bodypart_damage(
|
|
brute = 7,
|
|
)
|
|
else
|
|
M.take_random_targeted_damage(brute = 7)
|
|
M.eye_blurry += rand(3,4)
|
|
return
|
|
|
|
/obj/item/clean_blood()
|
|
. = ..()
|
|
if(blood_overlay)
|
|
cut_overlay(blood_overlay)
|
|
if(istype(src, /obj/item/clothing/gloves))
|
|
var/obj/item/clothing/gloves/G = src
|
|
G.transfer_blood = 0
|
|
|
|
/obj/item/reveal_blood()
|
|
if(was_bloodied && !fluorescent)
|
|
fluorescent = 1
|
|
blood_color = COLOR_LUMINOL
|
|
blood_overlay.color = COLOR_LUMINOL
|
|
update_icon()
|
|
|
|
/obj/item/add_blood(mob/living/carbon/human/M as mob)
|
|
if (!..())
|
|
return 0
|
|
|
|
if(istype(src, /obj/item/melee/transforming/energy))
|
|
return
|
|
|
|
//if we haven't made our blood_overlay already
|
|
if( !blood_overlay )
|
|
generate_blood_overlay()
|
|
|
|
//Make the blood_overlay have the proper color then apply it.
|
|
blood_overlay.color = blood_color
|
|
add_overlay(blood_overlay)
|
|
|
|
//if this blood isn't already in the list, add it
|
|
if(istype(M))
|
|
if(blood_DNA[M.dna.unique_enzymes])
|
|
return 0 //already bloodied with this blood. Cannot add more.
|
|
blood_DNA[M.dna.unique_enzymes] = M.dna.b_type
|
|
return 1 //we applied blood to the item
|
|
|
|
// Protip: don't use world scans to implement caching. Yes, that is how this used to work.
|
|
GLOBAL_LIST_EMPTY(blood_overlay_cache)
|
|
|
|
/obj/item/proc/generate_blood_overlay()
|
|
if(blood_overlay)
|
|
return
|
|
|
|
if (GLOB.blood_overlay_cache[type])
|
|
blood_overlay = GLOB.blood_overlay_cache[type]
|
|
return
|
|
|
|
var/icon/I = new /icon(icon, icon_state)
|
|
I.Blend(new /icon('icons/effects/blood.dmi', rgb(255,255,255)), ICON_ADD) //fills the icon_state with white (except where it's transparent)
|
|
I.Blend(new /icon('icons/effects/blood.dmi', "itemblood"), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant
|
|
|
|
blood_overlay = GLOB.blood_overlay_cache[type] = image(I)
|
|
|
|
/obj/item/proc/showoff(mob/user)
|
|
for (var/mob/M in view(user))
|
|
M.show_message("[user] holds up [src]. <a HREF=?src=\ref[M];lookitem=\ref[src]>Take a closer look.</a>",1)
|
|
|
|
/mob/living/carbon/verb/showoff()
|
|
set name = "Show Held Item"
|
|
set category = VERB_CATEGORY_OBJECT
|
|
|
|
var/obj/item/I = get_active_held_item()
|
|
if(I && !(I.atom_flags & ATOM_ABSTRACT))
|
|
I.showoff(src)
|
|
|
|
/*
|
|
For zooming with scope or binoculars. This is called from
|
|
modules/mob/mob_movement.dm if you move you will be zoomed out
|
|
modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
|
|
*/
|
|
//Looking through a scope or binoculars should /not/ improve your periphereal vision. Still, increase viewsize a tiny bit so that sniping isn't as restricted to NSEW
|
|
|
|
// this is shitcode holy crap
|
|
/obj/item/proc/zoom(tileoffset = 14, viewsize = 9, mob/user = usr, wornslot = FALSE) //tileoffset is client view offset in the direction the user is facing. viewsize is how far out this thing zooms. 7 is normal view. slot determines whether the item needs to be held in-hand (by being set to FALSE) OR worn on a specific slot to look through
|
|
|
|
var/devicename
|
|
|
|
if(zoomdevicename)
|
|
devicename = zoomdevicename
|
|
else
|
|
devicename = src.name
|
|
|
|
var/cannotzoom
|
|
|
|
if((user.stat && !zoom) || !(istype(user,/mob/living/carbon/human)))
|
|
to_chat(user, "You are unable to focus through the [devicename]")
|
|
cannotzoom = 1
|
|
else if(!zoom && (GLOB.global_hud.darkMask[1] in user.client.screen))
|
|
to_chat(user, "Your visor gets in the way of looking through the [devicename]")
|
|
cannotzoom = 1
|
|
else if(!zoom && user.get_active_held_item() != src && wornslot == FALSE)
|
|
to_chat(user, "You are too distracted to look through the [devicename], perhaps if it was in your active hand this might work better")
|
|
cannotzoom = 1
|
|
else if(!zoom && user.item_by_slot_id(wornslot) != src && wornslot != FALSE)
|
|
to_chat(user, "You need to wear the [devicename] to look through it properly")
|
|
cannotzoom = 1
|
|
//We checked above if they are a human and returned already if they weren't.
|
|
var/mob/living/carbon/human/H = user
|
|
if(!zoom && !cannotzoom)
|
|
H.ensure_self_perspective()
|
|
H.self_perspective.set_augmented_view(viewsize - 2, viewsize - 2)
|
|
zoom = 1
|
|
|
|
var/tilesize = 32
|
|
var/viewoffset = tilesize * tileoffset
|
|
|
|
switch(H.dir)
|
|
if (NORTH)
|
|
H.client.pixel_x = 0
|
|
H.client.pixel_y = viewoffset
|
|
if (SOUTH)
|
|
H.client.pixel_x = 0
|
|
H.client.pixel_y = -viewoffset
|
|
if (EAST)
|
|
H.client.pixel_x = viewoffset
|
|
H.client.pixel_y = 0
|
|
if (WEST)
|
|
H.client.pixel_x = -viewoffset
|
|
H.client.pixel_y = 0
|
|
H.visible_message("[user] peers through the [zoomdevicename ? "[zoomdevicename] of the [src.name]" : "[src.name]"].")
|
|
H.looking_elsewhere = TRUE
|
|
H.handle_vision()
|
|
else
|
|
H.ensure_self_perspective()
|
|
H.self_perspective.set_augmented_view(0, 0)
|
|
zoom = 0
|
|
|
|
H.client.pixel_x = 0
|
|
H.client.pixel_y = 0
|
|
H.looking_elsewhere = FALSE
|
|
H.handle_vision()
|
|
|
|
if(!cannotzoom)
|
|
user.visible_message("[zoomdevicename ? "[user] looks up from the [src.name]" : "[user] lowers the [src.name]"].")
|
|
|
|
return
|
|
|
|
/obj/item/proc/pwr_drain()
|
|
return 0 // Process Kill
|
|
|
|
/// Check if an object should ignite others, like a lit lighter or candle.
|
|
/obj/item/proc/is_hot()
|
|
return FALSE
|
|
|
|
/// These procs are for RPEDs and part ratings. The concept for this was borrowed from /vg/station.
|
|
/// Gets the rating of the item, used in stuff like machine construction.
|
|
/// return null for don't use as part
|
|
/obj/item/proc/get_rating()
|
|
return null
|
|
|
|
/// These procs are for RPEDs and part ratings, but used for RPED sorting of parts.
|
|
/obj/item/proc/rped_rating()
|
|
return get_rating()
|
|
|
|
// todo: WHAT?
|
|
/obj/item/interact(mob/user)
|
|
add_fingerprint(user)
|
|
ui_interact(user)
|
|
|
|
//* Actions *//
|
|
|
|
/**
|
|
* instructs all our action buttons to re-render
|
|
*/
|
|
/obj/item/proc/update_action_buttons()
|
|
if(islist(item_actions))
|
|
for(var/datum/action/action in item_actions)
|
|
action.update_buttons()
|
|
else if(istype(item_actions, /datum/action))
|
|
var/datum/action/action = item_actions
|
|
action.update_buttons()
|
|
|
|
/**
|
|
* ensures our [item_actions] variable is set to:
|
|
*
|
|
* * null
|
|
* * a list of actions
|
|
* * an action instance
|
|
*/
|
|
/obj/item/proc/ensure_item_actions_loaded()
|
|
if(item_actions)
|
|
if(islist(item_actions))
|
|
var/requires_init = FALSE
|
|
for(var/i in 1 to length(item_actions))
|
|
if(ispath(item_actions[i]))
|
|
requires_init = TRUE
|
|
break
|
|
if(requires_init)
|
|
set_actions_to(item_actions)
|
|
else if(ispath(item_actions))
|
|
set_actions_to(item_actions)
|
|
else if(item_action_name)
|
|
var/datum/action/item_action/created = new(src)
|
|
created.name = item_action_name
|
|
created.check_mobility_flags = item_action_mobility_flags
|
|
set_actions_to(created)
|
|
|
|
/**
|
|
* setter for [item_actions]
|
|
*
|
|
* accepts:
|
|
*
|
|
* * an instance of /datum/action
|
|
* * a typepath of /datum/action
|
|
* * a list of /datum/action instances and typepaths
|
|
* * null
|
|
*/
|
|
/obj/item/proc/set_actions_to(descriptor)
|
|
var/mob/get_worn_mob = get_worn_mob()
|
|
|
|
if(get_worn_mob)
|
|
unregister_item_actions(get_worn_mob)
|
|
|
|
if(ispath(descriptor, /datum/action))
|
|
descriptor = new descriptor(src)
|
|
else if(islist(descriptor))
|
|
var/list/transforming = descriptor:Copy()
|
|
for(var/i in 1 to length(transforming))
|
|
if(ispath(transforming[i], /datum/action))
|
|
var/path = transforming[i]
|
|
transforming[i] = new path(src)
|
|
descriptor = transforming
|
|
else
|
|
item_actions = descriptor
|
|
|
|
if(get_worn_mob)
|
|
register_item_actions(get_worn_mob)
|
|
|
|
/**
|
|
* handles action granting
|
|
*/
|
|
/obj/item/proc/register_item_actions(mob/user)
|
|
ensure_item_actions_loaded()
|
|
if(islist(item_actions))
|
|
for(var/datum/action/action in item_actions)
|
|
action.grant(user.inventory.actions)
|
|
else if(istype(item_actions, /datum/action))
|
|
var/datum/action/action = item_actions
|
|
action.grant(user.inventory.actions)
|
|
|
|
/**
|
|
* handles action revoking
|
|
*/
|
|
/obj/item/proc/unregister_item_actions(mob/user)
|
|
if(islist(item_actions))
|
|
for(var/datum/action/action in item_actions)
|
|
action.revoke(user.inventory.actions)
|
|
else if(istype(item_actions, /datum/action))
|
|
var/datum/action/action = item_actions
|
|
action.revoke(user.inventory.actions)
|
|
|
|
//* Attack *//
|
|
|
|
/**
|
|
* grabs an attack verb to use
|
|
*
|
|
* @params
|
|
* * target - thing being attacked
|
|
* * user - person attacking
|
|
*
|
|
* @return attack verb
|
|
*/
|
|
/obj/item/proc/get_attack_verb(atom/target, mob/user)
|
|
return length(attack_verb)? pick(attack_verb) : attack_verb
|
|
|
|
/**
|
|
* can be sharp; even if not being used as such
|
|
*
|
|
* @params
|
|
* * strict - require us to be toggled to sharp mode if there's multiple modes of attacking.
|
|
*/
|
|
/obj/item/proc/is_sharp(strict)
|
|
return (damage_mode & DAMAGE_MODE_SHARP)
|
|
|
|
/**
|
|
* can be edged; even if not being used as such
|
|
*
|
|
* @params
|
|
* * strict - require us to be toggled to sharp mode if there's multiple modes of attacking.
|
|
*/
|
|
/obj/item/proc/is_edge(strict)
|
|
return (damage_mode & DAMAGE_MODE_EDGE)
|
|
|
|
/**
|
|
* can be piercing; even if not being used as such
|
|
*
|
|
* @params
|
|
* * strict - require us to be toggled to sharp mode if there's multiple modes of attacking.
|
|
*/
|
|
/obj/item/proc/is_pierce(strict)
|
|
return (damage_mode & DAMAGE_MODE_PIERCE)
|
|
|
|
/**
|
|
* can be shredding; even if not being used as such
|
|
*
|
|
* @params
|
|
* * strict - require us to be toggled to sharp mode if there's multiple modes of attacking.
|
|
*/
|
|
/obj/item/proc/is_shred(strict)
|
|
return (damage_mode & DAMAGE_MODE_SHRED)
|
|
|
|
//* Combat *//
|
|
|
|
/obj/item/proc/load_passive_parry()
|
|
if(!passive_parry)
|
|
return
|
|
passive_parry = resolve_passive_parry_data(passive_parry)
|
|
var/datum/component/passive_parry/loaded = GetComponent(/datum/component/passive_parry)
|
|
if(loaded)
|
|
loaded.parry_data = passive_parry
|
|
|
|
/obj/item/proc/reload_passive_parry()
|
|
load_passive_parry()
|
|
|
|
//* Flags *//
|
|
|
|
/obj/item/proc/set_body_cover_flags(new_body_cover_flags)
|
|
body_cover_flags = new_body_cover_flags
|
|
inv_inside.invalidate_coverage_cache()
|
|
|
|
//* Interactions *//
|
|
|
|
/obj/item/attackby(obj/item/I, mob/user, list/params, clickchain_flags, damage_multiplier)
|
|
if(isturf(loc) && I.obj_storage?.allow_mass_gather && I.obj_storage.allow_mass_gather_via_click)
|
|
I.obj_storage.auto_handle_interacted_mass_pickup(new /datum/event_args/actor(user), src)
|
|
return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
|
|
return ..()
|
|
|
|
/**
|
|
* Hitsound override when successfully melee attacking someone for melee_hit()
|
|
*
|
|
* We get final say by returning a sound here.
|
|
*/
|
|
/obj/item/proc/attacksound_override(atom/target, attack_type)
|
|
return
|
|
|
|
//* Inventory *//
|
|
|
|
/**
|
|
* Called when someone clisk us on a storage, before the storage handler's
|
|
* 'put item in' runs. Return FALSE to deny.
|
|
*/
|
|
/obj/item/proc/allow_auto_storage_insert(datum/event_args/actor/actor, datum/object_system/storage/storage)
|
|
return TRUE
|
|
|
|
/obj/item/proc/on_exit_storage(datum/object_system/storage/storage)
|
|
SEND_SIGNAL(src, COMSIG_STORAGE_EXITED, storage)
|
|
|
|
/obj/item/proc/on_enter_storage(datum/object_system/storage/storage)
|
|
SEND_SIGNAL(src, COMSIG_STORAGE_ENTERED, storage)
|
|
|
|
//* Materials *//
|
|
|
|
/obj/item/material_trait_brittle_shatter()
|
|
var/datum/prototype/material/material = get_primary_material()
|
|
var/turf/T = get_turf(src)
|
|
T.visible_message("<span class='danger'>\The [src] [material.destruction_desc]!</span>")
|
|
// if(istype(loc, /mob/living))
|
|
// var/mob/living/M = loc
|
|
// if(material.shard_type == SHARD_SHARD) // Wearing glass armor is a bad idea.
|
|
// var/obj/item/material/shard/S = material.place_shard(T)
|
|
// M.embed(S)
|
|
|
|
playsound(src, "shatter", 70, 1)
|
|
qdel(src)
|
|
|
|
//* Mount *//
|
|
|
|
/**
|
|
* Mounts to an item mount
|
|
*/
|
|
/obj/item/proc/mount(datum/item_mount/mount)
|
|
return mount.mount(src)
|
|
|
|
/**
|
|
* Unmounts us if we're mounted onto an item mount
|
|
*/
|
|
/obj/item/proc/unmount()
|
|
return item_mount?.unmount(src)
|
|
|
|
/**
|
|
* Returns if we're mounted (true / false)
|
|
*/
|
|
/obj/item/proc/is_mounted() as num
|
|
return !!item_mount
|
|
|
|
/**
|
|
* Returns our mount, if any
|
|
*/
|
|
/obj/item/proc/get_mount() as /datum/item_mount
|
|
return item_mount
|
|
|
|
//* Mouse *//
|
|
|
|
/obj/item/MouseEntered(location, control, params)
|
|
..()
|
|
SEND_SIGNAL(src, COMSIG_ITEM_MOUSE_ENTERED, usr)
|
|
|
|
/obj/item/MouseExited(location, control, params)
|
|
..()
|
|
SEND_SIGNAL(src, COMSIG_ITEM_MOUSE_EXITED, usr)
|
|
|
|
//* Rendering *//
|
|
|
|
/**
|
|
* invokes
|
|
*
|
|
* * update_icon()
|
|
* * update_worn_icon()
|
|
* * update_action_buttons()
|
|
*
|
|
* please be very delicate with this, this is expensive
|
|
*/
|
|
/obj/item/proc/update_full_icon()
|
|
update_icon()
|
|
update_worn_icon()
|
|
update_action_buttons()
|
|
|
|
//* Storage *//
|
|
|
|
/obj/item/proc/get_weight_class()
|
|
return w_class
|
|
|
|
/obj/item/proc/get_weight_volume()
|
|
return isnull(weight_volume)? global.w_class_to_volume[w_class || WEIGHT_CLASS_GIGANTIC] : weight_volume
|
|
|
|
/obj/item/proc/set_weight_class(weight_class)
|
|
var/old = w_class
|
|
w_class = weight_class
|
|
loc?.on_contents_weight_class_change(src, old, weight_class)
|
|
|
|
/obj/item/proc/set_weight_volume(volume)
|
|
var/old = weight_volume
|
|
weight_volume = volume
|
|
loc?.on_contents_weight_volume_change(src, old, volume)
|
|
|
|
/**
|
|
* called when someone is opening a storage with us in it
|
|
*
|
|
* @return TRUE to stop the storage from opening
|
|
*/
|
|
/obj/item/proc/on_containing_storage_opening(datum/event_args/actor/actor, datum/object_system/storage/storage)
|
|
return FALSE
|
|
|
|
//* VV *//
|
|
|
|
/obj/item/vv_get_var(var_name, resolve)
|
|
switch(var_name)
|
|
if(NAMEOF(src, passive_parry))
|
|
if(resolve)
|
|
load_passive_parry()
|
|
return ..()
|
|
|
|
/obj/item/vv_edit_var(var_name, var_value, mass_edit, raw_edit)
|
|
switch(var_name)
|
|
if(NAMEOF(src, passive_parry))
|
|
. = ..()
|
|
reload_passive_parry()
|
|
if(NAMEOF(src, item_flags))
|
|
var/requires_update = (item_flags & (ITEM_ENCUMBERS_WHILE_HELD | ITEM_ENCUMBERS_ONLY_HELD)) != (var_value & (ITEM_ENCUMBERS_WHILE_HELD | ITEM_ENCUMBERS_ONLY_HELD))
|
|
. = ..()
|
|
if(. && requires_update)
|
|
var/mob/living/L = get_worn_mob()
|
|
// check, as get_worn_mob() returns /mob, not /living
|
|
if(istype(L))
|
|
L.recalculate_carry()
|
|
L.update_carry()
|
|
if(NAMEOF(src, weight), NAMEOF(src, encumbrance), NAMEOF(src, flat_encumbrance))
|
|
// todo: introspection system update - this should be 'handled', as opposed to hooked.
|
|
. = ..()
|
|
if(. )
|
|
var/mob/living/L = get_worn_mob()
|
|
// check, as get_worn_mob() returns /mob, not /living
|
|
if(istype(L))
|
|
L.update_carry_slowdown()
|
|
if(NAMEOF(src, slowdown))
|
|
. = ..()
|
|
if(.)
|
|
var/mob/living/L = get_worn_mob()
|
|
// check, as get_worn_mob() returns /mob, not /living
|
|
if(istype(L))
|
|
L.update_item_slowdown()
|
|
if(NAMEOF(src, w_class))
|
|
if(!isnum(var_value) && !raw_edit)
|
|
return FALSE
|
|
if((var_value < WEIGHT_CLASS_MIN) || (var_value > WEIGHT_CLASS_MAX))
|
|
return FALSE
|
|
set_weight_class(var_value)
|
|
return TRUE
|
|
if(NAMEOF(src, weight_volume))
|
|
if(!isnum(var_value) && !raw_edit)
|
|
return FALSE
|
|
if(var_value < 0)
|
|
return FALSE
|
|
set_weight_volume(var_value)
|
|
return TRUE
|
|
else
|
|
return ..()
|