Storage / table interactions at the bottom of the interaction chain (#85512)

Because the wings were in fact made of wax 

## About The Pull Request

Storage goes to the very bottom of the interaction chain, hardcoded in
on `/atom`.
This is not preferred, obviously, but it ends up being a lot less
snowflaking overall.

Tables also go at the very bottom by extending `base_item_interaction`. 

Fixes #83742
Fixes #84434 
Fixes #83982
Fixes #85516
Fixes #84990
Fixes #84890
Closes #85036
Closes #84025 (RMB places it on the table.)
Closes #86616

Other changes:

Refactored pod storage to be less jank. Patches some exploits around it.

## Why It's Good For The Game

Should make a lot more interactions a lot more reliable... hopefully

## Changelog

🆑 Melbert
refactor: Storage and Tables are now a lower priority action, meaning
some uses of items on storage should work... better, now. Here's hoping
at least, report any oddities.
refactor: Note: For an overwhelming majority of items, **combat mode**
will attempt to attack/insert into the target, while **non-combat-mode**
will attempt to use on a target. This means screwdrivering or emagging a
MODsuit must be done on non-combat-mode, as combat mode will simply put
the screwdriver or emag into its storage. Same applies to tables, though
when in doubt, RMB may help (for things which are also weapons, like
mops).
refactor: Refactored escape pod storage, now they actually properly show
as unlocked on red alert and above.
/🆑
This commit is contained in:
MrMelbert
2024-09-12 23:48:19 +02:00
committed by GitHub
parent 76968949d7
commit 8486f2f7e2
65 changed files with 386 additions and 357 deletions
+2
View File
@@ -27,6 +27,8 @@
. += span_notice("The rope looks like you could use it [uses] times before it falls apart.")
/obj/item/climbing_hook/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(HAS_TRAIT(interacting_with, TRAIT_COMBAT_MODE_SKIP_INTERACTION))
return NONE
return ranged_interact_with_atom(interacting_with, user, modifiers)
/obj/item/climbing_hook/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
-3
View File
@@ -161,9 +161,6 @@
return CLEAN_BLOCKED
return ..()
/obj/item/soap/storage_insert_on_interaction(datum/storage, atom/storage_holder, mob/living/user)
return !user.combat_mode // only cleans a storage item if on combat
/*
* Bike Horns
*/
+5 -3
View File
@@ -35,10 +35,12 @@
update_icon_state()
balloon_alert(user, "mode: [desc[mode]]")
/obj/item/door_remote/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
return interact_with_atom(interacting_with, user, modifiers)
/obj/item/door_remote/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(!istype(interacting_with, /obj/machinery/door) && !isturf(interacting_with))
return NONE
return ranged_interact_with_atom(interacting_with, user, modifiers)
/obj/item/door_remote/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
var/obj/machinery/door/door
if (istype(interacting_with, /obj/machinery/door))
+8 -4
View File
@@ -865,7 +865,10 @@
/obj/item/toy/crayon/spraycan/can_use_on(atom/target, mob/user, list/modifiers)
if(iscarbon(target))
return TRUE
if(ismob(target) && (HAS_TRAIT(target, TRAIT_SPRAY_PAINTABLE)))
if(is_capped && HAS_TRAIT(target, TRAIT_COMBAT_MODE_SKIP_INTERACTION))
// specifically don't try to use a capped spraycan on stuff like bags and tables, just place it
return FALSE
if(ismob(target) && HAS_TRAIT(target, TRAIT_SPRAY_PAINTABLE))
return TRUE
if(isobj(target) && !(target.flags_1 & UNPAINTABLE_1))
return TRUE
@@ -967,6 +970,10 @@
/obj/item/toy/crayon/spraycan/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
if(is_capped)
if(!interacting_with.color)
// let's be generous and assume if they're trying to match something with no color, while capped,
// we shouldn't be blocking further interactions
return NONE
balloon_alert(user, "take the cap off first!")
return ITEM_INTERACT_BLOCKING
if(check_empty(user))
@@ -1003,9 +1010,6 @@
update_appearance()
return CLICK_ACTION_SUCCESS
/obj/item/toy/crayon/spraycan/storage_insert_on_interaction(datum/storage, atom/storage_holder, mob/user)
return is_capped
/obj/item/toy/crayon/spraycan/update_icon_state()
icon_state = is_capped ? icon_capped : icon_uncapped
return ..()
+7 -9
View File
@@ -43,17 +43,15 @@
user.visible_message(span_suicide("[user] is trying to upload [user.p_them()]self into [src]! That's not going to work out well!"))
return BRUTELOSS
/obj/item/aicard/pre_attack(atom/target, mob/living/user, params)
. = ..()
if(.)
return
/obj/item/aicard/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(AI)
if(upload_ai(target, user))
return TRUE
if(upload_ai(interacting_with, user))
return ITEM_INTERACT_SUCCESS
else
if(capture_ai(target, user))
return TRUE
if(capture_ai(interacting_with, user))
return ITEM_INTERACT_SUCCESS
return NONE
/// Tries to get an AI from the atom clicked
/obj/item/aicard/proc/capture_ai(atom/from_what, mob/living/user)
@@ -36,26 +36,37 @@
else
to_chat(user, span_warning("You can't use [src] while inside something!"))
/obj/item/chameleon/interact_with_atom(atom/target, mob/living/user, list/modifiers)
if(!check_sprite(target))
return ITEM_INTERACT_BLOCKING
if(active_dummy)//I now present you the blackli(f)st
return ITEM_INTERACT_BLOCKING
if(isturf(target))
return ITEM_INTERACT_BLOCKING
if(ismob(target))
return ITEM_INTERACT_BLOCKING
if(istype(target, /obj/structure/falsewall))
return ITEM_INTERACT_BLOCKING
if(target.alpha != 255)
return ITEM_INTERACT_BLOCKING
if(target.invisibility != 0)
return ITEM_INTERACT_BLOCKING
if(iseffect(target) && !istype(target, /obj/effect/decal)) //be a footprint
return ITEM_INTERACT_BLOCKING
make_copy(target, user)
/obj/item/chameleon/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(!can_copy(interacting_with) || SHOULD_SKIP_INTERACTION(interacting_with, src, user))
return NONE
make_copy(interacting_with, user)
return ITEM_INTERACT_SUCCESS
/obj/item/chameleon/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
if(!can_copy(interacting_with)) // RMB scan works on storage items, LMB scan does not
return NONE
make_copy(interacting_with, user)
return ITEM_INTERACT_SUCCESS
/obj/item/chameleon/proc/can_copy(atom/target)
if(!check_sprite(target))
return FALSE
if(active_dummy)//I now present you the blackli(f)st
return FALSE
if(isturf(target))
return FALSE
if(ismob(target))
return FALSE
if(istype(target, /obj/structure/falsewall))
return FALSE
if(target.alpha != 255)
return FALSE
if(target.invisibility != 0)
return FALSE
if(iseffect(target) && !istype(target, /obj/effect/decal)) //be a footprint
return FALSE
return TRUE
/obj/item/chameleon/proc/make_copy(atom/target, mob/user)
playsound(get_turf(src), 'sound/weapons/flash.ogg', 100, TRUE, -6)
to_chat(user, span_notice("Scanned [target]."))
@@ -21,10 +21,10 @@
/// Checks to make sure the projector isn't busy with making another forcefield.
var/force_proj_busy = FALSE
/obj/item/forcefield_projector/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
return interact_with_atom(interacting_with, user, modifiers)
/obj/item/forcefield_projector/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
return ranged_interact_with_atom(interacting_with, user, modifiers)
/obj/item/forcefield_projector/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(!check_allowed_items(interacting_with, not_inside = TRUE))
return NONE
if(istype(interacting_with, /obj/structure/projected_forcefield))
@@ -67,13 +67,13 @@
update_appearance(UPDATE_ICON)
balloon_alert(user, "switch [scanning ? "on" : "off"]")
/obj/item/geiger_counter/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
return interact_with_atom(interacting_with, user, modifiers)
/obj/item/geiger_counter/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if (user.combat_mode)
if(SHOULD_SKIP_INTERACTION(interacting_with, src, user))
return NONE
if (!CAN_IRRADIATE(interacting_with))
return ranged_interact_with_atom(interacting_with, user, modifiers)
/obj/item/geiger_counter/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(!CAN_IRRADIATE(interacting_with))
return NONE
user.visible_message(span_notice("[user] scans [interacting_with] with [src]."), span_notice("You scan [interacting_with]'s radiation levels with [src]..."))
@@ -183,12 +183,14 @@
and the wide margin between it and the focus lens could probably house <b>a crystal</b> of some sort.</i>"
/obj/item/laser_pointer/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
return interact_with_atom(interacting_with, user, modifiers)
/obj/item/laser_pointer/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
laser_act(interacting_with, user, modifiers)
return ITEM_INTERACT_BLOCKING
/obj/item/laser_pointer/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(HAS_TRAIT(interacting_with, TRAIT_COMBAT_MODE_SKIP_INTERACTION))
return NONE
return ranged_interact_with_atom(interacting_with, user, modifiers)
///Handles shining the clicked atom,
/obj/item/laser_pointer/proc/laser_act(atom/target, mob/living/user, list/modifiers)
if(isnull(diode))
@@ -361,15 +361,6 @@ effective or pretty fucking useless.
new /obj/item/analyzer(src)
new /obj/item/wirecutters(src)
/obj/item/storage/toolbox/emergency/turret/storage_insert_on_interacted_with(datum/storage, obj/item/inserted, mob/living/user)
if(!istype(inserted, /obj/item/wrench/combat))
return TRUE
if(!user.combat_mode)
return TRUE
if(!inserted.toolspeed)
return TRUE
return FALSE
/obj/item/storage/toolbox/emergency/turret/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
if(!istype(tool, /obj/item/wrench/combat))
return NONE
+4 -4
View File
@@ -137,16 +137,16 @@
. = ..()
type_blacklist = list(typesof(/obj/machinery/door/airlock) + typesof(/obj/machinery/door/window/) + typesof(/obj/machinery/door/firedoor) - typesof(/obj/machinery/door/airlock/tram)) //list of all typepaths that require a specialized emag to hack.
/obj/item/card/emag/storage_insert_on_interaction(datum/storage, atom/storage_holder, mob/living/user)
return !user.combat_mode
/obj/item/card/emag/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(SHOULD_SKIP_INTERACTION(interacting_with, src, user))
return NONE // lets us put things in bags without trying to emag them
if(!can_emag(interacting_with, user))
return ITEM_INTERACT_BLOCKING
log_combat(user, interacting_with, "attempted to emag")
if(interacting_with.emag_act(user, src))
SSblackbox.record_feedback("tally", "atom_emagged", 1, interacting_with.type)
return ITEM_INTERACT_SUCCESS
return ITEM_INTERACT_SUCCESS
return NONE // In a perfect world this would be blocking, but this is not a perfect world
/obj/item/card/emag/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
return prox_check ? NONE : interact_with_atom(interacting_with, user)
+6 -4
View File
@@ -207,13 +207,15 @@
else
return FALSE
/obj/item/extinguisher/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
return interact_with_atom(interacting_with, user, modifiers)
/obj/item/extinguisher/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if (interacting_with.loc == user)
if(interacting_with.loc == user)
return NONE
// Always skip interaction if it's a bag or table (that's not on fire)
if(!(interacting_with.resistance_flags & ON_FIRE) && HAS_TRAIT(interacting_with, TRAIT_COMBAT_MODE_SKIP_INTERACTION))
return NONE
return ranged_interact_with_atom(interacting_with, user, modifiers)
/obj/item/extinguisher/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(refilling)
refilling = FALSE
return NONE
+5 -3
View File
@@ -85,10 +85,12 @@
remove_old_machine()
return CLICK_ACTION_SUCCESS
/obj/item/machine_remote/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
return interact_with_atom(interacting_with, user, modifiers)
/obj/item/machine_remote/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(HAS_TRAIT(interacting_with, TRAIT_COMBAT_MODE_SKIP_INTERACTION) || (!ismachinery(interacting_with) && !isbot(interacting_with)))
return NONE
return ranged_interact_with_atom(interacting_with, user, modifiers)
/obj/item/machine_remote/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(!COOLDOWN_FINISHED(src, timeout_time))
playsound(src, 'sound/machines/synth_no.ogg', 30 , TRUE)
say("Remote control disabled temporarily. Please try again soon.")
+10 -13
View File
@@ -207,15 +207,15 @@
* * [mob][user]- the user building this structure
*/
/obj/item/construction/rcd/proc/rcd_create(atom/target, mob/user)
var/list/rcd_results = target.rcd_vals(user, src) // does this atom allow for rcd actions?
if(!rcd_results) // nope
return NONE
//straight up can't touch this
if(mode == RCD_DECONSTRUCT && (target.resistance_flags & INDESTRUCTIBLE))
balloon_alert(user, "too durable!")
return
return ITEM_INTERACT_BLOCKING
//does this atom allow for rcd actions?
var/list/rcd_results = target.rcd_vals(user, src)
if(!rcd_results)
return FALSE
rcd_results["[RCD_DESIGN_MODE]"] = mode
rcd_results["[RCD_DESIGN_PATH]"] = rcd_design_path
@@ -237,6 +237,7 @@
log_tool("[key_name(user)] used [src] to [rcd_results["[RCD_DESIGN_MODE]"] != RCD_DECONSTRUCT ? "construct [initial(design_path.name)]([design_path])" : "deconstruct [target_name]([target_path])"] at [location]")
current_active_effects -= 1
return ITEM_INTERACT_SUCCESS
/**
* Internal proc which creates the rcd effects & creates the structure
@@ -401,29 +402,25 @@
return .
mode = construction_mode
rcd_create(interacting_with, user)
return ITEM_INTERACT_SUCCESS
return rcd_create(interacting_with, user)
/obj/item/construction/rcd/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(!ranged || !range_check(interacting_with, user))
return ITEM_INTERACT_BLOCKING
mode = construction_mode
rcd_create(interacting_with, user)
return ITEM_INTERACT_SUCCESS
return rcd_create(interacting_with, user)
/obj/item/construction/rcd/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
mode = RCD_DECONSTRUCT
rcd_create(interacting_with, user)
return ITEM_INTERACT_SUCCESS
return rcd_create(interacting_with, user)
/obj/item/construction/rcd/ranged_interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
if(!ranged || !range_check(interacting_with, user))
return ITEM_INTERACT_BLOCKING
mode = RCD_DECONSTRUCT
rcd_create(interacting_with, user)
return ITEM_INTERACT_SUCCESS
return rcd_create(interacting_with, user)
/obj/item/construction/rcd/handle_openspace_click(turf/target, mob/user, list/modifiers)
interact_with_atom(target, user, modifiers)
+5 -3
View File
@@ -343,10 +343,12 @@
var/staffcooldown = 0
var/staffwait = 30
/obj/item/godstaff/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
return interact_with_atom(interacting_with, user, modifiers)
/obj/item/godstaff/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(SHOULD_SKIP_INTERACTION(interacting_with, src, user))
return NONE
return ranged_interact_with_atom(interacting_with, user, modifiers)
/obj/item/godstaff/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(staffcooldown + staffwait > world.time)
return ITEM_INTERACT_BLOCKING
+2 -7
View File
@@ -102,13 +102,6 @@
/obj/item/delivery/can_be_package_wrapped()
return FALSE
/obj/item/stack/package_wrap/storage_insert_on_interaction(datum/storage, atom/storage_holder, mob/user)
if(isitem(storage_holder))
// Don't insert if the target can be wrapped
var/obj/item/item = storage_holder
return !item.can_be_package_wrapped()
return TRUE
/obj/item/stack/package_wrap/interact_with_atom(obj/interacting_with, mob/living/user, list/modifiers)
if(!isobj(interacting_with))
return NONE
@@ -118,6 +111,8 @@
if(isitem(interacting_with))
var/obj/item/item = interacting_with
if(!item.can_be_package_wrapped())
if(SHOULD_SKIP_INTERACTION(interacting_with, src, user))
return NONE // put it in the bag instead of yelling
balloon_alert(user, "can't be wrapped!")
return ITEM_INTERACT_BLOCKING
if(user.is_holding(item))
+5 -5
View File
@@ -155,13 +155,13 @@
UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED)
listeningTo = null
/obj/item/storage/bag/ore/storage_insert_on_interacted_with(datum/storage, obj/item/inserted, mob/living/user)
if(istype(inserted, /obj/item/boulder))
to_chat(user, span_warning("You can't fit [inserted] into [src]. \
/obj/item/storage/bag/ore/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
if(istype(tool, /obj/item/boulder))
to_chat(user, span_warning("You can't fit [tool] into [src]. \
Perhaps you should break it down first, or find an ore box."))
return FALSE
return ITEM_INTERACT_BLOCKING
return TRUE
return NONE
/obj/item/storage/bag/ore/proc/pickup_ores(mob/living/user)
SIGNAL_HANDLER
+1 -1
View File
@@ -16,4 +16,4 @@
else
var/message = interacting_with.reagents.generate_taste_message(user, taste_sensitivity)
to_chat(user, span_notice("[src] tastes <i>[message]</i> in [interacting_with]."))
return ITEM_INTERACT_SUCCESS
return user.combat_mode ? NONE : ITEM_INTERACT_SUCCESS
@@ -888,7 +888,7 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets)
user.log_message("[welded ? "welded":"unwelded"] closet [src] with [weapon]", LOG_GAME)
update_appearance()
else if(!user.combat_mode)
else if(!user.combat_mode || (weapon.item_flags & NOBLUDGEON))
var/item_is_id = weapon.GetID()
if(!item_is_id)
return FALSE
@@ -259,11 +259,11 @@
M.attacked_by(src, user)
add_fingerprint(user)
/obj/item/gun_control/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
/obj/item/gun_control/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
var/obj/machinery/deployable_turret/E = user.buckled
E.calculated_projectile_vars = calculate_projectile_angle_and_pixel_offsets(user, interacting_with, modifiers)
E.direction_track(user, interacting_with)
E.checkfire(interacting_with, user)
/obj/item/gun_control/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
return interact_with_atom(interacting_with, user, modifiers)
/obj/item/gun_control/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
return ranged_interact_with_atom(interacting_with, user, modifiers)
+16 -22
View File
@@ -55,6 +55,8 @@
AddElement(/datum/element/give_turf_traits, give_turf_traits)
register_context()
ADD_TRAIT(src, TRAIT_COMBAT_MODE_SKIP_INTERACTION, INNATE_TRAIT)
///Adds the element used to make the object climbable, and also the one that shift the mob buckled to it up.
/obj/structure/table/proc/make_climbable()
AddElement(/datum/element/climbable)
@@ -224,36 +226,24 @@
deconstruct(TRUE)
return ITEM_INTERACT_SUCCESS
/obj/structure/table/item_interaction_secondary(mob/living/user, obj/item/tool, list/modifiers)
if(istype(tool, /obj/item/construction/rcd))
return NONE
// This extends base item interaction because tables default to blocking 99% of interactions
/obj/structure/table/base_item_interaction(mob/living/user, obj/item/tool, list/modifiers)
. = ..()
if(.)
return .
var/deck_act_value = NONE
if(istype(tool, /obj/item/toy/cards/deck))
deck_act_value = deck_act(user, tool, modifiers, TRUE)
// Continue to placing if we don't do anything else
if(deck_act_value != NONE)
return deck_act_value
if(!user.combat_mode)
return table_place_act(user, tool, modifiers)
return NONE
/obj/structure/table/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
. = NONE
. = deck_act(user, tool, modifiers, !!LAZYACCESS(modifiers, RIGHT_CLICK))
if(istype(tool, /obj/item/storage/bag/tray))
. = tray_act(user, tool)
else if(istype(tool, /obj/item/toy/cards/deck))
. = deck_act(user, tool, modifiers, FALSE)
else if(istype(tool, /obj/item/riding_offhand))
. = riding_offhand_act(user, tool)
// Continue to placing if we don't do anything else
if(. != NONE)
if(.)
return .
if(!user.combat_mode)
if(!user.combat_mode || (tool.item_flags & NOBLUDGEON))
return table_place_act(user, tool, modifiers)
return NONE
@@ -865,6 +855,7 @@
AddElement(/datum/element/climbable)
AddElement(/datum/element/elevation, pixel_shift = 12)
register_context()
ADD_TRAIT(src, TRAIT_COMBAT_MODE_SKIP_INTERACTION, INNATE_TRAIT)
/obj/structure/rack/add_context(atom/source, list/context, obj/item/held_item, mob/living/user)
if(isnull(held_item))
@@ -892,8 +883,11 @@
deconstruct(TRUE)
return ITEM_INTERACT_SUCCESS
/obj/structure/rack/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
if((tool.item_flags & ABSTRACT) || user.combat_mode)
/obj/structure/rack/base_item_interaction(mob/living/user, obj/item/tool, list/modifiers)
. = ..()
if(.)
return .
if((tool.item_flags & ABSTRACT) || (user.combat_mode && !(tool.item_flags & NOBLUDGEON)))
return NONE
if(user.transferItemToLoc(tool, drop_location(), silent = FALSE))
return ITEM_INTERACT_SUCCESS
@@ -71,7 +71,7 @@
oxygentanks++
else
full = TRUE
else if(!user.combat_mode)
else if(!user.combat_mode || (I.item_flags & NOBLUDGEON))
balloon_alert(user, "can't insert!")
return
else
@@ -204,7 +204,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sink, (-14))
if(O.item_flags & ABSTRACT) //Abstract items like grabs won't wash. No-drop items will though because it's still technically an item in your hand.
return
if(!user.combat_mode)
if(!user.combat_mode || (O.item_flags & NOBLUDGEON))
to_chat(user, span_notice("You start washing [O]..."))
busy = TRUE
if(!do_after(user, 4 SECONDS, target = src))
@@ -114,7 +114,7 @@
attacking_item.use(1)
return
if(!user.combat_mode)
if(!user.combat_mode || (attacking_item.item_flags & NOBLUDGEON))
to_chat(user, span_notice("You start washing [attacking_item]..."))
busy = TRUE
if(!do_after(user, 4 SECONDS, target = src))