diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm index 3ca26908e24..9a8f4002a44 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm @@ -132,9 +132,6 @@ ///cancel clean #define COMSIG_ATOM_CANCEL_CLEAN (1<<0) -/// From /obj/item/stack/make_item() -#define COMSIG_ATOM_CONSTRUCTED "atom_constructed" - /// From /obj/effect/particle_effect/sparks/proc/sparks_touched(datum/source, atom/movable/singed) #define COMSIG_ATOM_TOUCHED_SPARKS "atom_touched_sparks" #define COMSIG_ATOM_TOUCHED_HAZARDOUS_SPARKS "atom_touched_hazardous_sparks" diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm index 48d1c6f9a04..33fad5b5309 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm @@ -24,8 +24,8 @@ #define COMPONENT_BULLET_PIERCED (1<<2) ///from base of atom/bullet_act(): (/obj/proj, def_zone, piercing_hit, blocked) #define COMSIG_ATOM_BULLET_ACT "atom_bullet_act" -///from base of atom/on_craft_completion(): (components, datum/crafting_recipe/current_recipe) -#define COMSIG_ATOM_ON_CRAFT "atom_checkparts" +///from base of atom/on_craft_completion(): (components, datum/crafting_recipe/current_recipe, atom/crafter) +#define COMSIG_ATOM_ON_CRAFT "atom_on_craft_completion" ///from base of atom/used_in_craft(): (atom/result) #define COMSIG_ATOM_USED_IN_CRAFT "atom_used_in_craft" ///from base of atom/blob_act(): (/obj/structure/blob) diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index 2c098eb2e7b..b1210932f47 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -229,6 +229,10 @@ GLOBAL_LIST_INIT(skin_tone_names, list( var/holding = user.get_active_held_item() +#ifdef UNIT_TESTS + timed_action_flags &= ~IGNORE_SLOWDOWNS //it shouldn't stop unit test dummies from being fast as hell +#endif + if(!(timed_action_flags & IGNORE_SLOWDOWNS)) delay *= user.cached_multiplicative_actions_slowdown diff --git a/code/_globalvars/lists/clothing.dm b/code/_globalvars/lists/clothing.dm index 674a32664a7..92bf067e001 100644 --- a/code/_globalvars/lists/clothing.dm +++ b/code/_globalvars/lists/clothing.dm @@ -9,3 +9,11 @@ GLOBAL_LIST_INIT(backpacklist, list( GMESSENGER, LSATCHEL, )) + + +GLOBAL_LIST_INIT(suit_sensor_mode_to_defines, list( + "Off" = SENSOR_OFF, + "Binary vitals" = SENSOR_LIVING, + "Exact vitals" = SENSOR_VITALS, + "Tracking beacon" = SENSOR_COORDS, +)) diff --git a/code/datums/components/crafting/crafting.dm b/code/datums/components/crafting/crafting.dm index 9c0677f1fc4..3a3cc16eb6a 100644 --- a/code/datums/components/crafting/crafting.dm +++ b/code/datums/components/crafting/crafting.dm @@ -372,19 +372,17 @@ if(recipe.structures) requirements += recipe.structures + var/list/surroundings = get_environment(atom, recipe.blacklist) for(var/path_key in requirements) - var/list/surroundings var/amount = recipe.reqs?[path_key] || recipe.machinery?[path_key] || recipe.structures?[path_key] if(!amount)//since machinery & structures can have 0 aka CRAFTING_MACHINERY_USE - i.e. use it, don't consume it! continue - surroundings = get_environment(atom, recipe.blacklist) - surroundings -= return_list if(ispath(path_key, /datum/reagent)) if(!holder) holder = new(INFINITY, NO_REACT) //an infinite volume holder than can store reagents without reacting return_list += holder while(amount > 0) - var/obj/item/reagent_containers/container = locate() in surroundings + var/obj/item/reagent_containers/container = (locate() in surroundings) || (locate() in return_list) if(isnull(container)) //This would only happen if the previous checks for contents and tools were flawed. stack_trace("couldn't fulfill the required amount for [path_key]. Dangit") if(QDELING(container)) //it's deleting... @@ -394,7 +392,6 @@ if(reagent_volume) container.reagents.trans_to(holder, min(amount, reagent_volume), target_id = path_key, no_react = TRUE) amount -= reagent_volume - surroundings -= container container.update_appearance(UPDATE_ICON) else if(ispath(path_key, /obj/item/stack)) var/obj/item/stack/tally_stack diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm index ce77ca09ada..1433af02787 100644 --- a/code/game/atom/_atom.dm +++ b/code/game/atom/_atom.dm @@ -336,11 +336,12 @@ return FALSE /** - * Ensure a list of atoms/reagents exists inside this atom + * Called whenever an item is crafted, either via stack recipes or crafting recipes from the crafting menu * - * Cycles through the list of movables used up in the recipe and calls used_in_craft() for each of them - * then it either moves them inside the object or deletes - * them depending on whether they're in the list of parts for the recipe or not + * By default, it just cycles through the list of movables used in the recipe and calls used_in_craft() for each of them, + * then it either moves them inside the object if they're in the list of parts for the recipe + * or deletes them if they're not. + * The proc can be overriden by subtypes, as long as it always call parent. */ /atom/proc/on_craft_completion(list/components, datum/crafting_recipe/current_recipe, atom/crafter) SHOULD_CALL_PARENT(TRUE) @@ -349,17 +350,17 @@ var/list/parts_by_type = remaining_parts?.Copy() for(var/parttype in parts_by_type) //necessary for our is_type_in_list() call with the zebra arg set to true parts_by_type[parttype] = parttype - for(var/obj/item/item in components) // machinery or structure objects in the list are guaranteed to be used up. We only check items. - item.used_in_craft(src, current_recipe) - var/matched_type = is_type_in_list(item, parts_by_type, zebra = TRUE) + for(var/atom/movable/movable as anything in components) // machinery or structure objects in the list are guaranteed to be used up. We only check items. + movable.used_in_craft(src, current_recipe) + var/matched_type = is_type_in_list(movable, parts_by_type, zebra = TRUE) if(!matched_type) continue - if(isliving(item.loc)) - var/mob/living/living = item.loc - living.transferItemToLoc(item, src) + if(isliving(movable.loc) && isitem(movable)) + var/mob/living/living = movable.loc + living.transferItemToLoc(movable, src) else - item.forceMove(src) + movable.forceMove(src) if(matched_type) remaining_parts[matched_type] -= 1 diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 7475cb0ec59..38c2c4dc26d 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -594,7 +594,8 @@ GLOBAL_LIST_INIT(durathread_recipes, list ( \ . = ..() . += GLOB.durathread_recipes -/obj/item/stack/sheet/durathread/on_item_crafted(mob/builder, atom/created) +/obj/item/stack/sheet/durathread/used_in_craft(atom/created, datum/crafting_recipe/recipe) + . = ..() created.set_armor_rating(CONSUME, max(50, created.get_armor_rating(CONSUME))) /obj/item/stack/sheet/cotton diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 4c41a55aff1..52a74476fbe 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -454,22 +454,22 @@ var/turf/covered_turf = builder.drop_location() if(!isturf(covered_turf)) return - var/turf/created_turf = covered_turf.place_on_top(recipe.result_type, flags = CHANGETURF_INHERIT_AIR) + created = covered_turf.place_on_top(recipe.result_type, flags = CHANGETURF_INHERIT_AIR) builder.balloon_alert(builder, "placed [ispath(recipe.result_type, /turf/open) ? "floor" : "wall"]") if((recipe.crafting_flags & CRAFT_APPLIES_MATS) && LAZYLEN(mats_per_unit)) - created_turf.set_custom_materials(mats_per_unit, recipe.req_amount / recipe.res_amount) + created.set_custom_materials(mats_per_unit, recipe.req_amount / recipe.res_amount) else created = new recipe.result_type(builder.drop_location()) builder.balloon_alert(builder, "built item") - if(created) + // split the material and use it for the craft + var/obj/item/stack/used_stack = split_stack(recipe.req_amount * multiplier) + if(ismovable(created)) created.setDir(builder.dir) - SEND_SIGNAL(created, COMSIG_ATOM_CONSTRUCTED, builder) - on_item_crafted(builder, created) + created.on_craft_completion(list(used_stack), null, builder) + qdel(used_stack) //you've outlived your purpose - // Use up the material - use(recipe.req_amount * multiplier) builder.investigate_log("crafted [recipe.title]", INVESTIGATE_CRAFTING) // Apply mat datums @@ -497,10 +497,6 @@ return TRUE -/// Run special logic on created items after they've been successfully crafted. -/obj/item/stack/proc/on_item_crafted(mob/builder, atom/created) - return - /obj/item/stack/vv_edit_var(vname, vval) if(vname == NAMEOF(src, amount)) add(clamp(vval, 1-amount, max_amount - amount)) //there must always be one. diff --git a/code/game/objects/structures/water_structures/toilet.dm b/code/game/objects/structures/water_structures/toilet.dm index 67d24d4007a..cd9fcfe6604 100644 --- a/code/game/objects/structures/water_structures/toilet.dm +++ b/code/game/objects/structures/water_structures/toilet.dm @@ -232,9 +232,12 @@ if(!flushing && cover_open) . += "[base_icon_state]-water" -/obj/structure/toilet/atom_deconstruct(dissambled = TRUE) - for(var/obj/toilet_item in cistern_items) +/obj/structure/toilet/dump_contents() + for(var/obj/toilet_item in (cistern_items + fishes)) toilet_item.forceMove(drop_location()) + +/obj/structure/toilet/atom_deconstruct(dissambled = TRUE) + dump_contents() if(buildstacktype) new buildstacktype(loc,buildstackamount) else @@ -242,8 +245,6 @@ new M.sheet_type(loc, FLOOR(custom_materials[M] / SHEET_MATERIAL_AMOUNT, 1)) if(has_water_reclaimer) new /obj/item/stock_parts/water_recycler(drop_location()) - if(stuck_item) - stuck_item.forceMove(drop_location()) /obj/structure/toilet/item_interaction(mob/living/user, obj/item/tool, list/modifiers) if(user.combat_mode) diff --git a/code/modules/antagonists/cult/cult_structures.dm b/code/modules/antagonists/cult/cult_structures.dm index 697b1dc88c9..1db1297c884 100644 --- a/code/modules/antagonists/cult/cult_structures.dm +++ b/code/modules/antagonists/cult/cult_structures.dm @@ -19,12 +19,11 @@ cult_team = null return ..() -/obj/structure/destructible/cult/Initialize(mapload) +/obj/structure/destructible/cult/on_craft_completion(list/components, datum/crafting_recipe/current_recipe, atom/crafter) . = ..() - RegisterSignal(src, COMSIG_ATOM_CONSTRUCTED, PROC_REF(on_constructed)) - -/obj/structure/destructible/cult/proc/on_constructed(datum/source, mob/builder) - SIGNAL_HANDLER + if(!ismob(crafter)) + return + var/mob/living/builder = crafter var/datum/antagonist/cult/cultist = builder.mind?.has_antag_datum(/datum/antagonist/cult, TRUE) cult_team = cultist?.get_team() diff --git a/code/modules/clothing/under/_suit_sensor.dm b/code/modules/clothing/under/_suit_sensor.dm new file mode 100644 index 00000000000..2951a3ad2d4 --- /dev/null +++ b/code/modules/clothing/under/_suit_sensor.dm @@ -0,0 +1,84 @@ +///The item representing suit sensors when removed from a suit or dress (obj/item/clothing/under) +/obj/item/suit_sensor + name = "suit sensor" + desc = "That thingamabob medbay keeps telling you to set to 'Tracking Beacon'. It needs to be attached to a worn suit or dress to work." + icon = 'icons/obj/devices/tracker.dmi' + icon_state = "suit_sensor" + base_icon_state = "suit_sensor" + obj_flags = CONDUCTS_ELECTRICITY + w_class = WEIGHT_CLASS_TINY + custom_materials = list(/datum/material/iron= SMALL_MATERIAL_AMOUNT, /datum/material/glass= SMALL_MATERIAL_AMOUNT) + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + throwforce = 2 + throw_speed = 3 + throw_range = 7 + ///The current sensor mode, inherited from the clothing it's cut from. Can also be changed when using it in hand. + var/sensor_mode = SENSOR_OFF + ///Suit sensors are busted when struck by a heavy electromagnetic pulse. + var/broken = FALSE + +/obj/item/suit_sensor/examine(mob/user) + . = ..() + if(broken) + . += span_warning("It's currently broken. You can use a piece of [EXAMINE_HINT("cable")] to fix it.") + else + . += span_notice("It's currently set on '[GLOB.suit_sensor_mode_to_defines.Find(sensor_mode + 1)]'.") + +/obj/item/suit_sensor/update_overlays() + . = ..() + if(broken) + return + switch(sensor_mode) + if(SENSOR_LIVING) + . += "suit_sensor_binary" + if(SENSOR_VITALS) + . += "suit_sensor_vitals" + if(SENSOR_COORDS) + . += "suit_sensor_tracking" + +/obj/item/suit_sensor/proc/set_mode(new_mode) + if(sensor_mode == new_mode) + return FALSE + sensor_mode = new_mode + update_appearance(UPDATE_OVERLAYS) + return TRUE + +/obj/item/suit_sensor/attack_self(mob/living/user) + . = ..() + if(!(user.mobility_flags & MOBILITY_USE) || !IsReachableBy(user)) + return FALSE + if(broken) + balloon_alert(user, "fix it first!") + return + var/current_mode_text = GLOB.suit_sensor_mode_to_defines[sensor_mode + 1] + var/new_mode = tgui_input_list(user, "Select a sensor mode", "Suit Sensors", GLOB.suit_sensor_mode_to_defines, current_mode_text) + if(isnull(new_mode) || broken|| !(user.mobility_flags & MOBILITY_USE) || !IsReachableBy(user)) + user.balloon_alert(user, "can't do that now!") + return + set_mode(GLOB.suit_sensor_mode_to_defines[new_mode]) + balloon_alert(user, "sensor set to '[LOWER_TEXT(new_mode)]'") + +/obj/item/suit_sensor/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF || broken) + return + + if(severity <= EMP_HEAVY) + broken = TRUE + else + set_mode(pick(SENSOR_OFF, SENSOR_OFF, SENSOR_OFF, SENSOR_LIVING, SENSOR_LIVING, SENSOR_VITALS, SENSOR_VITALS, SENSOR_COORDS)) + playsound(source = src, soundin = 'sound/effects/sparks/sparks3.ogg', vol = 75, vary = TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE, ignore_walls = FALSE) + +/obj/item/suit_sensor/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/stack/cable_coil)) + return ..() + if(!broken) + balloon_alert(user, "not broken!") + return ITEM_INTERACT_BLOCKING + var/obj/item/stack/cable_coil/cabling = tool + cabling.use(1) + balloon_alert(user, "suit sensor repaired") + broken = FALSE + update_appearance(UPDATE_OVERLAYS) + return ITEM_INTERACT_SUCCESS diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm index b9910bfb2bf..89f744246ad 100644 --- a/code/modules/clothing/under/_under.dm +++ b/code/modules/clothing/under/_under.dm @@ -34,7 +34,7 @@ /// Does this undersuit spawn with a random sensor value var/random_sensor = TRUE /// What is the active sensor mode of this udnersuit - var/sensor_mode = NO_SENSORS + var/sensor_mode = SENSOR_OFF // Accessory handling (Can be componentized eventually) /// The max number of accessories we can have on this suit. @@ -60,6 +60,19 @@ register_context() AddElement(/datum/element/update_icon_updates_onmob, flags = ITEM_SLOT_ICLOTHING|ITEM_SLOT_OCLOTHING|ITEM_SLOT_NECK, body = TRUE) +/obj/item/clothing/under/on_craft_completion(list/components, datum/crafting_recipe/current_recipe, atom/crafter) + . = ..() + var/obj/item/clothing/under/any_original = locate() in components + if(!any_original) + set_has_sensor(NO_SENSORS) + return + set_has_sensor(any_original.has_sensor) + set_sensor_mode(any_original.sensor_mode) + +/obj/item/clothing/under/used_in_craft(atom/result, datum/crafting_recipe/current_recipe) + . = ..() + dump_attachments() + /obj/item/clothing/under/setup_reskinning() if(!check_setup_reskinning()) return @@ -72,7 +85,7 @@ var/changed = FALSE - if((isnull(held_item) || held_item == src) && has_sensor == HAS_SENSORS) + if(has_sensor == HAS_SENSORS && (isnull(held_item) || held_item == src)) context[SCREENTIP_CONTEXT_RMB] = "Toggle suit sensors" context[SCREENTIP_CONTEXT_CTRL_LMB] = "Set suit sensors to tracking" changed = TRUE @@ -85,10 +98,18 @@ context[SCREENTIP_CONTEXT_ALT_RMB] = "Remove accessory" changed = TRUE - if(istype(held_item, /obj/item/stack/cable_coil) && has_sensor == BROKEN_SENSORS) + if(has_sensor == BROKEN_SENSORS && istype(held_item, /obj/item/stack/cable_coil)) context[SCREENTIP_CONTEXT_LMB] = "Repair suit sensors" changed = TRUE + if(has_sensor == NO_SENSORS) + if(istype(held_item, /obj/item/suit_sensor)) + context[SCREENTIP_CONTEXT_LMB] = "Install suit sensors" + changed = TRUE + else if(held_item?.tool_behaviour == TOOL_WIRECUTTER) + context[SCREENTIP_CONTEXT_LMB] = "Cut suit sensors" + changed = TRUE + if(can_adjust && adjusted != DIGITIGRADE_STYLE) context[SCREENTIP_CONTEXT_ALT_LMB] = "Wear [adjusted == ALT_STYLE ? "normally" : "casually"]" changed = TRUE @@ -114,15 +135,55 @@ if (blood_overlay) . += blood_overlay -/obj/item/clothing/under/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers) - if(repair_sensors(attacking_item, user)) - return TRUE +/obj/item/clothing/under/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(istype(tool, /obj/item/stack/cable_coil)) + if(!repair_sensors(user)) + return ITEM_INTERACT_BLOCKING + var/obj/item/stack/cable_coil/cabling = tool + cabling.use(1) + cabling.visible_message(span_notice("[user] repairs the suit sensors on [src] with [cabling].")) + return ITEM_INTERACT_SUCCESS - if(istype(attacking_item, /obj/item/clothing/accessory)) - return attach_accessory(attacking_item, user) + if(istype(tool, /obj/item/clothing/accessory)) + return attach_accessory(tool, user) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING + + if(istype(tool, /obj/item/suit_sensor)) + if(has_sensor != NO_SENSORS) + balloon_alert(user, "already has sensors!") + return ITEM_INTERACT_BLOCKING + balloon_alert(user, "installing sensors...") + if(!do_after(user, 5 SECONDS, target = src)) + return ITEM_INTERACT_BLOCKING + var/obj/item/suit_sensor/sensor = tool + if(sensor.broken) + set_has_sensor(BROKEN_SENSORS) + else + set_has_sensor(HAS_SENSORS) + set_sensor_mode(sensor.sensor_mode) + qdel(tool) + balloon_alert(user, "sensors installed") + playsound(source = src, soundin = 'sound/effects/sparks/sparks4.ogg', vol = 50, vary = TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE, ignore_walls = FALSE) + return ITEM_INTERACT_SUCCESS return ..() +/obj/item/clothing/under/wirecutter_act(mob/living/user, obj/item/tool) + if(has_sensor == NO_SENSORS) + balloon_alert(user, "doesn't have sensors!") + return ITEM_INTERACT_BLOCKING + balloon_alert(user, "cutting out sensors...") + if(!do_after(user, 5 SECONDS, target = src)) + return ITEM_INTERACT_BLOCKING + var/obj/item/suit_sensor/sensor = new (drop_location()) + if(sensor.IsReachableBy(user)) + user.put_in_hands(sensor) + if(has_sensor == BROKEN_SENSORS) + sensor.broken = TRUE + else + sensor.set_mode(sensor_mode) + set_has_sensor(NO_SENSORS) + return ITEM_INTERACT_SUCCESS + /obj/item/clothing/under/attack_hand_secondary(mob/user, params) . = ..() if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) @@ -144,7 +205,7 @@ if(damaged_state == CLOTHING_SHREDDED && has_sensor > NO_SENSORS) break_sensors() else if(damaged_state == CLOTHING_PRISTINE && has_sensor == BROKEN_SENSORS) - repair_sensors(cable_required = FALSE) + repair_sensors() update_appearance() /obj/item/clothing/under/visual_equipped(mob/user, slot) @@ -189,23 +250,12 @@ /** * Repair the suit sensors and update the mob's status on the global sensor list. * Can be called either through player action such as repairing with coil, or as part of a general fixing proc - * - * Arguments: - * * attacking_item - the item being used for the repair, if any - * * user - mob that's doing the repair - * * cable_required - set to FALSE to bypass consuming cable coil */ -/obj/item/clothing/under/proc/repair_sensors(obj/item/attacking_item, mob/user, cable_required = TRUE) +/obj/item/clothing/under/proc/repair_sensors(mob/user) if(has_sensor != BROKEN_SENSORS) - return - - if(cable_required) - if(!istype(attacking_item, /obj/item/stack/cable_coil)) - return - var/obj/item/stack/cable_coil/cabling = attacking_item - if(!cabling.use(1)) - return - cabling.visible_message(span_notice("[user] repairs the suit sensors on [src] with [cabling].")) + if(user) + balloon_alert(user, "sensors [has_sensor == NO_SENSORS ? "missing" : "not broken"]!") + return FALSE playsound(source = src, soundin = 'sound/effects/sparks/sparks4.ogg', vol = 100, vary = TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE, ignore_walls = FALSE) set_has_sensor(HAS_SENSORS) @@ -393,6 +443,9 @@ . += "Its vital tracker appears to be enabled." if(SENSOR_COORDS) . += "Its vital tracker and tracking beacon appear to be enabled." + else + . += span_tooltip("You can always get new suit sensors to install from a lathe.", "It isn't equipped with medical sensors.") + if(LAZYLEN(attached_accessories)) var/list/accessories = list_accessories_with_icon(user) . += "It has [english_list(accessories)] attached." @@ -414,14 +467,14 @@ if(!can_toggle_sensors(user_mob)) return - var/list/modes = list("Off", "Binary vitals", "Exact vitals", "Tracking beacon") - var/switchMode = tgui_input_list(user_mob, "Select a sensor mode", "Suit Sensors", modes, modes[sensor_mode + 1]) - if(isnull(switchMode)) + var/current_mode_text = GLOB.suit_sensor_mode_to_defines[sensor_mode + 1] + var/new_mode = tgui_input_list(user_mob, "Select a sensor mode", "Suit Sensors", GLOB.suit_sensor_mode_to_defines, current_mode_text) + if(isnull(new_mode)) return if(!can_toggle_sensors(user_mob)) return - set_sensor_mode(modes.Find(switchMode) - 1) + set_sensor_mode(GLOB.suit_sensor_mode_to_defines[new_mode]) if (loc == user_mob) switch(sensor_mode) if(SENSOR_OFF) diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm index ae6d8fad2c3..4f6f3e4ea75 100644 --- a/code/modules/mob/living/carbon/human/dummy.dm +++ b/code/modules/mob/living/carbon/human/dummy.dm @@ -157,6 +157,21 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy) /mob/living/carbon/human/consistent/domutcheck() return // We skipped adding any mutations so this runtimes +/mob/living/carbon/human/consistent/slow + +#ifdef UNIT_TESTS +//unit test dummies should be very fast with actions +/mob/living/carbon/human/dummy/consistent/initialize_actionspeed() + add_or_update_variable_actionspeed_modifier(/datum/actionspeed_modifier/base, multiplicative_slowdown = -1) + +/mob/living/carbon/human/consistent/initialize_actionspeed() + add_or_update_variable_actionspeed_modifier(/datum/actionspeed_modifier/base, multiplicative_slowdown = -1) + +//this one gives us a small window of time for checks on asynced actions. +/mob/living/carbon/human/consistent/slow/initialize_actionspeed() + add_or_update_variable_actionspeed_modifier(/datum/actionspeed_modifier/base, multiplicative_slowdown = 0.1) +#endif + //Inefficient pooling/caching way. GLOBAL_LIST_EMPTY(human_dummy_list) GLOBAL_LIST_EMPTY(dummy_mob_list) diff --git a/code/modules/research/designs/autolathe/multi-department_designs.dm b/code/modules/research/designs/autolathe/multi-department_designs.dm index a1a1037b2e2..59fe328104a 100644 --- a/code/modules/research/designs/autolathe/multi-department_designs.dm +++ b/code/modules/research/designs/autolathe/multi-department_designs.dm @@ -495,6 +495,18 @@ ) departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_SCIENCE +/datum/design/suit_sensor + name = "Suit Sensor" + id = "suit_sensor" + build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT, /datum/material/glass = SMALL_MATERIAL_AMOUNT) + build_path = /obj/item/suit_sensor + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_ASSEMBLIES, + ) + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL | DEPARTMENT_BITFLAG_SECURITY + /datum/design/conveyor_belt name = "Conveyor Belt" id = "conveyor_belt" diff --git a/code/modules/research/techweb/nodes/medbay_nodes.dm b/code/modules/research/techweb/nodes/medbay_nodes.dm index 49d4aad71f7..09f404e97ec 100644 --- a/code/modules/research/techweb/nodes/medbay_nodes.dm +++ b/code/modules/research/techweb/nodes/medbay_nodes.dm @@ -33,6 +33,7 @@ "jerrycan", "reflex_hammer", "blood_scanner", + "suit_sensor", ) experiments_to_unlock = list( /datum/experiment/autopsy/human, diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm index 672d29ad825..11ce9320b64 100644 --- a/code/modules/surgery/surgery_step.dm +++ b/code/modules/surgery/surgery_step.dm @@ -91,22 +91,25 @@ // Only followers of Asclepius have the ability to use Healing Touch and perform miracle feats of surgery. // Prevents people from performing multiple simultaneous surgeries unless they're holding a Rod of Asclepius. - surgery.step_in_progress = TRUE - var/speed_mod = 1 - var/fail_prob = 0//100 - fail_prob = success_prob - var/advance = FALSE + var/interaction_key = HAS_TRAIT(user, TRAIT_HIPPOCRATIC_OATH) ? target : DOAFTER_SOURCE_SURGERY + if(DOING_INTERACTION(user, interaction_key)) + user.balloon_alert(user, "already doing surgery!") + return FALSE if(!chem_check(target)) user.balloon_alert(user, "missing [LOWER_TEXT(get_chem_list())]!") to_chat(user, span_warning("[target] is missing the [LOWER_TEXT(get_chem_list())] required to perform this surgery step!")) - surgery.step_in_progress = FALSE return FALSE if(preop(user, target, target_zone, tool, surgery) == SURGERY_STEP_FAIL) update_surgery_mood(target, SURGERY_STATE_FAILURE) - surgery.step_in_progress = FALSE return FALSE + surgery.step_in_progress = TRUE + var/speed_mod = 1 + var/fail_prob = 0//100 - fail_prob = success_prob + var/advance = FALSE + update_surgery_mood(target, SURGERY_STATE_STARTED) play_preop_sound(user, target, target_zone, tool, surgery) // Here because most steps overwrite preop @@ -153,7 +156,7 @@ var/was_sleeping = (target.stat != DEAD && target.IsSleeping()) - if(do_after(user, modded_time, target = target, interaction_key = user.has_status_effect(/datum/status_effect/hippocratic_oath) ? target : DOAFTER_SOURCE_SURGERY)) //If we have the hippocratic oath, we can perform one surgery on each target, otherwise we can only do one surgery in total. + if(do_after(user, modded_time, target = target, interaction_key = interaction_key)) //If we have the hippocratic oath, we can perform one surgery on each target, otherwise we can only do one surgery in total. if((prob(100-fail_prob) || (iscyborg(user) && !silicons_obey_prob)) && !try_to_fail) if(success(user, target, target_zone, tool, surgery)) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 8a184d7e966..66568b8b537 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -306,6 +306,7 @@ #include "stuns.dm" #include "style_hotswapping.dm" #include "subsystem_init.dm" +#include "suit_sensor.dm" #include "suit_storage_icons.dm" #include "surgeries.dm" #include "syringe_gun.dm" diff --git a/code/modules/unit_tests/combat_pistol_whip.dm b/code/modules/unit_tests/combat_pistol_whip.dm index cafecd4f59e..4783d94242b 100644 --- a/code/modules/unit_tests/combat_pistol_whip.dm +++ b/code/modules/unit_tests/combat_pistol_whip.dm @@ -57,4 +57,4 @@ butcher.put_in_active_hand(gun, forced = TRUE) click_wrapper(butcher, meat, list(RIGHT_CLICK = TRUE, BUTTON = RIGHT_CLICK)) - TEST_ASSERT(DOING_INTERACTION(butcher, meat), "The butcher did not start butchering the monkey when using a bayonetted weapon.") + TEST_ASSERT(QDELETED(meat), "The butcher did not butchering the monkey when using a bayonetted weapon.") diff --git a/code/modules/unit_tests/suit_sensor.dm b/code/modules/unit_tests/suit_sensor.dm new file mode 100644 index 00000000000..6e8df2b9a9f --- /dev/null +++ b/code/modules/unit_tests/suit_sensor.dm @@ -0,0 +1,43 @@ +///Test that ensures that basic functions and interactions around suit sensors are working +/datum/unit_test/suit_sensor + +/datum/unit_test/suit_sensor/Run() + var/mob/living/carbon/human/consistent/dummy = allocate(__IMPLIED_TYPE__) + var/obj/item/clothing/under/sensor_test/under = allocate(__IMPLIED_TYPE__) + + dummy.equip_to_slot_or_del(under, ITEM_SLOT_ICLOTHING) + + TEST_ASSERT(under.set_sensor_mode(SENSOR_LIVING), "couldn't set the suit sensor mode to '[GLOB.suit_sensor_mode_to_defines[SENSOR_LIVING + 1]]'") + TEST_ASSERT((dummy in GLOB.suit_sensors_list), "couldn't find the dummy in the GLOB.suit_sensors_list") + + var/obj/item/wirecutters/cutter = allocate(__IMPLIED_TYPE__) + dummy.put_in_active_hand(cutter, forced = TRUE) + click_wrapper(dummy, under) //cut sensor + TEST_ASSERT_EQUAL(under.has_sensor, NO_SENSORS, "couldn't properly cut suit sensor from the jumpsuit") + + var/obj/item/suit_sensor/sensor = dummy.is_holding_item_of_type(__IMPLIED_TYPE__) + TEST_ASSERT(sensor, "dummy isn't holding the cut sensor") + //we set it to sensor_living before, remember? + TEST_ASSERT_EQUAL(sensor.sensor_mode, SENSOR_LIVING, "cut sensor isn't set to '[GLOB.suit_sensor_mode_to_defines[SENSOR_LIVING + 1]]'") + TEST_ASSERT(sensor.set_mode(SENSOR_OFF), "couldn't set cut sensor's mode to '[GLOB.suit_sensor_mode_to_defines[SENSOR_OFF + 1]]'") + sensor.emp_act(EMP_HEAVY) + TEST_ASSERT(sensor.broken, "cut sensor wasn't broken by EMP") + + dummy.dropItemToGround(cutter) //thank you for your service, wirecutters o7 + var/obj/item/stack/cable_coil/thirty/coil = allocate(__IMPLIED_TYPE__) + dummy.put_in_active_hand(coil, forced = TRUE) + click_wrapper(dummy, sensor) //fix sensor + TEST_ASSERT(!sensor.broken, "cut sensor couldn't be fixed by cable coil") + + dummy.swap_hand(dummy.get_held_index_of_item(sensor)) + click_wrapper(dummy, under) //install sensor + TEST_ASSERT_EQUAL(under.has_sensor, HAS_SENSORS, "couldn't properly install suit sensor on the jumpsuit") + under.emp_act(EMP_HEAVY) + TEST_ASSERT_EQUAL(under.has_sensor, BROKEN_SENSORS, "the jumpsuit sensor wasn't broken by EMP") + dummy.swap_hand(dummy.get_held_index_of_item(coil)) + click_wrapper(dummy, under) //fix sensor, again + TEST_ASSERT_EQUAL(under.has_sensor, HAS_SENSORS, "couldn't fix the jumpsuit sensor with cable coil") + + +/obj/item/clothing/under/sensor_test + random_sensor = FALSE diff --git a/code/modules/unit_tests/surgeries.dm b/code/modules/unit_tests/surgeries.dm index f85ea6cc870..ab02d33244f 100644 --- a/code/modules/unit_tests/surgeries.dm +++ b/code/modules/unit_tests/surgeries.dm @@ -68,7 +68,7 @@ TEST_ASSERT_EQUAL(alice.facial_hair_color, COLOR_LIGHT_BROWN, "Bob's head was transplanted onto Alice's body, but their facial hair color is not COLOR_LIGHT_BROWN") /datum/unit_test/multiple_surgeries/Run() - var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human/consistent/slow) var/mob/living/carbon/human/patient_zero = allocate(/mob/living/carbon/human/consistent) var/mob/living/carbon/human/patient_one = allocate(/mob/living/carbon/human/consistent) diff --git a/icons/obj/devices/tracker.dmi b/icons/obj/devices/tracker.dmi index fd6c7edd997..a5047a4f23c 100644 Binary files a/icons/obj/devices/tracker.dmi and b/icons/obj/devices/tracker.dmi differ diff --git a/tgstation.dme b/tgstation.dme index ba97677cab1..3ba5891ae7c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4156,6 +4156,7 @@ #include "code\modules\clothing\suits\wetfloor.dm" #include "code\modules\clothing\suits\wintercoats.dm" #include "code\modules\clothing\suits\wiz_robe.dm" +#include "code\modules\clothing\under\_suit_sensor.dm" #include "code\modules\clothing\under\_under.dm" #include "code\modules\clothing\under\color.dm" #include "code\modules\clothing\under\costume.dm"