Files
Leland KembleandGitHub 06608081d2 Moves a greater manyer morer things from attackby() to item_interaction() (#96739)
## About The Pull Request

50 files, plus additonal file that was necessary because I slightly
refactored windoor construction. This ended up like three and a half
times larger than most of the other ones, I really wasn't meaning for
that to happen. Maybe its because I more vigorously enforced the empty
line after every return thing. The only way to find out is to read 1700
lines of changes, and I don't feel like doing that again. I pinky
promise it all works, and I've tested every item changed individually.

Changes beyond conversion:
You can't welder down reinforced falsewalls anymore, pretty sure it was
an oversight because they're supposed to be taken down by wirecutters
You can right click to close gun lockers with an empty hand, because
otherwise you can't close the lockers at all if there's a gun in them
Meatspikes no longer runtime on construction due to the old trick of
balloon alert + qdel
Windoor assembly no longer SUCKS DICK!!, it's no longer storing
essentially a boolean in an undocumented number in a string, it no
longer requires a VERB to flip its direction.
on a related note, the creator of the windoor assembly construction
steps has been sent to live on a farm far out in the country

Also, a quick note about how these prs are gonna fuck everything up
until they're all together. There's a lot more places where `attackby()`
is directly called than I would have assumed, and given as those are
being changed to call `item_interaction()` potentially(usually) in a
different pr than the pr in which that atom's `attackby()` is actually
converted, that function's not going to work at all until both are
merged. The good news is that almost every instance this is happening,
it's pure convenience, where the user would have access to both items
anyway and could just manually call the attack chain by clicking them
together. Instances where this is not the case are being skipped over
until they can be packaged together to avoid the issue, but I'm not
bothering to do this on ones where the desync won't make an interaction
actually unusable, because that would require me to turn a 1700 line pr
into a 3500 line pr and I really was aiming for like 500

## Why It's Good For The Game

Ignore the paragraph about how I expect this to create a hostage
situation where the only fix to the bugs I make is to merge my other
prs, and instead think about how cool swing combat would be and how much
you want it

## Changelog
🆑
fix: you now can only use a wirecutter to take down a reinforced
falsewall
fix: meatspikes no longer runtime on construction
fix: you no longer have to use a verb that you can't use to flip windoor
assemblies

qol: you can right click a gun locker to close it

code: 50 files have been moved from attackby() to item_interaction()
/🆑
2026-07-16 15:07:43 +02:00

500 lines
18 KiB
Plaintext

#define WEAPON_BASH_COOLDOWN (3 SECONDS)
/obj/item/shield
name = "shield"
icon = 'icons/obj/weapons/shields.dmi'
lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi'
abstract_type = /obj/item/shield
block_chance = 50
slot_flags = ITEM_SLOT_BACK
force = 10
throwforce = 5
throw_speed = 2
throw_range = 3
w_class = WEIGHT_CLASS_BULKY
attack_verb_continuous = list("shoves", "bashes")
attack_verb_simple = list("shove", "bash")
armor_type = /datum/armor/item_shield
block_sound = 'sound/items/weapons/block_shield.ogg'
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 5.55, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 4.8)
/// makes beam projectiles pass through the shield
var/transparent = FALSE
/// if the shield will break by sustaining damage
var/breakable_by_damage = TRUE
/// what the shield leaves behind when it breaks
var/shield_break_leftover = /obj/item/stack/sheet/mineral/wood
/// sound the shield makes when it breaks
var/shield_break_sound = 'sound/effects/bang.ogg'
/// baton bash cooldown
COOLDOWN_DECLARE(weapon_bash)
/// is shield bashable?
var/is_bashable = TRUE
/// sound when a shield is bashed
var/shield_bash_sound = 'sound/effects/shieldbash.ogg'
/datum/armor/item_shield
melee = 50
bullet = 50
laser = 50
bomb = 30
fire = 80
acid = 70
/obj/item/shield/Initialize(mapload)
. = ..()
AddElement(/datum/element/disarm_attack)
AddElement(/datum/element/cuffable_item)
/obj/item/shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE)
var/effective_block_chance = final_block_chance
if(transparent && (hitby.pass_flags & PASSGLASS))
return FALSE
if(attack_type == THROWN_PROJECTILE_ATTACK)
effective_block_chance += 30
if(attack_type == LEAP_ATTACK)
effective_block_chance = 100
if(attack_type == OVERWHELMING_ATTACK)
effective_block_chance = 0
final_block_chance = clamp(effective_block_chance, 0, 100)
. = ..()
if(.)
on_shield_block(owner, hitby, attack_text, damage, attack_type, damage_type)
/obj/item/shield/examine(mob/user)
. = ..()
var/healthpercent = round((atom_integrity/max_integrity) * 100, 1)
switch(healthpercent)
if(50 to 99)
. += span_info("It looks slightly damaged.")
if(25 to 50)
. += span_info("It appears heavily damaged.")
if(0 to 25)
. += span_warning("It's falling apart!")
/obj/item/shield/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking)
. = ..()
if(. & ITEM_INTERACT_ANY_BLOCKER)
return .
if(!istype(tool, /obj/item/melee) || !is_bashable)
return .
if(!COOLDOWN_FINISHED(src, weapon_bash))
return ITEM_INTERACT_BLOCKING
playsound(src, shield_bash_sound, 50, TRUE)
user.manual_emote("bashes [src] with [tool]!")
COOLDOWN_START(src, weapon_bash, WEAPON_BASH_COOLDOWN)
user.Shake(3, 3, 0.5 SECONDS)
return ITEM_INTERACT_SUCCESS
/obj/item/shield/proc/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE)
if(!breakable_by_damage || (damage_type != BRUTE && damage_type != BURN))
return TRUE
var/penetration = 0
var/armor_flag = MELEE
if(isprojectile(hitby))
var/obj/projectile/bang_bang = hitby
armor_flag = bang_bang.armor_flag
penetration = bang_bang.armour_penetration
else if(isitem(hitby))
var/obj/item/weapon = hitby
penetration = weapon.armour_penetration
else if(isanimal(hitby))
var/mob/living/simple_animal/critter = hitby
penetration = critter.armour_penetration
else if(isbasicmob(hitby))
var/mob/living/basic/critter = hitby
penetration = critter.armour_penetration
take_damage(damage, damage_type, armor_flag, armour_penetration = penetration)
/obj/item/shield/atom_destruction(damage_flag)
playsound(src, shield_break_sound, 50)
new shield_break_leftover(get_turf(src))
if(isliving(loc))
loc.balloon_alert(loc, "shield broken!")
return ..()
/obj/item/shield/buckler
name = "wooden buckler"
desc = "A medieval wooden buckler."
icon_state = "buckler"
inhand_icon_state = "buckler"
custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT * 20)
resistance_flags = FLAMMABLE
block_chance = 30
max_integrity = 55
w_class = WEIGHT_CLASS_NORMAL
/obj/item/shield/buckler/moonflower
name = "moonflower buckler"
desc = "A buckler made from a steel-cap reinforced moonflower."
icon_state = "moonflower_buckler"
inhand_icon_state = "moonflower_buckler"
block_chance = 40
max_integrity = 40
w_class = WEIGHT_CLASS_NORMAL
custom_materials = null
/obj/item/shield/kite
name = "kite shield"
desc = "Protect your internal organs with this almond shaped shield."
icon_state = "kite"
inhand_icon_state = "kite"
custom_materials = list(/datum/material/wood = SHEET_MATERIAL_AMOUNT * 15)
shield_break_sound = 'sound/effects/grillehit.ogg'
max_integrity = 60
/obj/item/shield/roman
name = "\improper Roman shield"
desc = "Bears an inscription on the inside: <i>\"Romanes venio domus\"</i>."
icon_state = "roman_shield"
inhand_icon_state = "roman_shield"
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 4.25)
max_integrity = 65
shield_break_sound = 'sound/effects/grillehit.ogg'
shield_break_leftover = /obj/item/stack/sheet/iron
/obj/item/shield/roman/fake
desc = "Bears an inscription on the inside: <i>\"Romanes venio domus\"</i>. It appears to be a bit flimsy."
block_chance = 0
armor_type = /datum/armor/none
max_integrity = 30
/datum/armor/item_shield/riot
melee = 80
bullet = 20
laser = 20
/obj/item/shield/riot
name = "riot shield"
desc = "A shield adept at blocking blunt objects from connecting with the torso of the shield wielder, less so bullets and laser beams."
icon_state = "riot"
inhand_icon_state = "riot"
custom_materials = list(/datum/material/glass= SHEET_MATERIAL_AMOUNT * 4.05, /datum/material/iron = SHEET_MATERIAL_AMOUNT * 2.8)
transparent = TRUE
max_integrity = 75
shield_break_sound = 'sound/effects/glass/glassbr3.ogg'
shield_break_leftover = /obj/item/shard
armor_type = /datum/armor/item_shield/riot
pickup_sound = 'sound/items/handling/shield/plastic_shield_pick_up.ogg'
drop_sound = 'sound/items/handling/shield/plastic_shield_drop.ogg'
/obj/item/shield/riot/Initialize(mapload)
. = ..()
var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/strobeshield)
AddElement(
/datum/element/slapcrafting,\
slapcraft_recipes = slapcraft_recipe_list,\
)
/obj/item/shield/riot/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
if(!istype(tool, /obj/item/stack/sheet/mineral/titanium))
return NONE
if (atom_integrity >= max_integrity)
to_chat(user, span_warning("[src] is already in perfect condition."))
return ITEM_INTERACT_BLOCKING
var/obj/item/stack/sheet/mineral/titanium/titanium_sheet = tool
titanium_sheet.use(1)
atom_integrity = max_integrity
to_chat(user, span_notice("You repair [src] with [titanium_sheet]."))
return ITEM_INTERACT_SUCCESS
/obj/item/shield/riot/flash
name = "strobe shield"
desc = "A shield with a built in, high intensity light capable of blinding and disorienting suspects. Takes regular handheld flashes as bulbs."
icon_state = "flashshield"
inhand_icon_state = "flashshield"
var/obj/item/assembly/flash/handheld/embedded_flash = /obj/item/assembly/flash/handheld
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 5.55, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 4.8)
/obj/item/shield/riot/flash/Initialize(mapload)
. = ..()
AddElement(/datum/element/update_icon_updates_onmob)
if(embedded_flash)
embedded_flash = new(src)
embedded_flash.set_light_flags(embedded_flash.light_flags | LIGHT_ATTACHED)
update_appearance(UPDATE_ICON)
/obj/item/shield/riot/flash/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
if(istype(arrived, /obj/item/assembly/flash/handheld))
embedded_flash = arrived
embedded_flash.set_light_flags(embedded_flash.light_flags | LIGHT_ATTACHED)
update_appearance(UPDATE_ICON)
return ..()
/obj/item/shield/riot/flash/Exited(atom/movable/gone, direction)
if(gone == embedded_flash)
embedded_flash.set_light_flags(embedded_flash.light_flags & ~LIGHT_ATTACHED)
embedded_flash = null
update_appearance(UPDATE_ICON)
return ..()
/obj/item/shield/riot/flash/vv_edit_var(vname, vval)
. = ..()
if(vname == NAMEOF(src, embedded_flash))
update_appearance(UPDATE_ICON)
/obj/item/shield/riot/flash/Destroy(force)
QDEL_NULL(embedded_flash)
return ..()
/obj/item/shield/riot/flash/attack(mob/living/target_mob, mob/living/user)
if(user.combat_mode)
return ..()
flash_away(user, target_mob)
/obj/item/shield/riot/flash/attack_self(mob/living/carbon/user)
flash_away(user)
/obj/item/shield/riot/flash/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
. = ..()
if(.)
flash_away(owner)
///Handles calls for the actual flash object + plays the flashing animations.
/obj/item/shield/riot/flash/proc/flash_away(mob/owner, mob/target, animation_only)
if(QDELETED(embedded_flash) || (embedded_flash.burnt_out && !animation_only))
return
var/flick = animation_only ? TRUE : (target ? embedded_flash.attack(target, owner) : embedded_flash.AOE_flash(user = owner))
if(!flick && !embedded_flash.burnt_out)
return
flick("flashshield_flash", src)
inhand_icon_state = "flashshield_flash"
owner?.update_held_items()
addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, update_appearance)), 0.5 SECONDS, (TIMER_UNIQUE|TIMER_OVERRIDE)) //.5 second delay so the inhands sprite finishes its anim since inhands don't support flick().
/obj/item/shield/riot/flash/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
if(!istype(tool, /obj/item/assembly/flash/handheld))
return ..()
var/obj/item/assembly/flash/handheld/flash = tool
if(flash.burnt_out)
to_chat(user, span_warning("No sense replacing it with a broken bulb!"))
return ITEM_INTERACT_BLOCKING
to_chat(user, span_notice("You begin to replace the bulb..."))
if(!do_after(user, 2 SECONDS, target = user))
return ITEM_INTERACT_BLOCKING
if(QDELETED(flash) || flash.burnt_out)
return ITEM_INTERACT_BLOCKING
playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
qdel(embedded_flash)
flash.forceMove(src)
return ITEM_INTERACT_SUCCESS
/obj/item/shield/riot/flash/emp_act(severity)
. = ..()
if(QDELETED(embedded_flash) || embedded_flash.burnt_out)
return
embedded_flash.emp_act(severity)
if(embedded_flash.burnt_out) // a little hacky but no good way to check otherwise.
flash_away((ismob(loc) ? loc : null), animation_only = TRUE)
/obj/item/shield/riot/flash/update_icon_state()
if(QDELETED(embedded_flash) || embedded_flash.burnt_out)
icon_state = "riot"
inhand_icon_state = "riot"
else
icon_state = "flashshield"
inhand_icon_state = "flashshield"
return ..()
/obj/item/shield/riot/flash/examine(mob/user)
. = ..()
if (embedded_flash?.burnt_out)
. += span_info("The mounted bulb has burnt out. You can try replacing it with a new <b>flash</b>.")
/obj/item/shield/energy
name = "combat energy shield"
desc = "A hardlight shield capable of reflecting blocked energy projectiles, as well as providing well-rounded defense from most all other attacks."
icon_state = "eshield"
inhand_icon_state = "eshield"
w_class = WEIGHT_CLASS_TINY
attack_verb_continuous = list("shoves", "bashes")
attack_verb_simple = list("shove", "bash")
throw_range = 5
force = 3
throwforce = 3
throw_speed = 3
breakable_by_damage = FALSE
block_sound = 'sound/items/weapons/block_blade.ogg'
is_bashable = FALSE // Gotta wait till it activates y'know
shield_bash_sound = 'sound/effects/energyshieldbash.ogg'
/// Force of the shield when active.
var/active_force = 10
/// Throwforce of the shield when active.
var/active_throwforce = 8
/// Throwspeed of ethe shield when active.
var/active_throw_speed = 2
/// Whether clumsy people can transform this without side effects.
var/can_clumsy_use = FALSE
/// The chance for projectiles to be reflected by the shield
var/reflection_probability = 50
/obj/item/shield/energy/Initialize(mapload)
. = ..()
AddComponent( \
/datum/component/transforming, \
force_on = active_force, \
throwforce_on = active_throwforce, \
throw_speed_on = active_throw_speed, \
hitsound_on = hitsound, \
clumsy_check = !can_clumsy_use, \
)
RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform))
RegisterSignal(src, COMSIG_ITEM_CAN_DISARM_ATTACK, PROC_REF(can_disarm_attack))
/obj/item/shield/energy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE)
if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE))
return FALSE
var/effective_block_chance = final_block_chance
if(attack_type == OVERWHELMING_ATTACK)
effective_block_chance = 0
if(attack_type == PROJECTILE_ATTACK)
var/obj/projectile/our_projectile = hitby
if(our_projectile.reflectable) //We handle this via IsReflect() instead.
effective_block_chance = 0
final_block_chance = clamp(effective_block_chance, 0, 100)
return ..()
/obj/item/shield/energy/IsReflect()
return HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE) && prob(reflection_probability)
/*
* Signal proc for [COMSIG_TRANSFORMING_ON_TRANSFORM].
*/
/obj/item/shield/energy/proc/on_transform(obj/item/source, mob/user, active)
SIGNAL_HANDLER
if(user)
balloon_alert(user, active ? "activated" : "deactivated")
playsound(src, active ? 'sound/items/weapons/saberon.ogg' : 'sound/items/weapons/saberoff.ogg', 35, TRUE)
is_bashable = !is_bashable
return COMPONENT_NO_DEFAULT_MESSAGE
/obj/item/shield/energy/proc/can_disarm_attack(datum/source, mob/living/victim, mob/living/user, send_message = TRUE)
SIGNAL_HANDLER
if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE))
if(send_message)
balloon_alert(user, "activate it first!")
return COMPONENT_BLOCK_ITEM_DISARM_ATTACK
/obj/item/shield/energy/advanced
name = "advanced combat energy shield"
desc = "A hardlight shield capable of reflecting all energy projectiles, as well as providing well-rounded defense from most all other attacks. \
Often employed by Nanotrasen deathsquads."
icon_state = "advanced_eshield"
inhand_icon_state = "advanced_eshield"
reflection_probability = 100 //Guaranteed reflection
/obj/item/shield/riot/tele
name = "telescopic shield"
desc = "An advanced riot shield made of lightweight materials that collapses for easy storage."
icon_state = "teleriot"
inhand_icon_state = "teleriot"
worn_icon_state = "teleriot"
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 2, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 2, /datum/material/silver = SMALL_MATERIAL_AMOUNT * 3, /datum/material/titanium = SMALL_MATERIAL_AMOUNT * 2)
slot_flags = null
force = 3
throwforce = 3
throw_speed = 3
throw_range = 4
w_class = WEIGHT_CLASS_NORMAL
/obj/item/shield/riot/tele/Initialize(mapload)
. = ..()
AddComponent( \
/datum/component/transforming, \
force_on = 8, \
throwforce_on = 5, \
throw_speed_on = 2, \
hitsound_on = hitsound, \
w_class_on = WEIGHT_CLASS_BULKY, \
attack_verb_continuous_on = list("smacks", "strikes", "cracks", "beats"), \
attack_verb_simple_on = list("smack", "strike", "crack", "beat"), \
)
RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform))
RegisterSignal(src, COMSIG_ITEM_CAN_DISARM_ATTACK, PROC_REF(can_disarm_attack))
/obj/item/shield/riot/tele/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE)
if(HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE))
return ..()
return FALSE
/**
* Signal proc for [COMSIG_TRANSFORMING_ON_TRANSFORM].
*
* Allows it to be placed on back slot when active.
*/
/obj/item/shield/riot/tele/proc/on_transform(obj/item/source, mob/user, active)
SIGNAL_HANDLER
slot_flags = active ? ITEM_SLOT_BACK : null
if(user)
balloon_alert(user, active ? "extended" : "collapsed")
playsound(src, 'sound/items/weapons/batonextend.ogg', 50, TRUE)
return COMPONENT_NO_DEFAULT_MESSAGE
/obj/item/shield/riot/tele/proc/can_disarm_attack(datum/source, mob/living/victim, mob/living/user, send_message = TRUE)
SIGNAL_HANDLER
if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE))
if(send_message)
balloon_alert(user, "extend it first!")
return COMPONENT_BLOCK_ITEM_DISARM_ATTACK
/datum/armor/item_shield/ballistic
melee = 30
bullet = 85
bomb = 10
laser = 80
/obj/item/shield/ballistic
name = "ballistic shield"
desc = "A heavy shield designed for blocking projectiles, weaker to melee."
icon_state = "ballistic"
inhand_icon_state = "ballistic"
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 2, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 2, /datum/material/titanium =SHEET_MATERIAL_AMOUNT)
max_integrity = 75
shield_break_leftover = /obj/item/stack/rods/ten
armor_type = /datum/armor/item_shield/ballistic
/obj/item/shield/ballistic/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
if(!istype(tool, /obj/item/stack/sheet/mineral/titanium))
return NONE
if (atom_integrity >= max_integrity)
to_chat(user, span_warning("[src] is already in perfect condition."))
return ITEM_INTERACT_BLOCKING
var/obj/item/stack/sheet/mineral/titanium/titanium_sheet = tool
titanium_sheet.use(1)
atom_integrity = max_integrity
to_chat(user, span_notice("You repair [src] with [titanium_sheet]."))
return ITEM_INTERACT_SUCCESS
/datum/armor/item_shield/improvised
melee = 40
bullet = 30
laser = 30
/obj/item/shield/improvised
name = "improvised shield"
desc = "A crude shield made out of several sheets of iron taped together, not very durable."
icon_state = "improvised"
inhand_icon_state = "improvised"
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 10, /datum/material/plastic = SMALL_MATERIAL_AMOUNT * 2)
max_integrity = 35
shield_break_leftover = /obj/item/stack/rods/two
armor_type = /datum/armor/item_shield/improvised
block_sound = 'sound/items/trayhit/trayhit2.ogg'
#undef WEAPON_BASH_COOLDOWN