diff --git a/code/_helpers/_lists.dm b/code/_helpers/_lists.dm index af56246af6..d83253a83f 100644 --- a/code/_helpers/_lists.dm +++ b/code/_helpers/_lists.dm @@ -345,14 +345,6 @@ Checks if a list has the same entries and values as an element of big. * Sorting */ -//Reverses the order of items in the list -/proc/reverselist(list/L) - var/list/output = list() - if(L) - for(var/i = L.len; i >= 1; i--) - output += L[i] - return output - //Randomize: Return the list in a random order /proc/shuffle(var/list/L) if(!L) @@ -608,7 +600,7 @@ Checks if a list has the same entries and values as an element of big. //to_world_log("descending len input: [L.len]") var/list/out = insertion_sort_numeric_list_ascending(L) //to_world_log(" output: [out.len]") - return reverselist(out) + return reverseList(out) /proc/dd_sortedObjectList(var/list/L, var/cache=list()) if(L.len < 2) diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index 814bad9cb1..22052212ef 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -64,7 +64,7 @@ avoid code duplication. This includes items that may sometimes act as a standard return A.attackby(src, user, attack_modifier, click_parameters) // No comment -/atom/proc/attackby(obj/item/W, mob/user, var/attack_modifier, var/click_parameters) +/atom/proc/attackby(obj/item/W, mob/user, attack_modifier, click_parameters) if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACKBY, W, user, click_parameters) & COMPONENT_CANCEL_ATTACK_CHAIN) return TRUE return FALSE diff --git a/code/datums/components/materials/remote_materials.dm b/code/datums/components/materials/remote_materials.dm index e5728d8f54..f85277c3a0 100644 --- a/code/datums/components/materials/remote_materials.dm +++ b/code/datums/components/materials/remote_materials.dm @@ -171,6 +171,12 @@ handles linking back and forth. if(istype(target, /obj/item/storage/bag/sheetsnatcher)) return mat_container.OnSheetSnatcher(source, user, target) + if(istype(target, /obj/item/gripper)) + var/obj/item/gripper/robot_gripper = target + target = robot_gripper.get_wrapped_item() + attempt_insert(user, target) + return FALSE + return attempt_insert(user, target) /// Insert mats into silo diff --git a/code/game/machinery/computer/computer.dm b/code/game/machinery/computer/computer.dm index 7039394e7c..d1ce461fed 100644 --- a/code/game/machinery/computer/computer.dm +++ b/code/game/machinery/computer/computer.dm @@ -121,7 +121,7 @@ else if(istype(I,/obj/item/gripper)) //Behold, Grippers and their horribleness. If ..() is called by any computers' attackby() now or in the future, this should let grippers work with them appropriately. var/obj/item/gripper/B = I //B, for Borg. - var/obj/item/wrapped = B.get_current_pocket() + var/obj/item/wrapped = B.get_wrapped_item() if(!wrapped) to_chat(user, "\The [B] is not holding anything.") return diff --git a/code/modules/client/preference_setup/general/01_basic.dm b/code/modules/client/preference_setup/general/01_basic.dm index 4076cf1829..1cda69bb14 100644 --- a/code/modules/client/preference_setup/general/01_basic.dm +++ b/code/modules/client/preference_setup/general/01_basic.dm @@ -294,8 +294,10 @@ max_days = 30 if(12) max_days = 31 - - var/new_day = tgui_input_number(user, "Choose your character's birth day (number, 1-[max_days])", "Birthday Day", pref.read_preference(/datum/preference/numeric/human/bday_day), max_days, 0) + var/old_days = pref.read_preference(/datum/preference/numeric/human/bday_day) + if(old_days > max_days) + old_days = max_days + var/new_day = tgui_input_number(user, "Choose your character's birth day (number, 1-[max_days])", "Birthday Day", old_days, max_days, 0) if(new_day) pref.update_preference_by_type(/datum/preference/numeric/human/bday_day, new_day) else if((tgui_alert(user, "Would you like to clear the birthday entry?","Clear?",list("No","Yes")) == "Yes")) diff --git a/code/modules/food/kitchen/cooking_machines/_appliance.dm b/code/modules/food/kitchen/cooking_machines/_appliance.dm index f8685fd9cb..8b615e4c2a 100644 --- a/code/modules/food/kitchen/cooking_machines/_appliance.dm +++ b/code/modules/food/kitchen/cooking_machines/_appliance.dm @@ -271,40 +271,32 @@ if(istype(I, /obj/item/gripper)) var/obj/item/gripper/GR = I - var/obj/item/wrap = GR.get_current_pocket() + var/obj/item/wrap = GR.get_wrapped_item() if(wrap) - wrap.loc = get_turf(src) var/result = can_insert(wrap, user) if(!result) - wrap.forceMove(GR) if(!(default_deconstruction_screwdriver(user, I))) default_part_replacement(user, I) - return + return TRUE + add_content(wrap, user) + update_icon() + return FALSE - if(QDELETED(wrap)) - GR.update_ref(null) + attack_hand(user) + return TRUE - if(wrap.loc != src) - GR.drop_item_nm() + var/result = can_insert(I, user) + if(!result) + if(!(default_deconstruction_screwdriver(user, I))) + default_part_replacement(user, I) + return - ToCook = wrap - else - attack_hand(user) + if(result == 2) + var/obj/item/grab/G = I + if (G && istype(G) && G.affecting) + cook_mob(G.affecting, user) return - else - var/result = can_insert(I, user) - if(!result) - if(!(default_deconstruction_screwdriver(user, I))) - default_part_replacement(user, I) - return - - if(result == 2) - var/obj/item/grab/G = I - if (G && istype(G) && G.affecting) - cook_mob(G.affecting, user) - return - //From here we can start cooking food add_content(ToCook, user) update_icon() diff --git a/code/modules/food/kitchen/cooking_machines/container.dm b/code/modules/food/kitchen/cooking_machines/container.dm index 871d02b74e..26eb1f0917 100644 --- a/code/modules/food/kitchen/cooking_machines/container.dm +++ b/code/modules/food/kitchen/cooking_machines/container.dm @@ -39,17 +39,10 @@ /obj/item/reagent_containers/cooking_container/attackby(var/obj/item/I as obj, var/mob/user as mob) if(istype(I, /obj/item/gripper)) var/obj/item/gripper/GR = I - var/obj/item/wrapped = GR.get_current_pocket() + var/obj/item/wrapped = GR.get_wrapped_item() if(wrapped) - wrapped.forceMove(get_turf(src)) attackby(wrapped, user) - if(QDELETED(wrapped)) - wrapped = null - - else if(wrapped.loc != src) - wrapped = null - - return + return FALSE for (var/possible_type in insertable) if (istype(I, possible_type)) diff --git a/code/modules/food/kitchen/smartfridge/smartfridge.dm b/code/modules/food/kitchen/smartfridge/smartfridge.dm index bcfa92dd02..1411a95519 100644 --- a/code/modules/food/kitchen/smartfridge/smartfridge.dm +++ b/code/modules/food/kitchen/smartfridge/smartfridge.dm @@ -155,24 +155,24 @@ else if(istype(O, /obj/item/gripper)) // Grippers. ~Mechoid. var/obj/item/gripper/B = O //B, for Borg. - var/obj/item/wrapped = B.get_current_pocket() + var/obj/item/wrapped = B.get_wrapped_item() if(!wrapped) to_chat(user, span_filter_notice("\The [B] is not holding anything.")) - return + return TRUE else to_chat(user, span_filter_notice("You use \the [B] to put \the [wrapped] into \the [src].")) - return + return FALSE else to_chat(user, span_notice("\The [src] smartly refuses [O].")) - return 1 + return TRUE /obj/machinery/smartfridge/secure/emag_act(var/remaining_charges, var/mob/user) if(!emagged) emagged = 1 locked = -1 to_chat(user, span_filter_notice("You short out the product lock on [src].")) - return 1 + return TRUE /obj/machinery/smartfridge/proc/find_record(var/obj/item/O) for(var/datum/stored_item/I as anything in item_records) diff --git a/code/modules/integrated_electronics/core/assemblies/clothing.dm b/code/modules/integrated_electronics/core/assemblies/clothing.dm index 3fedc1e768..8374bd764c 100644 --- a/code/modules/integrated_electronics/core/assemblies/clothing.dm +++ b/code/modules/integrated_electronics/core/assemblies/clothing.dm @@ -53,7 +53,7 @@ var/mob/living/silicon/robot/robot = user if(istype(robot.module_active, /obj/item/gripper)) var/obj/item/gripper/gripper = robot.module_active - I = gripper.get_current_pocket() + I = gripper.get_wrapped_item() else if(!I) return FALSE diff --git a/code/modules/mob/living/carbon/human/stripping.dm b/code/modules/mob/living/carbon/human/stripping.dm index 263fcbf3b9..ef027d1560 100644 --- a/code/modules/mob/living/carbon/human/stripping.dm +++ b/code/modules/mob/living/carbon/human/stripping.dm @@ -72,7 +72,7 @@ stripping = TRUE if(is_robot_module(held) && istype(held, /obj/item/gripper)) var/obj/item/gripper/G = held - var/obj/item/wrapped = G.get_current_pocket() + var/obj/item/wrapped = G.get_wrapped_item() if(istype(wrapped)) stripping = FALSE else @@ -98,7 +98,7 @@ visible_message(span_danger("\The [user] is trying to put \a [held] on \the [src]!")) else var/obj/item/gripper/G = held - var/obj/item/wrapped = G.get_current_pocket() + var/obj/item/wrapped = G.get_wrapped_item() if(slot_to_strip == slot_wear_mask && istype(wrapped, /obj/item/grenade)) visible_message(span_danger("\The [user] is trying to put \a [wrapped] in \the [src]'s mouth!")) else @@ -120,12 +120,12 @@ unEquip(target_slot) else if(is_robot_module(held) && istype(held, /obj/item/gripper)) var/obj/item/gripper/G = held - var/obj/item/wrapped = G.get_current_pocket() + var/obj/item/wrapped = G.get_wrapped_item() if(istype(wrapped)) if(equip_to_slot_if_possible(wrapped, text2num(slot_to_strip), 0, 1, 1)) - wrapped = null - G.generate_icons() - G.current_pocket = pick(G.pockets) + if(wrapped.loc != src) + return + G.clear_and_select_item() else if(user.unEquip(held)) equip_to_slot_if_possible(held, text2num(slot_to_strip), 0, 1, 1) if(held.loc != src) diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index d3ce497f42..189e7fc5a2 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -1351,8 +1351,8 @@ /mob/living/silicon/robot/drop_item(var/atom/Target) if(module_active && istype(module_active,/obj/item/gripper)) - var/obj/item/gripper/G = module_active - G.drop_item_nm() + var/obj/item/gripper/robot_gripper = module_active + robot_gripper.drop_item_nm() /mob/living/silicon/robot/disable_spoiler_vision() if(sight_mode & (BORGMESON|BORGMATERIAL|BORGXRAY|BORGANOMALOUS)) // Whyyyyyyyy have seperate defines. diff --git a/code/modules/mob/living/silicon/robot/robot_gripper_handling.dm b/code/modules/mob/living/silicon/robot/robot_gripper_handling.dm new file mode 100644 index 0000000000..60a8bcf1c7 --- /dev/null +++ b/code/modules/mob/living/silicon/robot/robot_gripper_handling.dm @@ -0,0 +1,439 @@ +//This is used to check if the gripper is currently being used. +//If it is, we don't allow any other actions to be performed. +//Returns TRUE if we're in use explaining how. +/obj/item/gripper/proc/is_in_use(mob/user, visible = TRUE) + if(gripper_in_use) + if(visible) + to_chat(user, span_danger("You are currently using the gripper on something!")) + return TRUE + + if(in_radial_menu) + if(visible) + to_chat(user, span_danger("You are currently in the radial menu! Close it to use the gripper.")) + return TRUE + return FALSE + +/obj/item/gripper/proc/update_ref(var/datum/weakref/new_ref) + var/had_item = get_wrapped_item() + WR = new_ref + var/holding_item = get_wrapped_item() + // Feedback + update_icon() + + if(had_item && !holding_item) // Dropped + our_robot.playsound_local(get_turf(our_robot), 'sound/machines/click.ogg', 50) + return + + if(holding_item && !had_item || (holding_item != had_item)) // Pickup or change item + our_robot.playsound_local(get_turf(our_robot), 'sound/machines/click2.ogg', 50) + +///Stops the gripper from being used multiple times when we're performing a do_after +/obj/item/gripper/proc/begin_using() + gripper_in_use = TRUE + +///Allows use of the gripper (and clears the weakref) after do_after is completed. Clears the weakref if the wrapped item is no longer in our borg's contents (items get moved into the borgs contents when using the gripper) +/obj/item/gripper/proc/end_using() + gripper_in_use = FALSE + var/obj/item/wrapped = get_wrapped_item() + if(!wrapped) + return + //Checks two things: + //Is our wrapped object currently in our borg still? + //AND Is it not a gripper pocket? If not, reset WR. + if(item_left_gripper(wrapped)) + update_ref(null) + +//This is the code that updates our pockets and decides if they should have icons or not. +//This should be called every time we use the gripper and our wrapped item is used up. +/obj/item/gripper/proc/generate_icons() + if(LAZYLEN(pockets)) + + pockets_by_name = list() + + photo_images = list() + + for(var/obj/item/storage/internal/gripper/pocket_to_check in pockets) + if(!LAZYLEN(pocket_to_check.contents)) + pockets_by_name[pocket_to_check.name] = pocket_to_check + photo_images[pocket_to_check.name] = image(icon = 'icons/effects/effects.dmi', icon_state = "nothing") + continue + var/obj/item/pocket_content = pocket_to_check.contents[1] + pockets_by_name["[pocket_to_check.name]" + "[pocket_content.name]"] = pocket_content + var/image/pocket_image = image(icon = pocket_content.icon, icon_state = pocket_content.icon_state) + if(pocket_content.color) + pocket_image.color = pocket_content.color + if(pocket_content.overlays) + for(var/overlay in pocket_content.overlays) + pocket_image.overlays += overlay + photo_images["[pocket_to_check.name]" + "[pocket_content.name]"] = pocket_image + +/obj/item/gripper/attack_self(mob/user) + . = ..() + if(.) + return TRUE + if(special_handling) + return FALSE + if(is_in_use(user)) + return FALSE + + generate_icons() + + var/list/options = list() + + for(var/Iname in pockets_by_name) + options[Iname] = photo_images[Iname] + + var/list/choice = list() + + in_radial_menu = TRUE + choice = show_radial_menu(user, src, options, radius = 40, require_near = TRUE, autopick_single_option = FALSE) + in_radial_menu = FALSE + + var/obj/item/wrapped = get_wrapped_item() + if(choice) + var/obj/item/storage/internal/gripper/selected_pocket = pockets_by_name[choice] + if(!isgripperpocket(selected_pocket)) //The pocket we're selecting is NOT a gripper storage + if(!isgripperpocket(selected_pocket.loc)) //We kept the radial menu opened, used the item, then selected it again. + clear_and_select_pocket() //Pick the next open pocket. + return + + update_ref(WEAKREF(selected_pocket)) + return + + current_pocket = selected_pocket + update_ref(null) + return + + if(wrapped) + return wrapped.attack_self(user) + +/obj/item/gripper/attackby(var/obj/item/O, var/mob/user) + if(is_in_use(user)) + return FALSE + + var/obj/item/wrapped = get_wrapped_item() + if(wrapped) + wrapped.loc = loc //Place it in to the robot. + var/resolved = wrapped.attackby(O, user) + wrapped = get_wrapped_item() //We check to see if the object exists after we do attackby. + + //The object has been deleted. Select a new pocket and stop here. + if(!wrapped) + clear_and_select_pocket() + return FALSE + + //Object is not in our contents AND is not in the gripper storage still. AKA, it was moved into something or somewhere. Either way, it's not ours anymore. + if(item_left_gripper(wrapped)) + clear_and_select_pocket() + return TRUE + + //We were not given a resolved, the object still exists, AND we hit something. Attack that thing with our wrapped item. + if(!resolved && wrapped && O) + O.afterattack(wrapped,user,1) + wrapped = get_wrapped_item() + //The object still exists, but is not in our contents OR back in the gripper storage. + if(item_left_gripper(wrapped)) + clear_and_select_pocket() + return TRUE + + //Nothing happened to it. Just put it back into our pocket. + wrapped.loc = current_pocket + return TRUE + + return ..() + +/obj/item/gripper/afterattack(atom/target, mob/living/user, proximity, params) + if(!proximity) + return // This will prevent them using guns at range but adminbuse can add them directly to modules, so eh. + + if(is_in_use(user)) + return + + var/obj/item/wrapped = get_wrapped_item() + if(wrapped && item_left_gripper(wrapped)) // This is used for items that are moved out during other attack chains + clear_and_select_item() + return + + if(use_item(target, user, wrapped)) //Already have an item. + return + update_ref(WEAKREF(wrapped)) + + if(pick_up_item(target, user)) + return + + if(handle_afterattack_special(target, user)) + return + + if(item_left_gripper(wrapped)) + clear_and_select_item() + +/obj/item/gripper/proc/use_item(atom/target, mob/user, obj/item/wrapped) + if(!wrapped) + return FALSE + + var/obj/item/storage/internal/gripper/previous_pocket + if(isgripperpocket(wrapped.loc)) + previous_pocket = wrapped.loc + else + previous_pocket = current_pocket + + var/original_amount = 0 + if(istype(wrapped, /obj/item/reagent_containers)) + var/obj/item/reagent_containers/wrapped_container = wrapped + original_amount = wrapped_container.reagents?.total_volume + + wrapped.loc = user + + var/resolved = target.attackby(wrapped, user) + if(!resolved && wrapped && target) + wrapped.afterattack(target, user, TRUE) + + if(item_left_gripper(wrapped)) + clear_and_select_pocket() + return TRUE + + if(istype(wrapped, /obj/item/reagent_containers)) + var/obj/item/reagent_containers/wrapped_container = wrapped + if(wrapped_container.reagents?.total_volume != original_amount || (istype(target, /obj/item/reagent_containers))) + wrapped.loc = previous_pocket + update_ref(WEAKREF(wrapped)) + return TRUE + + wrapped.loc = previous_pocket + update_ref(WEAKREF(wrapped)) + return FALSE + +/obj/item/gripper/proc/pick_up_item(atom/target, mob/user) + if(!isitem(target)) + return FALSE + + var/obj/item/I = target + + if(!isturf(I.loc)) + return FALSE + + if(I.anchored) + to_chat(user, span_notice("You are unable to lift \the [I].")) + return FALSE + + var/obj/item/storage/internal/gripper/selected_pocket = find_empty_pocket() + if(!selected_pocket) + to_chat(user, "Your gripper is full!") + return FALSE + + if(!is_type_in_list(I, can_hold)) + to_chat(user, span_danger("Your gripper cannot hold \the [I].")) + return FALSE + + to_chat(user, "You collect \the [I].") + I.loc = selected_pocket + current_pocket = selected_pocket + update_ref(WEAKREF(I)) + return TRUE + +/obj/item/gripper/proc/handle_afterattack_special(atom/target, mob/living/user) + if(istype(target, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/A = target + if(!A.opened) + return TRUE + + if(!A.cell || !is_type_in_list(A.cell, can_hold)) + return TRUE + + if(!grab_cell(A.cell, user)) + return TRUE + + A.cell = null + A.charging = FALSE + A.update_icon() + + user.visible_message( + span_danger("[user] removes the power cell from [A]!"), + "You remove the power cell." + ) + + return TRUE + + if(isrobot(target)) + var/mob/living/silicon/robot/A = target + if(!A.opened) + return TRUE + + if(!A.cell || !is_type_in_list(A.cell, can_hold)) + return TRUE + + if(!grab_cell(A.cell, user)) + return TRUE + + var/datum/robot_component/cell_component = A.components["power cell"] + + + A.cell = null + cell_component.uninstall(TRUE) + A.update_icon() + + user.visible_message( + span_danger("[user] removes the power cell from [A]!"), + "You remove the power cell." + ) + + return TRUE + + return FALSE + +/// Returns the first empty gripper pocket, or null +/obj/item/gripper/proc/find_empty_pocket() + for(var/obj/item/storage/internal/gripper/P in pockets) + if(!LAZYLEN(P.contents)) + return P + return null + +/// Returns TRUE if item is no longer in the gripper +/obj/item/gripper/proc/item_left_gripper(obj/item/I) + if(QDELETED(I)) + return TRUE + + if(isgripperpocket(I.loc)) + return FALSE + + if(I.loc == loc) + return FALSE + + return TRUE + + +/// Sets current_pocket to a valid pocket (never an item) +/obj/item/gripper/proc/select_pocket(obj/item/storage/internal/gripper/P) + if(!P) + P = pick(pockets) + + current_pocket = P + +/// Clears the currently wrapped item and selects the pocket +/obj/item/gripper/proc/clear_and_select_pocket() + update_ref(null) + select_empty_pocket() + +/// Clears the currently wrapped item and selects next item +/obj/item/gripper/proc/clear_and_select_item() + update_ref(null) + select_next_item() + +/// Selects the first empty pocket, or a random one +/obj/item/gripper/proc/select_empty_pocket() + var/obj/item/storage/internal/gripper/P = find_empty_pocket() + select_pocket(P) + +/obj/item/gripper/proc/select_next_item() + for(var/obj/item/storage/internal/gripper/P in reverseList(pockets)) + if(!LAZYLEN(P.contents)) + continue + var/obj/item/next_item = P.contents[1] + update_ref(WEAKREF(next_item)) + return + +/obj/item/gripper/proc/drop_item(mob/user) + var/obj/item/wrapped = get_wrapped_item() + if(!wrapped) + to_chat(user, span_warning("You have nothing to drop!")) + return + + if(is_in_use(user)) + return + + if((item_left_gripper(wrapped))) + clear_and_select_pocket() + generate_icons() + return + + to_chat(user, span_notice("You drop \the [wrapped].")) + wrapped.forceMove(get_turf(user)) + clear_and_select_pocket() + generate_icons() + +//FORCES the item onto the ground and resets. +/obj/item/gripper/proc/drop_item_nm(atom/taget) + var/obj/item/wrapped = get_wrapped_item() + if(!wrapped) + return + + if((item_left_gripper(wrapped))) + clear_and_select_pocket() + generate_icons() + return + + wrapped.forceMove(taget ? taget : get_turf(src)) + clear_and_select_pocket() + generate_icons() + +/obj/item/gripper/attack(mob/living/carbon/M, mob/living/carbon/user) + if(is_in_use(user)) + return FALSE + + var/obj/item/wrapped = get_wrapped_item() + //The force of the wrapped obj gets set to zero during the attack() and afterattack(). + if(!wrapped) + return FALSE + + //If our wrapper was deleted OR it's no longer in our internal gripper storage + if(item_left_gripper(wrapped)) + update_ref(null) + return FALSE + + wrapped.attack(M, user) + //attackby reportedly gets procced by being clicked on, at least according to Anewbe. + M.attackby(wrapped, user) + + //If our wrapper was deleted OR it's no longer in our internal gripper storage + if(item_left_gripper(wrapped)) + update_ref(null) + + return TRUE + +/obj/item/gripper/update_icon() + cut_overlays() + var/obj/item/wrapped = get_wrapped_item() + if(!wrapped) + return + + // Draw the held item as a mini-image in the gripper itself + var/mutable_appearance/item_display = new(wrapped) + item_display.SetTransform(0.75, offset_y = -8) + item_display.pixel_x = 0 + item_display.pixel_y = 0 + item_display.plane = plane + item_display.layer = layer + 0.01 + add_overlay(item_display) + +//HELPER PROCS +///Use this to get what the current pocket is. Returns NULL if no +/obj/item/gripper/proc/get_wrapped_item() //done as a proc so snowflake code can be found later down the line and consolidated. + var/obj/item/wrapped = WR?.resolve() + return wrapped + +/// Consolidates material stacks by searching our pockets to see if we currently have any stacks. Done in /obj/item/stack/attackby +/obj/item/gripper/proc/consolidate_stacks(var/obj/item/stack/stack_to_consolidate) + if(!stack_to_consolidate || !istype(stack_to_consolidate, /obj/item/stack)) + return + + for(var/obj/item/storage/internal/gripper/pocket in pockets) + if(!LAZYLEN(pocket.contents)) + continue + for(var/obj/item/stack/stack in pocket.contents) + if(istype(stack_to_consolidate, stack)) + stack_to_consolidate.transfer_to(stack) + return + +/obj/item/gripper/proc/grab_cell(obj/item/cell, mob/user) + var/obj/item/storage/internal/gripper/P = find_empty_pocket() + if(!P) + to_chat(user, "Your gripper is full!") + return FALSE + + cell.add_fingerprint(user) + cell.update_icon() + cell.forceMove(P) + + current_pocket = P + update_ref(WEAKREF(cell)) + return TRUE diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station.dm b/code/modules/mob/living/silicon/robot/robot_modules/station.dm index 74a2ff64ce..1ba52a8b7e 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules/station.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules/station.dm @@ -693,6 +693,9 @@ src.modules += new /obj/item/gun/energy/robotic/taser/xeno(src) src.modules += new /obj/item/xenoarch_multi_tool(src) src.modules += new /obj/item/pickaxe/excavationdrill(src) + // Anomaly handling + src.modules += new /obj/item/analyzer(src) + src.modules += new /obj/item/assembly/signaler(src) src.emag += new /obj/item/hand_tele(src) diff --git a/code/modules/mob/living/silicon/robot/robot_simple_items.dm b/code/modules/mob/living/silicon/robot/robot_simple_items.dm index 10384e2f36..5e96dda22f 100644 --- a/code/modules/mob/living/silicon/robot/robot_simple_items.dm +++ b/code/modules/mob/living/silicon/robot/robot_simple_items.dm @@ -543,7 +543,7 @@ //Has a list of items that it can hold. var/list/can_hold = list(BASIC_GRIPPER) - var/datum/weakref/WR = null //We resolve this to get wrapped. Use get_current_pocket when possible. + var/datum/weakref/WR = null //We resolve this to get wrapped. Use get_wrapped_item when possible. var/total_pockets = 5 //How many total inventory slots we want to have in the gripper @@ -603,160 +603,19 @@ /obj/item/gripper/examine(mob/user) . = ..() - var/obj/item/wrapped = get_current_pocket() + var/obj/item/wrapped = get_wrapped_item() if(wrapped) . += span_notice("\The [src] is holding \the [wrapped].") . += wrapped.examine(user) /obj/item/gripper/item_ctrl_click(mob/user) - var/obj/item/wrapped = get_current_pocket() - if(wrapped && !is_in_use()) + var/obj/item/wrapped = get_wrapped_item() + if(wrapped && !is_in_use(user, FALSE)) wrapped.attack_self(user) - return /obj/item/gripper/click_alt(mob/user) - if(!is_in_use()) - drop_item() - return - -//This is used to check if the gripper is currently being used. -//If it is, we don't allow any other actions to be performed. -//Returns a string if we're in use explaining how. -/obj/item/gripper/proc/is_in_use() - if(gripper_in_use) - return "You are currently using the gripper on something!" - if(in_radial_menu) - return "You are currently in the radial menu! Close it to use the gripper." - return FALSE - -///Stops the gripper from being used multiple times when we're performing a do_after -/obj/item/gripper/proc/begin_using() - gripper_in_use = TRUE - -///Allows use of the gripper (and clears the weakref) after do_after is completed. Clears the weakref if the wrapped item is no longer in our borg's contents (items get moved into the borgs contents when using the gripper) -/obj/item/gripper/proc/end_using() - gripper_in_use = FALSE - var/obj/item/wrapped = get_current_pocket() - if(!wrapped) - return - //Checks two things: - //Is our wrapped object currently in our borg still? - //AND Is it not a gripper pocket? If not, reset WR. - if(wrapped.loc != loc && !isgripperpocket(wrapped.loc)) - update_ref(null) - -//This is the code that updates our pockets and decides if they should have icons or not. -//This should be called every time we use the gripper and our wrapped item is used up. -/obj/item/gripper/proc/generate_icons() - if(LAZYLEN(pockets)) - - pockets_by_name = list() - - photo_images = list() - - for(var/obj/item/storage/internal/gripper/pocket_to_check in pockets) - if(!LAZYLEN(pocket_to_check.contents)) - pockets_by_name[pocket_to_check.name] = pocket_to_check - photo_images[pocket_to_check.name] = image(icon = 'icons/effects/effects.dmi', icon_state = "nothing") - continue - var/obj/item/pocket_content = pocket_to_check.contents[1] - pockets_by_name["[pocket_to_check.name]" + "[pocket_content.name]"] = pocket_content - var/image/pocket_image = image(icon = pocket_content.icon, icon_state = pocket_content.icon_state) - if(pocket_content.color) - pocket_image.color = pocket_content.color - if(pocket_content.overlays) - for(var/overlay in pocket_content.overlays) - pocket_image.overlays += overlay - photo_images["[pocket_to_check.name]" + "[pocket_content.name]"] = pocket_image - -/obj/item/gripper/attack_self(mob/user) - . = ..(user) - if(.) - return TRUE - if(special_handling) - return FALSE - var/busy = is_in_use() - if(busy) - to_chat(user, span_danger("[busy]")) - return - generate_icons() - var/list/options = list() - - for(var/Iname in pockets_by_name) - options[Iname] = photo_images[Iname] - - var/list/choice = list() - - in_radial_menu = TRUE - choice = show_radial_menu(user, src, options, radius = 40, require_near = TRUE, autopick_single_option = FALSE) - in_radial_menu = FALSE - - var/obj/item/wrapped = get_current_pocket() - if(choice) - current_pocket = pockets_by_name[choice] - if(!isgripperpocket(current_pocket)) //The pocket we're selecting is NOT a gripper storage - if(!isgripperpocket(current_pocket.loc)) //We kept the radial menu opened, used the item, then selected it again. - get_open_pocket(set_pocket = TRUE, clear_wrapped = TRUE) //Pick the next open pocket. - else - update_ref(WEAKREF(current_pocket)) - else - update_ref(null) - else if(wrapped) - return wrapped.attack_self(user) - return ..() - -/obj/item/gripper/attackby(var/obj/item/O, var/mob/user) - var/busy = is_in_use() - if(busy) - to_chat(user, span_danger("[busy]")) - return FALSE - var/obj/item/wrapped = get_current_pocket() - if(wrapped) - wrapped.loc = src.loc //Place it in to the robot. - var/resolved = wrapped.attackby(O, user) - wrapped = get_current_pocket() //We check to see if the object exists after we do attackby. - - //The object has been deleted. Select a new pocket and stop here. - if(!wrapped) - get_open_pocket(set_pocket = TRUE, clear_wrapped = TRUE) - - //Object is not in our contents AND is not in the gripper storage still. AKA, it was moved into something or somewhere. Either way, it's not ours anymore. - else if((wrapped.loc != src.loc && !isgripperpocket(wrapped.loc))) - get_open_pocket(set_pocket = TRUE, clear_wrapped = TRUE) - - //We were not given a resolved, the object still exists, AND we hit something. Attack that thing with our wrapped item. - else if(!resolved && wrapped && O) - O.afterattack(wrapped,user,1) - wrapped = get_current_pocket() - //The object still exists, but is not in our contents OR back in the gripper storage. - if((wrapped && wrapped.loc != src.loc && !isgripperpocket(wrapped.loc))) - get_open_pocket(set_pocket = TRUE, clear_wrapped = TRUE) - else //Nothing happened to it. Just put it back into our pocket. - wrapped.loc = current_pocket - - return resolved - return ..() - -///Gets an open pocket in the gripper. -///ARGS: -///set_pocket TRUE/FALSE. If set to TRUE, will set our current_pocket to the first open pocket it finds. -///clear_wrapped TRUE/FALSE. If set to TRUE, will set WR to null. -/obj/item/gripper/proc/get_open_pocket(set_pocket = FALSE, clear_wrapped = FALSE) - var/pocket_to_select - for(var/obj/item/storage/internal/gripper/our_pocket in pockets) - if(LAZYLEN(our_pocket.contents)) - continue - pocket_to_select = our_pocket - break - if(clear_wrapped) - update_ref(null) - if(set_pocket) - if(!pocket_to_select) - pocket_to_select = pick(pockets) //If we don't have an open pocket, pick a random one. - if(!isgripperpocket(pocket_to_select)) //If we picked an item instead of a gripper storage, we need to reset WR. - update_ref(WEAKREF(pocket_to_select)) //We set WR to the pocket we selected. - current_pocket = pocket_to_select - return pocket_to_select + if(!is_in_use(user, FALSE)) + drop_item(user) /obj/item/gripper/verb/drop_gripper_item() @@ -764,231 +623,7 @@ set desc = "Release an item from your magnetic gripper." set category = "Abilities.Silicon" - drop_item() - -/obj/item/gripper/proc/drop_item() - var/obj/item/wrapped = get_current_pocket() - var/busy = is_in_use() - if(!wrapped) - to_chat(src, span_warning("You have nothing to drop!")) - return - if(busy) - to_chat(src, span_danger("[busy]")) - return - if((wrapped == current_pocket && !isgripperpocket(wrapped.loc))) //We have wrapped selected as our current_pocket AND wrapped is not in a gripper storage - get_open_pocket(set_pocket = TRUE, clear_wrapped = TRUE) - generate_icons() - return - - to_chat(src.loc, span_notice("You drop \the [wrapped].")) - wrapped.loc = get_turf(src) - get_open_pocket(set_pocket = TRUE, clear_wrapped = TRUE) - generate_icons() - -//FORCES the item onto the ground and resets. -/obj/item/gripper/proc/drop_item_nm() - var/obj/item/wrapped = get_current_pocket() - if(!wrapped) - return - if((wrapped == current_pocket && !isgripperpocket(wrapped.loc))) //We have wrapped selected as our current_pocket AND wrapped is not in a gripper storage - update_ref(null) - current_pocket = pick(pockets) - return - - wrapped.forceMove(get_turf(src)) - update_ref(null) - - //Reselect our pocket. - current_pocket = pick(pockets) - -/obj/item/gripper/attack(mob/living/carbon/M, mob/living/carbon/user) - var/busy = is_in_use() - if(busy) - to_chat(user, span_danger("[busy]")) - return FALSE - var/obj/item/wrapped = get_current_pocket() - if(wrapped) //The force of the wrapped obj gets set to zero during the attack() and afterattack(). - if((wrapped.loc != src.loc && !isgripperpocket(wrapped.loc))) //If our wrapper was deleted OR it's no longer in our internal gripper storage - update_ref(null) - return 0 - - var/old_loc = wrapped.loc - wrapped.attack(M,user) - M.attackby(wrapped, user) //attackby reportedly gets procced by being clicked on, at least according to Anewbe. - if((wrapped.loc != old_loc && !isgripperpocket(wrapped.loc))) //If our wrapper was deleted OR it's no longer in our internal gripper storage - update_ref(null) - return 1 - -/obj/item/gripper/afterattack(var/atom/target, var/mob/living/user, proximity, params) - if(!proximity) - return // This will prevent them using guns at range but adminbuse can add them directly to modules, so eh. - var/busy = is_in_use() - if(busy) - to_chat(user, span_danger("[busy]")) - return - var/current_pocket_full = FALSE - var/obj/item/wrapped = get_current_pocket() - if(wrapped == current_pocket) - current_pocket_full = TRUE - if(!wrapped && current_pocket) - if(LAZYLEN(current_pocket.contents)) - wrapped = current_pocket.contents[1] - current_pocket_full = TRUE - - if(current_pocket && !LAZYLEN(current_pocket.contents)) //We have a pocket selected and it has no contents! This means we're an item OR we need to null our wrapper! - if(isgripperpocket(current_pocket.loc) && !LAZYLEN(current_pocket.loc.contents)) //If our pocket is a gripper, AND we have no contents, WR = null - update_ref(null) - else if(!isgripperpocket(current_pocket.loc)) //If our pocket is an item and we are not in the gripper, WR = null - update_ref(null) - - if(!LAZYLEN(pockets)) //Shouldn't happen, but safety first. - to_chat(user, span_danger("Your gripper has nowhere to hold \the [target].")) - return - - var/obj/item/storage/internal/gripper/selected_pocket //Find an open pocket to use in case we're trying to pick something up. - for(var/obj/item/storage/internal/gripper/available_pocket in pockets) - if(LAZYLEN(available_pocket.contents)) - continue - selected_pocket = available_pocket - break - - if(wrapped) //Already have an item. - //Temporary put wrapped into user so target's attackby() checks pass. - var/obj/previous_pocket - if(isgripperpocket(wrapped.loc)) - previous_pocket = wrapped.loc - wrapped.loc = user - - //Pass the attack on to the target. This might delete/relocate wrapped. - var/resolved = target.attackby(wrapped,user) - if(!resolved && wrapped && target) - wrapped.afterattack(target,user,1) - - if(!WR) //We put our wrapped thing INTO something! - get_open_pocket(set_pocket = TRUE, clear_wrapped = TRUE) - return - //If we had a previous pocket and the wrapped isn't put into something, put it back in our pocket. - else if((previous_pocket && wrapped.loc == user)) - wrapped.loc = previous_pocket - else - get_open_pocket(set_pocket = TRUE, clear_wrapped = TRUE) - return - else if(current_pocket_full) //Pocket is full. No grabbing more things. - to_chat(user, "Your gripper is currently full! You can't pick anything else up!") - return - - else if(isitem(target)) //Check that we're not pocketing a mob. - - //...and that the item is not in a container. - if(!isturf(target.loc)) - return - - var/obj/item/I = target - - if(I.anchored) - to_chat(user,span_notice("You are unable to lift \the [I] from \the [I.loc].")) - return - - //Check if the item is blacklisted. - var/grab = 0 - for(var/typepath in can_hold) - if(istype(I,typepath)) - grab = 1 - break - - //We can grab the item, finally. - if(grab) - to_chat(user, "You collect \the [I].") - I.loc = selected_pocket - if(selected_pocket == current_pocket) //If we put the item into our current pocket, we need to set WR to the item. - update_ref(WEAKREF(I)) - current_pocket = I - return - else - to_chat(user, span_danger("Your gripper cannot hold \the [target].")) - - else if(istype(target,/obj/machinery/power/apc)) - var/obj/machinery/power/apc/A = target - if(A.opened) - if(A.cell && is_type_in_list(A.cell, can_hold)) - - current_pocket = A.cell - - A.cell.add_fingerprint(user) - A.cell.update_icon() - A.update_icon() - A.cell.forceMove(current_pocket) - current_pocket = A.cell - WR = WEAKREF(current_pocket) - A.cell = null - - A.charging = 0 - A.update_icon() - - user.visible_message(span_danger("[user] removes the power cell from [A]!"), "You remove the power cell.") - - else if(isrobot(target)) - var/mob/living/silicon/robot/A = target - if(A.opened) - if(A.cell && is_type_in_list(A.cell, can_hold)) - var/datum/robot_component/cell_component = A.components["power cell"] - A.cell.update_icon() - A.cell.add_fingerprint(user) - A.cell.forceMove(current_pocket) - current_pocket = A.cell - WR = WEAKREF(current_pocket) - A.cell = null - cell_component.uninstall(TRUE) - A.update_icon() - - user.visible_message(span_danger("[user] removes the power cell from [A]!"), "You remove the power cell.") - -/obj/item/gripper/proc/update_ref(var/datum/weakref/new_ref) - var/had_item = get_current_pocket() - WR = new_ref - var/holding_item = get_current_pocket() - // Feedback - update_icon() - if(had_item && !holding_item) // Dropped - our_robot.playsound_local(get_turf(our_robot), 'sound/machines/click.ogg', 50) - else if(holding_item && !had_item || (holding_item != had_item)) // Pickup or change item - our_robot.playsound_local(get_turf(our_robot), 'sound/machines/click2.ogg', 50) - -/obj/item/gripper/update_icon() - cut_overlays() - var/obj/item/wrapped = get_current_pocket() - if(!wrapped) - return - // Draw the held item as a mini-image in the gripper itself - var/mutable_appearance/item_display = new(wrapped) - item_display.SetTransform(0.75, offset_y = -8) - item_display.pixel_x = 0 - item_display.pixel_y = 0 - item_display.plane = plane - item_display.layer = layer + 0.01 - add_overlay(item_display) - -//HELPER PROCS -///Use this to get what the current pocket is. Returns NULL if no -/obj/item/gripper/proc/get_current_pocket() //done as a proc so snowflake code can be found later down the line and consolidated. - var/obj/item/wrapped = WR?.resolve() - return wrapped - -/// Consolidates material stacks by searching our pockets to see if we currently have any stacks. Done in /obj/item/stack/attackby -/obj/item/gripper/proc/consolidate_stacks(var/obj/item/stack/stack_to_consolidate) - if(!stack_to_consolidate || !istype(stack_to_consolidate, /obj/item/stack)) - return - var/stacked = FALSE //So we can break the for loop 2 forloops deep. - for(var/obj/item/storage/internal/gripper/pocket in pockets) - if(stacked) //We've stacked our item, break! - break - - if(LAZYLEN(pocket.contents)) - for(var/obj/item/stack/stack in pocket.contents) - if(istype(stack_to_consolidate, stack)) - stack_to_consolidate.transfer_to(stack) - stacked = TRUE - break + drop_item(src.loc) //Different types of grippers! diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index 5e94bc7cea..d8713889e1 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -177,6 +177,13 @@ . += "It has a power rating of [maxcharge]." . += "The charge meter reads [round(src.percent() )]%." +/obj/item/cell/attack(mob/living/M, mob/living/user, var/target_zone, var/attack_modifier) + if(isrobot(M)) + var/mob/living/silicon/robot/target = M + if(target.opened) + return FALSE + ..() + /obj/item/cell/attackby(obj/item/W, mob/user) ..() if(istype(W, /obj/item/reagent_containers/syringe)) diff --git a/code/modules/reagents/machinery/grinder.dm b/code/modules/reagents/machinery/grinder.dm index 7d5e38ffae..2e9d661871 100644 --- a/code/modules/reagents/machinery/grinder.dm +++ b/code/modules/reagents/machinery/grinder.dm @@ -109,7 +109,7 @@ if(istype(O,/obj/item/gripper)) var/obj/item/gripper/B = O //B, for Borg. - var/obj/item/wrapped = B.get_current_pocket() + var/obj/item/wrapped = B.get_wrapped_item() if(!wrapped) to_chat(user, "\The [B] is not holding anything.") return 0 diff --git a/code/modules/refinery/core/industrial_reagent_grinder.dm b/code/modules/refinery/core/industrial_reagent_grinder.dm index 2d63390770..161409ea4c 100644 --- a/code/modules/refinery/core/industrial_reagent_grinder.dm +++ b/code/modules/refinery/core/industrial_reagent_grinder.dm @@ -62,7 +62,7 @@ // Borgos! if(istype(O,/obj/item/gripper)) var/obj/item/gripper/B = O //B, for Borg. - var/obj/item/wrapped = B.get_current_pocket() + var/obj/item/wrapped = B.get_wrapped_item() if(!wrapped) to_chat(user, "\The [B] is not holding anything.") return FALSE diff --git a/code/modules/surgery/implant.dm b/code/modules/surgery/implant.dm index 93e6ae34db..d6afd11b84 100644 --- a/code/modules/surgery/implant.dm +++ b/code/modules/surgery/implant.dm @@ -135,7 +135,7 @@ if(istype(user,/mob/living/silicon/robot)) if(istype(tool, /obj/item/gripper)) var/obj/item/gripper/gripper = tool - var/obj/item/wrapped = gripper.get_current_pocket() + var/obj/item/wrapped = gripper.get_wrapped_item() if(wrapped) tool = wrapped else @@ -154,7 +154,7 @@ var/obj/item/organ/external/affected = target.get_organ(target_zone) if(isrobot(user) && istype(tool, /obj/item/gripper)) var/obj/item/gripper/G = tool - tool = G.get_current_pocket() + tool = G.get_wrapped_item() user.visible_message(span_notice("[user] starts putting \the [tool] inside [target]'s [get_cavity(affected)] cavity."), \ span_notice("You start putting \the [tool] inside [target]'s [get_cavity(affected)] cavity.") ) //Nobody will probably ever see this, but I made these two blue. ~CK user.balloon_alert_visible("starts putting \the [tool] inside [target]'s [get_cavity(affected)]", "putting \the [tool] inside \the [get_cavity(affected)]") @@ -165,7 +165,7 @@ var/obj/item/organ/external/chest/affected = target.get_organ(target_zone) if(isrobot(user) && istype(tool, /obj/item/gripper)) var/obj/item/gripper/G = tool - tool = G.get_current_pocket() + tool = G.get_wrapped_item() G.drop_item_nm() else user.drop_item() diff --git a/code/modules/tables/interactions.dm b/code/modules/tables/interactions.dm index efb9ef3a73..28aa5b081f 100644 --- a/code/modules/tables/interactions.dm +++ b/code/modules/tables/interactions.dm @@ -84,7 +84,7 @@ return ..() -/obj/structure/table/attackby(obj/item/W as obj, mob/user as mob, var/hit_modifier, var/click_parameters) +/obj/structure/table/attackby(obj/item/W, mob/user, hit_modifier, click_parameters) if (!W) return // Handle harm intent grabbing/tabling. @@ -128,7 +128,13 @@ // Handle dismantling or placing things on the table from here on. if(isrobot(user)) - return + if(istype(W, /obj/item/gripper)) + var/obj/item/gripper/robot_gripper = W + var/obj/item/item_to_drop = robot_gripper.get_wrapped_item() + robot_gripper.drop_item_nm(loc) + item_to_drop.do_drop_animation(user) + auto_align(item_to_drop, click_parameters) + return FALSE if(W.loc != user) // This should stop mounted modules ending up outside the module. return diff --git a/vorestation.dme b/vorestation.dme index c43d41ed4f..39d3260815 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -3716,6 +3716,7 @@ #include "code\modules\mob\living\silicon\robot\robot_animation_vr.dm" #include "code\modules\mob\living\silicon\robot\robot_bellies.dm" #include "code\modules\mob\living\silicon\robot\robot_damage.dm" +#include "code\modules\mob\living\silicon\robot\robot_gripper_handling.dm" #include "code\modules\mob\living\silicon\robot\robot_items.dm" #include "code\modules\mob\living\silicon\robot\robot_movement.dm" #include "code\modules\mob\living\silicon\robot\robot_remote_control.dm"