Files
Bubberstation/code/game/objects/items.dm
MrPerson ff3f84ab81 Replaces /image with /mutable_appearance, where appropriate (#26518)
In cases where you're creating an image to use as an overlay, it makes more sense to use a mutable_appearance if you can. The image will create a static appearance for not just the image but also each intermediate step if you change vars along the way. The mutable appearance avoids this unnecessary and expensive process. The only situation that requires an image instead of a mutable_appearance is if the overlay is supposed to be directional. MA's ignore direction while images don't. I dunno why, probably another BYOND-ism.

I added a convenience function, mutable_appearance(), designed to emulate image(). Also went ahead and set the default plane of /mutable_appearance to FLOAT_PLANE because it's fucking 0 by default.

Several overlays that were image() calls were changed to just text strings when I could. overlays += "string" has the same result as overlays += image(icon, "string") and saves a proc call.
2017-04-25 12:15:16 +02:00

630 lines
23 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.
/obj/item
name = "item"
icon = 'icons/obj/items.dmi'
var/item_state = null
var/lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
var/righthand_file = 'icons/mob/inhands/items_righthand.dmi'
//Dimensions of the icon file used when this item is worn, eg: hats.dmi
//eg: 32x32 sprite, 64x64 sprite, etc.
//allows inhands/worn sprites to be of any size, but still centered on a mob properly
var/worn_x_dimension = 32
var/worn_y_dimension = 32
//Same as above but for inhands, uses the lefthand_ and righthand_ file vars
var/inhand_x_dimension = 32
var/inhand_y_dimension = 32
//Not on /clothing because for some reason any /obj/item can technically be "worn" with enough fuckery.
var/icon/alternate_worn_icon = null//If this is set, update_icons() will find on mob (WORN, NOT INHANDS) states in this file instead, primary use: badminnery/events
var/alternate_worn_layer = null//If this is set, update_icons() will force the on mob state (WORN, NOT INHANDS) onto this layer, instead of it's default
obj_integrity = 200
max_integrity = 200
var/hitsound = null
var/usesound = null
var/throwhitsound = null
var/w_class = WEIGHT_CLASS_NORMAL
var/slot_flags = 0 //This is used to determine on which slots an item can fit.
pass_flags = PASSTABLE
pressure_resistance = 4
var/obj/item/master = null
var/heat_protection = 0 //flags which determine which body parts are protected from heat. Use the HEAD, CHEST, GROIN, etc. flags. See setup.dm
var/cold_protection = 0 //flags which determine which body parts are protected from cold. Use the HEAD, CHEST, GROIN, etc. flags. See setup.dm
var/max_heat_protection_temperature //Set this variable to determine up to which temperature (IN KELVIN) the item protects against heat damage. Keep at null to disable protection. Only protects areas set by heat_protection flags
var/min_cold_protection_temperature //Set this variable to determine down to which temperature (IN KELVIN) the item protects against cold damage. 0 is NOT an acceptable number due to if(varname) tests!! Keep at null to disable protection. Only protects areas set by cold_protection flags
var/list/actions //list of /datum/action's that this item has.
var/list/actions_types //list of paths of action datums to give to the item on New().
//Since any item can now be a piece of clothing, this has to be put here so all items share it.
var/flags_inv //This flag is used to determine when items in someone's inventory cover others. IE helmets making it so you can't see glasses, etc.
var/item_color = null //this needs deprecating, soonish
var/body_parts_covered = 0 //see setup.dm for appropriate bit flags
//var/heat_transfer_coefficient = 1 //0 prevents all transfers, 1 is invisible
var/gas_transfer_coefficient = 1 // for leaking gas from turf to mask and vice-versa (for masks right now, but at some point, i'd like to include space helmets)
var/permeability_coefficient = 1 // for chemicals/diseases
var/siemens_coefficient = 1 // for electrical admittance/conductance (electrocution checks and shit)
var/slowdown = 0 // How much clothing is slowing you down. Negative values speeds you up
var/armour_penetration = 0 //percentage of armour effectiveness to remove
var/list/allowed = null //suit storage stuff.
var/obj/item/device/uplink/hidden_uplink = null
var/strip_delay = 40
var/put_on_delay = 20
var/breakouttime = 0
var/list/materials
var/origin_tech = null //Used by R&D to determine what research bonuses it grants.
var/needs_permit = 0 //Used by security bots to determine if this item is safe for public use.
var/list/attack_verb //Used in attackby() to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]"
var/list/species_exception = null // list() of species types, if a species cannot put items in a certain slot, but species type is in list, it will be able to wear that item
var/suittoggled = 0
var/hooded = 0
var/mob/thrownby = null
/obj/item/mouse_drag_pointer = MOUSE_ACTIVE_POINTER //the icon to indicate this object is being dragged
//So items can have custom embedd values
//Because customisation is king
var/embed_chance = EMBED_CHANCE
var/embedded_fall_chance = EMBEDDED_ITEM_FALLOUT
var/embedded_pain_chance = EMBEDDED_PAIN_CHANCE
var/embedded_pain_multiplier = EMBEDDED_PAIN_MULTIPLIER //The coefficient of multiplication for the damage this item does while embedded (this*w_class)
var/embedded_fall_pain_multiplier = EMBEDDED_FALL_PAIN_MULTIPLIER //The coefficient of multiplication for the damage this item does when falling out of a limb (this*w_class)
var/embedded_impact_pain_multiplier = EMBEDDED_IMPACT_PAIN_MULTIPLIER //The coefficient of multiplication for the damage this item does when first embedded (this*w_class)
var/embedded_unsafe_removal_pain_multiplier = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER //The coefficient of multiplication for the damage removing this without surgery causes (this*w_class)
var/embedded_unsafe_removal_time = EMBEDDED_UNSAFE_REMOVAL_TIME //A time in ticks, multiplied by the w_class.
var/flags_cover = 0 //for flags such as GLASSESCOVERSEYES
var/heat = 0
var/sharpness = IS_BLUNT
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
var/reach = 1 //In tiles, how far this weapon can reach; 1 for adjacent, which is default
//The list of slots by priority. equip_to_appropriate_slot() uses this list. Doesn't matter if a mob type doesn't have a slot.
var/list/slot_equipment_priority = null // for default list, see /mob/proc/equip_to_appropriate_slot()
// Needs to be in /obj/item because corgis can wear a lot of
// non-clothing items
var/datum/dog_fashion/dog_fashion = null
var/datum/rpg_loot/rpg_loot = null
/obj/item/Initialize()
if (!materials)
materials = list()
. = ..()
for(var/path in actions_types)
new path(src)
actions_types = null
if(GLOB.rpg_loot_items)
rpg_loot = new(src)
/obj/item/Destroy()
flags &= ~DROPDEL //prevent reqdels
if(ismob(loc))
var/mob/m = loc
m.temporarilyRemoveItemFromInventory(src, TRUE)
for(var/X in actions)
qdel(X)
QDEL_NULL(rpg_loot)
return ..()
/obj/item/device
icon = 'icons/obj/device.dmi'
/obj/item/proc/check_allowed_items(atom/target, not_inside, target_self)
if(((src in target) && !target_self) || (!isturf(target.loc) && !isturf(target) && not_inside))
return 0
else
return 1
/obj/item/blob_act(obj/structure/blob/B)
if(B && B.loc == loc)
qdel(src)
//user: The mob that is suiciding
//damagetype: The type of damage the item will inflict on the user
//BRUTELOSS = 1
//FIRELOSS = 2
//TOXLOSS = 4
//OXYLOSS = 8
//Output a creative message and then return the damagetype done
/obj/item/proc/suicide_act(mob/user)
return
/obj/item/verb/move_to_top()
set name = "Move To Top"
set category = "Object"
set src in oview(1)
if(!isturf(loc) || usr.stat || usr.restrained() || !usr.canmove)
return
var/turf/T = src.loc
src.loc = null
src.loc = T
/obj/item/examine(mob/user) //This might be spammy. Remove?
..()
var/pronoun
if(src.gender == PLURAL)
pronoun = "They are"
else
pronoun = "It is"
var/size = weightclass2text(src.w_class)
to_chat(user, "[pronoun] a [size] item." )
if(user.research_scanner) //Mob has a research scanner active.
var/msg = "*--------* <BR>"
if(origin_tech)
msg += "<span class='notice'>Testing potentials:</span><BR>"
var/list/techlvls = params2list(origin_tech)
for(var/T in techlvls) //This needs to use the better names.
msg += "Tech: [CallTechName(T)] | magnitude: [techlvls[T]] <BR>"
else
msg += "<span class='danger'>No tech origins detected.</span><BR>"
if(materials.len)
msg += "<span class='notice'>Extractable materials:<BR>"
for(var/mat in materials)
msg += "[CallMaterialName(mat)]<BR>" //Capitize first word, remove the "$"
else
msg += "<span class='danger'>No extractable materials detected.</span><BR>"
msg += "*--------*"
to_chat(user, msg)
/obj/item/attack_self(mob/user)
interact(user)
/obj/item/interact(mob/user)
add_fingerprint(user)
if(hidden_uplink && hidden_uplink.active)
hidden_uplink.interact(user)
return 1
ui_interact(user)
/obj/item/ui_act(action, params)
add_fingerprint(usr)
return ..()
/obj/item/attack_hand(mob/user)
if(!user)
return
if(anchored)
return
if(resistance_flags & ON_FIRE)
var/mob/living/carbon/C = user
if(istype(C))
if(C.gloves && (C.gloves.max_heat_protection_temperature > 360))
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
else
extinguish()
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(istype(loc, /obj/item/weapon/storage))
//If the item is in a storage item, take it out
var/obj/item/weapon/storage/S = loc
S.remove_from_storage(src, user.loc)
if(throwing)
throwing.finalize(FALSE)
if(loc == user)
if(!user.dropItemToGround(src))
return
pickup(user)
add_fingerprint(user)
if(!user.put_in_active_hand(src))
dropped(user)
/obj/item/attack_paw(mob/user)
if(!user)
return
if(anchored)
return
if(istype(loc, /obj/item/weapon/storage))
var/obj/item/weapon/storage/S = loc
S.remove_from_storage(src, user.loc)
if(throwing)
throwing.finalize(FALSE)
if(loc == user)
if(!user.dropItemToGround(src))
return
pickup(user)
add_fingerprint(user)
if(!user.put_in_active_hand(src))
dropped(user)
/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/weapon/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()
// Due to storage type consolidation this should get used more now.
// I have cleaned it up a little, but it could probably use more. -Sayu
// The lack of ..() is intentional, do not add one
/obj/item/attackby(obj/item/weapon/W, mob/user, params)
if(istype(W,/obj/item/weapon/storage))
var/obj/item/weapon/storage/S = W
if(S.use_to_pickup)
if(S.collection_mode) //Mode is set to collect multiple items on a tile and we clicked on a valid one.
if(isturf(loc))
var/list/rejections = list()
var/list/things = loc.contents.Copy()
if (S.collection_mode == 2)
things = typecache_filter_list(things, typecacheof(type))
var/len = things.len
if(!len)
to_chat(user, "<span class='notice'>You failed to pick up anything with [S].</span>")
return
var/datum/progressbar/progress = new(user, len, loc)
while (do_after(user, 10, TRUE, S, FALSE, CALLBACK(src, .proc/handle_mass_pickup, S, things, loc, rejections, progress)))
sleep(1)
qdel(progress)
to_chat(user, "<span class='notice'>You put everything you could [S.preposition] [S].</span>")
else if(S.can_be_inserted(src))
S.handle_item_insertion(src)
/obj/item/proc/handle_mass_pickup(obj/item/weapon/storage/S, list/things, atom/thing_loc, list/rejections, datum/progressbar/progress)
for(var/obj/item/I in things)
things -= I
if(I.loc != thing_loc)
continue
if(I.type in rejections) // To limit bag spamming: any given type only complains once
continue
if(!S.can_be_inserted(I, stop_messages = TRUE)) // Note can_be_inserted still makes noise when the answer is no
if(S.contents.len >= S.storage_slots)
break
rejections += I.type // therefore full bags are still a little spammy
continue
S.handle_item_insertion(I, TRUE) //The 1 stops the "You put the [src] into [S]" insertion message from being displayed.
if (TICK_CHECK)
progress.update(progress.goal - things.len)
return TRUE
progress.update(progress.goal - things.len)
return FALSE
// afterattack() and attack() prototypes moved to _onclick/item_attack.dm for consistency
/obj/item/proc/hit_reaction(mob/living/carbon/human/owner, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
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)
for(var/X in actions)
var/datum/action/A = X
A.Remove(user)
if(DROPDEL & flags)
qdel(src)
// called just as an item is picked up (loc is not yet changed)
/obj/item/proc/pickup(mob/user)
return
// called when this item is removed from a storage item, which is passed on as S. The loc variable is already set to the new destination before this is called.
/obj/item/proc/on_exit_storage(obj/item/weapon/storage/S)
return
// called when this item is added into a storage item, which is passed on as S. The loc variable is already set to the storage item.
/obj/item/proc/on_enter_storage(obj/item/weapon/storage/S)
return
// 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 //NOPE, for example, if you put a helmet in slot_head, it is NOT in user's head variable yet, how stupid.
// user is mob that equipped it
// slot uses the slot_X defines found in setup.dm
// for items that can be placed in multiple slots
// note this isn't called during the initial dressing of a player
/obj/item/proc/equipped(mob/user, slot)
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)
//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 == slot_in_backpack || slot == slot_legcuffed) //these aren't true slots, so avoid granting actions there
return FALSE
return TRUE
//the mob M is attempting to equip this item into the slot passed through as 'slot'. Return 1 if it can do this and 0 if it can't.
//if this is being done by a mob other than M, it will include the mob equipper, who is trying to equip the item to mob M. equipper will be null otherwise.
//If you are making custom procs but would like to retain partial or complete functionality of this one, include a 'return ..()' to where you want this to happen.
//Set disable_warning to 1 if you wish it to not give you outputs.
/obj/item/proc/mob_can_equip(mob/M, mob/equipper, slot, disable_warning = 0)
if(!M)
return 0
return M.can_equip(src, slot, disable_warning)
/obj/item/verb/verb_pickup()
set src in oview(1)
set category = "Object"
set name = "Pick up"
if(usr.incapacitated() || !Adjacent(usr) || usr.lying)
return
if(usr.get_active_held_item() == null) // Let me know if this has any problems -Yota
usr.UnarmedAttack(src)
//This proc is executed when someone clicks the on-screen UI button.
//The default action is attack_self().
//Checks before we get to here are: mob is alive, mob is not restrained, paralyzed, asleep, resting, laying, item is on the mob.
/obj/item/proc/ui_action_click(mob/user, actiontype)
attack_self(user)
/obj/item/proc/IsReflect(var/def_zone) //This proc determines if and at what% an object will reflect energy projectiles if it's in l_hand,r_hand or wear_suit
return 0
/obj/item/proc/eyestab(mob/living/carbon/M, mob/living/carbon/user)
var/is_human_victim = 0
var/obj/item/bodypart/affecting = M.get_bodypart("head")
if(ishuman(M))
if(!affecting) //no head!
return
is_human_victim = 1
var/mob/living/carbon/human/H = M
if((H.head && H.head.flags_cover & HEADCOVERSEYES) || \
(H.wear_mask && H.wear_mask.flags_cover & MASKCOVERSEYES) || \
(H.glasses && H.glasses.flags_cover & GLASSESCOVERSEYES))
// you can't stab someone in the eyes wearing a mask!
to_chat(user, "<span class='danger'>You're going to need to remove that mask/helmet/glasses first!</span>")
return
if(ismonkey(M))
var/mob/living/carbon/monkey/Mo = M
if(Mo.wear_mask && Mo.wear_mask.flags_cover & MASKCOVERSEYES)
// you can't stab someone in the eyes wearing a mask!
to_chat(user, "<span class='danger'>You're going to need to remove that mask/helmet/glasses first!</span>")
return
if(isalien(M))//Aliens don't have eyes./N slimes also don't have eyes!
to_chat(user, "<span class='warning'>You cannot locate any eyes on this creature!</span>")
return
if(isbrain(M))
to_chat(user, "<span class='danger'>You cannot locate any organic eyes on this brain!</span>")
return
src.add_fingerprint(user)
playsound(loc, src.hitsound, 30, 1, -1)
user.do_attack_animation(M)
if(M != user)
M.visible_message("<span class='danger'>[user] has stabbed [M] in the eye with [src]!</span>", \
"<span class='userdanger'>[user] stabs you in the eye with [src]!</span>")
else
user.visible_message( \
"<span class='danger'>[user] has stabbed themself 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)
add_logs(user, M, "attacked", "[src.name]", "(INTENT: [uppertext(user.a_intent)])")
M.adjust_blurriness(3)
M.adjust_eye_damage(rand(2,4))
if(M.eye_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.disabilities & (NEARSIGHT | BLIND)))
if(M.become_nearsighted())
to_chat(M, "<span class='danger'>You become nearsighted!</span>")
if(prob(50))
if(M.stat != DEAD)
if(M.drop_item())
to_chat(M, "<span class='danger'>You drop what you're holding and clutch at your eyes!</span>")
M.adjust_blurriness(10)
M.Paralyse(1)
M.Weaken(2)
if (prob(M.eye_damage - 10 + 1))
if(M.become_blind())
to_chat(M, "<span class='danger'>You go blind!</span>")
/obj/item/clean_blood()
. = ..()
if(.)
if(initial(icon) && initial(icon_state))
var/index = blood_splatter_index()
var/icon/blood_splatter_icon = GLOB.blood_splatter_icons[index]
if(blood_splatter_icon)
cut_overlay(blood_splatter_icon)
/obj/item/clothing/gloves/clean_blood()
. = ..()
if(.)
transfer_blood = 0
/obj/item/singularity_pull(S, current_size)
if(current_size >= STAGE_FOUR)
throw_at(S,14,3, spin=0)
else ..()
/obj/item/throw_impact(atom/A)
if(A && !QDELETED(A))
var/itempush = 1
if(w_class < 4)
itempush = 0 //too light to push anything
return A.hitby(src, 0, itempush)
/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback)
thrownby = thrower
callback = CALLBACK(src, .proc/after_throw, callback) //replace their callback with our own
. = ..(target, range, speed, thrower, spin, diagonals_first, callback)
/obj/item/proc/after_throw(datum/callback/callback)
if (callback) //call the original callback
. = callback.Invoke()
throw_speed = initial(throw_speed) //explosions change this.
/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/weapon/storage
if(!newLoc)
return 0
if(istype(loc,/obj/item/weapon/storage))
var/obj/item/weapon/storage/S = loc
S.remove_from_storage(src,newLoc)
return 1
return 0
/obj/item/proc/is_hot()
return heat
/obj/item/proc/is_sharp()
return sharpness
/obj/item/proc/get_dismemberment_chance(obj/item/bodypart/affecting)
if(affecting.can_dismember(src))
if((sharpness || damtype == BURN) && w_class >= 3)
. = force*(w_class-1)
/obj/item/proc/get_dismember_sound()
if(damtype == BURN)
. = 'sound/weapons/sear.ogg'
else
. = pick('sound/misc/desceration-01.ogg', 'sound/misc/desceration-02.ogg', 'sound/misc/desceration-03.ogg')
/obj/item/proc/open_flame(flame_heat=700)
var/turf/location = loc
if(ismob(location))
var/mob/M = location
var/success = FALSE
if(src == M.get_item_by_slot(slot_wear_mask))
success = TRUE
if(success)
location = get_turf(M)
if(isturf(location))
location.hotspot_expose(flame_heat, 5)
/obj/item/proc/ignition_effect(atom/A, mob/user)
if(is_hot())
. = "<span class='notice'>[user] lights [A] with [src].</span>"
else
. = ""
//when an item modify our speech spans when in our active hand. Override this to modify speech spans.
/obj/item/proc/get_held_item_speechspans(mob/living/carbon/user)
return
/obj/item/hitby(atom/movable/AM)
return
/obj/item/attack_hulk(mob/living/carbon/human/user)
return 0
/obj/item/attack_animal(mob/living/simple_animal/M)
return 0
/obj/item/mech_melee_attack(obj/mecha/M)
return 0
/obj/item/burn()
if(!QDELETED(src))
var/turf/T = get_turf(src)
var/ash_type = /obj/effect/decal/cleanable/ash
if(w_class == WEIGHT_CLASS_HUGE || w_class == WEIGHT_CLASS_GIGANTIC)
ash_type = /obj/effect/decal/cleanable/ash/large
var/obj/effect/decal/cleanable/ash/A = new ash_type(T)
A.desc += "\nLooks like this used to be \an [name] some time ago."
..()
/obj/item/acid_melt()
if(!QDELETED(src))
var/turf/T = get_turf(src)
var/obj/effect/decal/cleanable/molten_object/MO = new(T)
MO.pixel_x = rand(-16,16)
MO.pixel_y = rand(-16,16)
MO.desc = "Looks like this was \an [src] some time ago."
..()
/obj/item/proc/microwave_act(obj/machinery/microwave/M)
if(M && M.dirty < 100)
M.dirty++