diff --git a/_maps/RandomRuins/SpaceRuins/syndicate_depot.dmm b/_maps/RandomRuins/SpaceRuins/syndicate_depot.dmm index a3f4699ad7e4d..877d566283886 100644 --- a/_maps/RandomRuins/SpaceRuins/syndicate_depot.dmm +++ b/_maps/RandomRuins/SpaceRuins/syndicate_depot.dmm @@ -3349,11 +3349,6 @@ /obj/item/circuitboard/machine/quantumpad, /obj/item/storage/toolbox/electrical, /obj/item/circuitboard/machine/spaceship_navigation_beacon, -/obj/item/manipulator_filter, -/obj/item/manipulator_filter, -/obj/item/manipulator_filter, -/obj/item/manipulator_filter/cargo, -/obj/item/manipulator_filter/cargo, /obj/item/circuitboard/machine/big_manipulator, /obj/item/circuitboard/machine/big_manipulator, /obj/item/circuitboard/machine/big_manipulator, diff --git a/code/__DEFINES/wires.dm b/code/__DEFINES/wires.dm index 6fa56bd69681a..4b1038d8e8ad8 100644 --- a/code/__DEFINES/wires.dm +++ b/code/__DEFINES/wires.dm @@ -26,6 +26,12 @@ #define WIRE_DENY "Scan Fail" #define WIRE_DISABLE "Disable" #define WIRE_DISARM "Disarm" +#define WIRE_ON "On" +#define WIRE_DROP "Drop" +#define WIRE_ITEM_TYPE "Item Type" +#define WIRE_CHANGE_MODE "Change Mode" +#define WIRE_ONE_PRIORITY_BUTTON "One Priority Button" +#define WIRE_THROW_RANGE "Throw Range" #define WIRE_DUD_PREFIX "__dud" #define WIRE_HACK "Hack" #define WIRE_IDSCAN "ID Scan" diff --git a/code/__HELPERS/matrices.dm b/code/__HELPERS/matrices.dm index 0a61ea86eb77e..2e8ed10c24e9a 100644 --- a/code/__HELPERS/matrices.dm +++ b/code/__HELPERS/matrices.dm @@ -127,6 +127,10 @@ c f 1 /matrix/proc/get_y_shift() . = f +///The angle of this matrix +/matrix/proc/get_angle() + . = -ATAN2(a,d) + ///////////////////// // COLOUR MATRICES // ///////////////////// diff --git a/code/datums/elements/deliver_first.dm b/code/datums/elements/deliver_first.dm index 65aceca26dd55..0fb83a2545603 100644 --- a/code/datums/elements/deliver_first.dm +++ b/code/datums/elements/deliver_first.dm @@ -28,7 +28,6 @@ RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) RegisterSignal(target, COMSIG_ATOM_EMAG_ACT, PROC_REF(on_emag)) RegisterSignal(target, COMSIG_CLOSET_POST_OPEN, PROC_REF(on_post_open)) - RegisterSignal(target, COMSIG_FILTER_CHECK, PROC_REF(on_filter_check)) ADD_TRAIT(target, TRAIT_BANNED_FROM_CARGO_SHUTTLE, REF(src)) //registers pre_open when appropriate area_check(target) @@ -103,9 +102,4 @@ playsound(src, SFX_SPARKS, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) target.RemoveElement(/datum/element/deliver_first, goal_area_type, payment) -/datum/element/deliver_first/proc/on_filter_check(obj/structure/closet/target, list/filter_locations) - var/name = departmental_destination_to_tag(goal_area_type) - if(name in filter_locations) - return TRUE - return FALSE #undef DENY_SOUND_COOLDOWN diff --git a/code/datums/greyscale/config_types/greyscale_configs.dm b/code/datums/greyscale/config_types/greyscale_configs.dm index 00db5b2c356c6..89d98cfc36100 100644 --- a/code/datums/greyscale/config_types/greyscale_configs.dm +++ b/code/datums/greyscale/config_types/greyscale_configs.dm @@ -1258,16 +1258,6 @@ icon_file = 'monkestation/icons/mob/clothing/head_32x48.dmi' json_config = 'code/datums/greyscale/json_configs/playbunny_ears_worn.json' -/datum/greyscale_config/big_manipulator - name = "Big Manipulator" - icon_file = 'monkestation/code/modules/factory_type_beat/icons/big_manipulator_core.dmi' - json_config = 'code/datums/greyscale/json_configs/big_manipulator.json' - -/datum/greyscale_config/manipulator_hand - name = "Manipulator Hand" - icon_file = 'monkestation/code/modules/factory_type_beat/icons/big_manipulator_hand.dmi' - json_config = 'code/datums/greyscale/json_configs/manipulator_hand.json' - /datum/greyscale_config/linjacket name = "Tassled Jacket" icon_file = 'monkestation/icons/obj/clothing/necks.dmi' @@ -1332,3 +1322,13 @@ name = "green_jester_shoes" icon_file = 'monkestation/icons/mob/clothing/feet.dmi' json_config = 'code/datums/greyscale/json_configs/green_jester_shoes_worn.json' + +/datum/greyscale_config/big_manipulator + name = "Big Manipulator" + icon_file = 'icons/obj/machines/big_manipulator_parts/big_manipulator_core.dmi' + json_config = 'code/datums/greyscale/json_configs/big_manipulator.json' + +/datum/greyscale_config/manipulator_arm + name = "Manipulator Arm" + icon_file = 'icons/obj/machines/big_manipulator_parts/big_manipulator_hand.dmi' + json_config = 'code/datums/greyscale/json_configs/manipulator_arm.json' diff --git a/code/datums/greyscale/json_configs/manipulator_arm.json b/code/datums/greyscale/json_configs/manipulator_arm.json new file mode 100644 index 0000000000000..ff1f927795707 --- /dev/null +++ b/code/datums/greyscale/json_configs/manipulator_arm.json @@ -0,0 +1,15 @@ +{ + "hand": [ + { + "type": "icon_state", + "icon_state": "hand", + "blend_mode": "overlay" + }, + { + "type": "icon_state", + "icon_state": "hand_colour", + "blend_mode": "overlay", + "color_ids": [1] + } + ] +} diff --git a/code/datums/wires/big_manipulator.dm b/code/datums/wires/big_manipulator.dm new file mode 100644 index 0000000000000..9ad7dbeb3a2e2 --- /dev/null +++ b/code/datums/wires/big_manipulator.dm @@ -0,0 +1,43 @@ +/datum/wires/big_manipulator + holder_type = /obj/machinery/big_manipulator + proper_name = "Big_Manipulator" + +/datum/wires/big_manipulator/New(atom/holder) + wires = list( + WIRE_ON, + WIRE_DROP, + WIRE_ITEM_TYPE, + WIRE_CHANGE_MODE, + WIRE_ONE_PRIORITY_BUTTON, + WIRE_THROW_RANGE + ) + return ..() + +/datum/wires/big_manipulator/interactable(mob/user) + var/obj/machinery/big_manipulator/holder_manipulator = holder + + return holder_manipulator.panel_open ? ..() : FALSE + +/datum/wires/big_manipulator/get_status() + var/obj/machinery/big_manipulator/holder_manipulator = holder + var/list/status = list() + status += "The big light bulb [holder_manipulator.power_access_wire_cut ? "is off" : "is glowing [holder_manipulator.on ? "green" : "red"]"]." + status += "The small light bulb [holder_manipulator.held_object ? "is glowing bright green" : "is off"]." + status += "The number on the display shows [length(holder_manipulator.tasks)]." + return status + +/datum/wires/big_manipulator/on_pulse(wire) + var/obj/machinery/big_manipulator/holder_manipulator = holder + switch(wire) + if(WIRE_ON) + holder_manipulator.try_press_on(usr) + if(WIRE_DROP) + holder_manipulator.drop_held_atom() + +/datum/wires/big_manipulator/on_cut(wire, mend, source) + var/obj/machinery/big_manipulator/holder_manipulator = holder + if(wire == WIRE_ON) + if(mend) + holder_manipulator.power_access_wire_cut = FALSE + return + holder_manipulator.power_access_wire_cut = TRUE diff --git a/code/game/machinery/big_manipulator/_defines.dm b/code/game/machinery/big_manipulator/_defines.dm new file mode 100644 index 0000000000000..b04d1b90bb601 --- /dev/null +++ b/code/game/machinery/big_manipulator/_defines.dm @@ -0,0 +1,62 @@ +// How should the manipulator interact with the point +#define INTERACT_DROP "DROP" +#define INTERACT_USE "USE" +#define INTERACT_THROW "THROW" + +// What should be picked up from the point +#define TAKE_ITEMS 1 +#define TAKE_CLOSETS 2 +#define TAKE_HUMANS 3 + +#define MIN_SPEED_MULTIPLIER_TIER_1 0.5 +#define MIN_SPEED_MULTIPLIER_TIER_2 0.4 +#define MIN_SPEED_MULTIPLIER_TIER_3 0.3 +#define MIN_SPEED_MULTIPLIER_TIER_4 0.1 + +#define MAX_SPEED_MULTIPLIER_TIER_1 2 +#define MAX_SPEED_MULTIPLIER_TIER_2 3 +#define MAX_SPEED_MULTIPLIER_TIER_3 5 +#define MAX_SPEED_MULTIPLIER_TIER_4 6 + +#define MAX_TASKS_TIER_1 6 +#define MAX_TASKS_TIER_2 12 +#define MAX_TASKS_TIER_3 24 +#define MAX_TASKS_TIER_4 32 + + +// How should the worker interact with the point +#define WORKER_SINGLE_USE "SINGLE TIME" +#define WORKER_EMPTY_USE "EMPTY HAND" +#define WORKER_NORMAL_USE "NORMAL" + +#define BASE_POWER_USAGE 0.2 +#define BASE_INTERACTION_TIME 0.3 SECONDS + +/// How long will the manipulator wait if there's nothing to do +#define CYCLE_SKIP_TIMEOUT 1 SECONDS + +// How should overflow should be handled +#define POINT_OVERFLOW_ALLOWED "ALLOW" +#define POINT_OVERFLOW_FILTERS "TO FILTERS" +#define POINT_OVERFLOW_HELD "TO HELD" +#define POINT_OVERFLOW_FORBIDDEN "FORBID" + +// What should the manipulator do after there's nothing else to interact with on this point anymore +#define POST_INTERACTION_DROP_AT_POINT "AT DROPOFF" +#define POST_INTERACTION_DROP_AT_MACHINE "AT MACHINE" +#define POST_INTERACTION_DROP_NEXT_FITTING "AT ANY FITTING" +#define POST_INTERACTION_WAIT "CONTINUE" + + +#define PICKUP_EAGER "Always Pick Up" +#define PICKUP_CAN_WAIT "Wait For Suiting" + +#define TASK_TYPE_PICKUP "pickup" +#define TASK_TYPE_DROP "drop" +#define TASK_TYPE_THROW "throw" +#define TASK_TYPE_USE "use" +#define TASK_TYPE_INTERACT "interact" +#define TASK_TYPE_WAIT "wait" + +#define TASKING_SEQUENTIAL "Sequential" +#define TASKING_STRICT "Strict order" diff --git a/code/game/machinery/big_manipulator/big_manipulator.dm b/code/game/machinery/big_manipulator/big_manipulator.dm new file mode 100644 index 0000000000000..b805e0338744a --- /dev/null +++ b/code/game/machinery/big_manipulator/big_manipulator.dm @@ -0,0 +1,908 @@ +/obj/machinery/big_manipulator + name = "big manipulator" + desc = "Operates different objects. Truly, a groundbreaking innovation..." + icon = 'icons/obj/machines/big_manipulator_parts/big_manipulator_core.dmi' + icon_state = "core" + density = TRUE + circuit = /obj/item/circuitboard/machine/big_manipulator + greyscale_colors = "#d8ce13" + greyscale_config = /datum/greyscale_config/big_manipulator + + /// Is the manipulator turned on? + var/on = FALSE + /// Was the next cycle already scheduled? + var/next_cycle_scheduled = FALSE + + /// How quickly the manipulator will process it's actions. + var/speed_multiplier = 1 + var/min_speed_multiplier = MIN_SPEED_MULTIPLIER_TIER_1 + var/max_speed_multiplier = MAX_SPEED_MULTIPLIER_TIER_1 + + /// The object inside the manipulator. + var/datum/weakref/held_object = null + /// The chimp worker that uses the manipulator (handles USE cases). + var/datum/weakref/monkey_worker = null + /// Weakref to the ID that locked this manipulator. + var/datum/weakref/id_lock = null + /// Inserted manipulator task disk. + var/obj/item/disk/manipulator/task_disk = null + /// The manipulator's arm. + var/obj/effect/big_manipulator_arm/manipulator_arm = null + /// Is the power access wire cut? Disables the power button if `TRUE`. + var/power_access_wire_cut = FALSE + + /// How many tasks total we can have. + var/interaction_point_limit = MAX_TASKS_TIER_1 + + /// A list of tasks for the manipulator. + var/list/tasks = list() + /// The task we're currently working on. + var/datum/manipulator_task/current_task + + /// Is the manipulator in the process of stopping? + var/stopping = FALSE + /// Is the manipulator waiting for a turf signal to retry? + var/waiting_for_signal = FALSE + /// Turfs we registered enter/exit signals on while waiting. + var/list/signal_turfs = list() + + /// Which tasking scenario we use for iterating tasks. + var/tasking_strategy = TASKING_SEQUENTIAL + /// Tasking strategy instance. + var/datum/tasking_strategy/master_tasking + +/// Attempts to find a suitable turf near the manipulator for creating a cargo task. +/obj/machinery/big_manipulator/proc/find_suitable_turf() + var/turf/base = get_turf(src) + for(var/turf/checked_turf in orange(base, 1)) + if(!isclosedturf(checked_turf)) + return checked_turf + return null + +/// Attempts to create a new task and assign it to the list. +/obj/machinery/big_manipulator/proc/create_new_task(mob/user, task_type, turf/new_turf) + if(length(tasks) >= interaction_point_limit) + balloon_alert(user, "task limit reached!") + return FALSE + + var/datum/stock_part/manipulator/locate_servo = locate() in component_parts + var/manipulator_tier = locate_servo ? locate_servo.tier : 1 + + var/datum/manipulator_task/new_task + var/needs_turf = task_type in list(TASK_TYPE_PICKUP, TASK_TYPE_DROP, TASK_TYPE_THROW, TASK_TYPE_USE, TASK_TYPE_INTERACT) + + if(needs_turf) + if(!new_turf) + new_turf = find_suitable_turf() + if(!new_turf) + return FALSE + + switch(task_type) + if(TASK_TYPE_PICKUP) + new_task = new /datum/manipulator_task/cargo/pickup(new_turf, manipulator_tier) + if(TASK_TYPE_DROP) + new_task = new /datum/manipulator_task/cargo/dropoff_base/drop(new_turf, manipulator_tier) + if(TASK_TYPE_THROW) + new_task = new /datum/manipulator_task/cargo/dropoff_base/throw(new_turf, manipulator_tier) + if(TASK_TYPE_USE) + new_task = new /datum/manipulator_task/cargo/dropoff_base/use(new_turf, manipulator_tier) + if(TASK_TYPE_INTERACT) + new_task = new /datum/manipulator_task/cargo/interact(new_turf, manipulator_tier) + if(TASK_TYPE_WAIT) + new_task = new /datum/manipulator_task/simple/wait() + + if(QDELETED(new_task)) + return FALSE + + tasks += new_task + + if(istype(new_task, /datum/manipulator_task/cargo)) + var/datum/manipulator_task/cargo/cargo_task = new_task + cargo_task.offset_dx = new_turf.x - x + cargo_task.offset_dy = new_turf.y - y + + if((obj_flags & EMAGGED) && istype(new_task, /datum/manipulator_task/cargo)) + var/datum/manipulator_task/cargo/cargo_task = new_task + cargo_task.type_filters += /mob/living + + return new_task + +/obj/machinery/big_manipulator/Initialize(mapload) + . = ..() + create_manipulator_arm() + process_upgrades() + if(on) + toggle_power_state(null) + set_wires(new /datum/wires/big_manipulator(src)) + register_context() + + update_strategies() + +/// Checks the component tiers, adjusting the properties of the manipulator. +/obj/machinery/big_manipulator/proc/process_upgrades() + var/datum/stock_part/manipulator/locate_servo = locate() in component_parts + if(!locate_servo) + return + + var/manipulator_tier = locate_servo.tier + switch(manipulator_tier) + if(-INFINITY to 1) + min_speed_multiplier = MIN_SPEED_MULTIPLIER_TIER_1 + max_speed_multiplier = MAX_SPEED_MULTIPLIER_TIER_1 + interaction_point_limit = MAX_TASKS_TIER_1 + set_greyscale(COLOR_YELLOW) + manipulator_arm?.set_greyscale(COLOR_YELLOW) + if(2) + min_speed_multiplier = MIN_SPEED_MULTIPLIER_TIER_2 + max_speed_multiplier = MAX_SPEED_MULTIPLIER_TIER_2 + interaction_point_limit = MAX_TASKS_TIER_2 + set_greyscale(COLOR_ORANGE) + manipulator_arm?.set_greyscale(COLOR_ORANGE) + if(3) + min_speed_multiplier = MIN_SPEED_MULTIPLIER_TIER_3 + max_speed_multiplier = MAX_SPEED_MULTIPLIER_TIER_3 + interaction_point_limit = MAX_TASKS_TIER_3 + set_greyscale(COLOR_RED) + manipulator_arm?.set_greyscale(COLOR_RED) + if(4 to INFINITY) + min_speed_multiplier = MIN_SPEED_MULTIPLIER_TIER_4 + max_speed_multiplier = MAX_SPEED_MULTIPLIER_TIER_4 + interaction_point_limit = MAX_TASKS_TIER_4 + set_greyscale(COLOR_PURPLE) + manipulator_arm?.set_greyscale(COLOR_PURPLE) + + active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * BASE_POWER_USAGE * manipulator_tier + + for(var/datum/manipulator_task/cargo/cargo_task in tasks) + cargo_task.interaction_priorities = cargo_task.fill_priority_list(manipulator_tier) + +/obj/machinery/big_manipulator/examine(mob/user) + . = ..() + var/mob/monkey_resolve = monkey_worker?.resolve() + if(!isnull(monkey_resolve)) + . += "You can see a poor [monkey_resolve.name] buckled to [src]. You wonder if it's getting paid enough." + +/obj/machinery/big_manipulator/attack_hand_secondary(mob/living/user, list/modifiers) + try_press_on(user) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/machinery/big_manipulator/click_alt(mob/user) + eject_task_disk(user) + return CLICK_ACTION_SUCCESS + +/obj/machinery/big_manipulator/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + + context[SCREENTIP_CONTEXT_RMB] = "Toggle" + context[SCREENTIP_CONTEXT_ALT_LMB] = "Eject disk" + + if(isnull(held_item)) + context[SCREENTIP_CONTEXT_LMB] = panel_open ? "Interact with wires" : "Open UI" + return CONTEXTUAL_SCREENTIP_SET + + if(held_item.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Una" : "A"]nchor" + return CONTEXTUAL_SCREENTIP_SET + if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel" + return CONTEXTUAL_SCREENTIP_SET + if(held_item.tool_behaviour == TOOL_CROWBAR && panel_open) + context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" + return CONTEXTUAL_SCREENTIP_SET + if(is_wire_tool(held_item) && panel_open) + context[SCREENTIP_CONTEXT_LMB] = "Interact with wires" + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/big_manipulator/Destroy(force) + if(task_disk) + task_disk.forceMove(drop_location()) + task_disk = null + unregister_task_turf_signals() + QDEL_NULL(manipulator_arm) + QDEL_LIST(tasks) + id_lock = null + return ..() + +/obj/machinery/big_manipulator/Exited(atom/movable/gone, direction) + . = ..() + if(isnull(monkey_worker)) + return + + var/mob/living/carbon/human/species/monkey/poor_monkey = monkey_worker.resolve() + if(gone != poor_monkey) + return + + vis_contents -= poor_monkey + poor_monkey.transform = matrix() + monkey_worker = null + + +/// Removes an invalid task from the list. +/obj/machinery/big_manipulator/proc/remove_invalid_task(datum/manipulator_task/task) + if(!task) + return + tasks -= task + qdel(task) + +/obj/machinery/big_manipulator/emag_act(mob/user, obj/item/card/emag/emag_card) + . = ..() + if(obj_flags & EMAGGED) + return FALSE + + balloon_alert(user, "overloaded") + obj_flags |= EMAGGED + + for(var/datum/manipulator_task/cargo/cargo_task in tasks) + cargo_task.type_filters += /mob/living + + return TRUE + +/obj/machinery/big_manipulator/wrench_act(mob/living/user, obj/item/tool) + . = ..() + default_unfasten_wrench(user, tool, time = 1 SECONDS) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/big_manipulator/can_be_unfasten_wrench(mob/user, silent) + if(on || stopping) + to_chat(user, span_warning("[src] is activated!")) + return FAILED_UNFASTEN + return ..() + +/obj/machinery/big_manipulator/default_unfasten_wrench(mob/user, obj/item/wrench, time) + . = ..() + if(. == SUCCESSFUL_UNFASTEN) + if(anchored) + validate_all_tasks() + +/obj/machinery/big_manipulator/screwdriver_act(mob/living/user, obj/item/tool) + return default_deconstruction_screwdriver(user, "core", "core", tool) + +/obj/machinery/big_manipulator/crowbar_act(mob/living/user, obj/item/tool) + return default_deconstruction_crowbar(tool) + +/obj/machinery/big_manipulator/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(user.istate & ISTATE_HARM) + return NONE + + if(istype(tool, /obj/item/disk/manipulator)) + if(on || stopping) + balloon_alert(user, "turn it off first!") + return ITEM_INTERACT_BLOCKING + if(task_disk) + task_disk.forceMove(drop_location()) + task_disk = null + if(!user.transferItemToLoc(tool, src)) + return ITEM_INTERACT_BLOCKING + task_disk = tool + balloon_alert(user, "disk inserted") + SStgui.update_uis(src) + return ITEM_INTERACT_SUCCESS + + if(!panel_open || !is_wire_tool(tool)) + return NONE + wires.interact(user) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/big_manipulator/RefreshParts() + . = ..() + process_upgrades() + +/obj/machinery/big_manipulator/mouse_drop_dragged(atom/drop_point, mob/user, src_location, over_location, params) + if(on || stopping) + balloon_alert(user, "turn it off first!") + return + + var/mob/living/carbon/human/species/monkey/poor_monkey = monkey_worker?.resolve() + if(!poor_monkey) + return + + balloon_alert(user, "trying to unbuckle...") + if(!do_after(user, 3 SECONDS, src)) + balloon_alert(user, "interrupted") + return + + balloon_alert(user, "unbuckled") + poor_monkey.drop_all_held_items() + poor_monkey.forceMove(drop_location()) + +/obj/machinery/big_manipulator/mouse_drop_receive(atom/monkey, mob/user, params) + if(on || stopping) + balloon_alert(user, "turn it off first!") + return + + if(monkey_worker?.resolve()) + return + + if(!ismonkey(monkey)) + return + + var/mob/living/carbon/human/species/monkey/poor_monkey = monkey + if(poor_monkey.mind) + balloon_alert(user, "too smart!") + return + + poor_monkey.balloon_alert(user, "trying to buckle...") + if(!do_after(user, 3 SECONDS, poor_monkey)) + poor_monkey.balloon_alert(user, "interrupted") + return + + balloon_alert(user, "buckled") + monkey_worker = WEAKREF(poor_monkey) + poor_monkey.drop_all_held_items() + poor_monkey.forceMove(src) + vis_contents += poor_monkey + poor_monkey.dir = manipulator_arm.dir + poor_monkey.transform = manipulator_arm.transform + +/obj/machinery/big_manipulator/attackby(obj/item/some_item, mob/user, params) + . = ..() + if(!isidcard(some_item)) + return + + var/obj/item/card/id/clicked_by_this_id = some_item + + if(!id_lock) + id_lock = WEAKREF(clicked_by_this_id) + balloon_alert(user, "successfully locked") + return + var/obj/item/card/id/resolve_id = id_lock.resolve() + if(clicked_by_this_id != resolve_id) + balloon_alert(user, "locked by another id") + return + id_lock = null + balloon_alert(user, "successfully unlocked") + +/// Attaching the arm effect to the core. +/obj/machinery/big_manipulator/proc/create_manipulator_arm() + manipulator_arm = new /obj/effect/big_manipulator_arm(src) + manipulator_arm.dir = NORTH + manipulator_arm.target_dir = NORTH + vis_contents += manipulator_arm + +/obj/machinery/big_manipulator/proc/toggle_power_state(mob/user) + var/newly_on = !on + + if(!user) + on = newly_on + return + + if(newly_on) + if(!powered()) + balloon_alert(user, "no power!") + return + + if(!anchored) + balloon_alert(user, "anchor first!") + return + + validate_all_tasks() + + on = newly_on + SStgui.update_uis(src) + try_kickstart(user) + + else + drop_held_atom() + on = newly_on + next_cycle_scheduled = FALSE + if(current_task != null && !stopping) + stopping = TRUE + addtimer(CALLBACK(src, PROC_REF(complete_stopping_task)), 1 SECONDS) + else + stopping = FALSE + unregister_task_turf_signals() + waiting_for_signal = FALSE + SStgui.update_uis(src) + +/// Validates all cargo tasks, removing those on closed turfs. +/obj/machinery/big_manipulator/proc/validate_all_tasks() + for(var/datum/manipulator_task/cargo/cargo_task in tasks) + if(!cargo_task.is_valid()) + tasks -= cargo_task + qdel(cargo_task) + +/// Attempts to press the power button. +/obj/machinery/big_manipulator/proc/try_press_on(mob/living/carbon/human/user) + if(power_access_wire_cut) + balloon_alert(user, "unresponsive!") + return + + if(stopping) + balloon_alert(user, "stopping in progress!") + return + + toggle_power_state(user) + if(on) + balloon_alert(user, "activated") + else + balloon_alert(user, "deactivated") + +/obj/machinery/big_manipulator/proc/_collect_filter_names(list/filters) + var/list/names = list() + for(var/atom/f as anything in filters) + names += initial(f.name) + return names + +/obj/machinery/big_manipulator/proc/_collect_priorities(list/priorities) + var/list/out = list() + for(var/datum/manipulator_priority/pr in priorities) + var/list/entry = list() + entry["name"] = pr.name + entry["active"] = pr.active + out += list(entry) + return out + +/obj/machinery/big_manipulator/ui_act(action, params, datum/tgui/ui) + . = ..() + if(.) + return + + switch(action) + if("run_cycle") + try_press_on(ui.user) + return TRUE + + if("drop_held_atom") + drop_held_atom() + return TRUE + + if("create_task") + create_new_task(ui.user, params["task_type"]) + maybe_wake() + return TRUE + + if("reset_tasking_index") + master_tasking.current_index = 1 + balloon_alert(ui.user, "tasking index reset") + maybe_wake() + return TRUE + + if("cycle_tasking_strategy") + var/new_strategy = params["new_strategy"] + if(new_strategy in list(TASKING_SEQUENTIAL, TASKING_STRICT)) + tasking_strategy = new_strategy + update_strategies() + maybe_wake() + return TRUE + + if("adjust_interaction_speed") + var/new_speed = text2num(params["new_speed"]) + if(isnull(new_speed)) + return FALSE + speed_multiplier = clamp(new_speed, min_speed_multiplier, max_speed_multiplier) + return TRUE + + if("unbuckle") + if(monkey_worker) + var/mob/living/carbon/human/species/monkey/poor_monkey = monkey_worker.resolve() + if(poor_monkey && poor_monkey.loc == src) + poor_monkey.forceMove(drop_location()) + return TRUE + + if("adjust_task_param") + var/success = adjust_param_for_task(params["taskId"], params["param"], params["value"], ui.user) + if(success) + maybe_wake() + return success + + if("disk_eject") + return eject_task_disk(ui.user) + + if("disk_read") + if(read_disk_tasks(ui.user)) + maybe_wake() + return TRUE + + if("disk_write") + return write_disk_tasks(ui.user) + + if("disk_clear") + return clear_disk_tasks(ui.user) + + +/obj/machinery/big_manipulator/proc/eject_task_disk(mob/user) + if(on || stopping) + balloon_alert(user, "turn it off first!") + return FALSE + if(!task_disk) + return FALSE + var/obj/item/disk/manipulator/ejectable_disk = task_disk + task_disk = null + if(istype(user) && user.put_in_hands(ejectable_disk)) + balloon_alert(user, "disk ejected") + else + ejectable_disk.forceMove(drop_location()) + balloon_alert(user, "disk dropped") + SStgui.update_uis(src) + return TRUE + +/obj/machinery/big_manipulator/proc/clear_disk_tasks(mob/user) + if(on || stopping) + balloon_alert(user, "turn it off first!") + return FALSE + if(!task_disk) + return FALSE + if(task_disk.read_only) + balloon_alert(user, "disk protected") + return FALSE + task_disk.set_tasks(list()) + balloon_alert(user, "cleared") + SStgui.update_uis(src) + return TRUE + +/obj/machinery/big_manipulator/proc/write_disk_tasks(mob/user) + if(on || stopping) + balloon_alert(user, "turn it off first!") + return FALSE + if(!task_disk) + return FALSE + if(task_disk.read_only) + balloon_alert(user, "disk protected") + return FALSE + + var/list/out = list() + for(var/datum/manipulator_task/task as anything in tasks) + out += list(task.serialize()) + + task_disk.set_tasks(out) + balloon_alert(user, "written") + SStgui.update_uis(src) + return TRUE + +/obj/machinery/big_manipulator/proc/read_disk_tasks(mob/user) + if(on || stopping) + balloon_alert(user, "turn it off first!") + return FALSE + if(!task_disk) + return FALSE + + QDEL_LIST(tasks) + tasks = list() + current_task = null + + var/turf/base = get_turf(src) + var/datum/stock_part/manipulator/locate_servo = locate() in component_parts + var/manipulator_tier = locate_servo ? locate_servo.tier : 1 + + for(var/list/task_data as anything in task_disk.tasks_data) + if(length(tasks) >= interaction_point_limit) + break + if(!islist(task_data)) + continue + var/task_type = task_data["type"] + if(!ispath(task_type, /datum/manipulator_task)) + continue + var/datum/manipulator_task/new_task + if(ispath(task_type, /datum/manipulator_task/cargo)) + if(!base) + continue + var/list/offset = task_data["offset"] + if(!islist(offset)) + continue + var/dx = offset["dx"] + var/dy = offset["dy"] + if(!isnum(dx) || !isnum(dy)) + continue + if(dx < -1 || dx > 1 || dy < -1 || dy > 1) + continue + if(dx == 0 && dy == 0) + continue + var/turf/target_turf = locate(base.x + dx, base.y + dy, base.z) + if(!target_turf || isclosedturf(target_turf)) + continue + new_task = new task_type(target_turf, manipulator_tier, serialized_data = task_data) + if(istype(new_task, /datum/manipulator_task/cargo)) + var/datum/manipulator_task/cargo/c = new_task + c.offset_dx = dx + c.offset_dy = dy + else + new_task = new task_type(serialized_data = task_data) + if(!new_task || QDELETED(new_task)) + continue + tasks += new_task + + process_upgrades() + validate_all_tasks() + balloon_alert(user, "loaded") + SStgui.update_uis(src) + return TRUE + +/obj/machinery/big_manipulator/proc/adjust_param_for_task(task_ref, param, value, mob/user) + if(!param) + return FALSE + + var/datum/manipulator_task/target_task = locate(task_ref) in tasks + if(!target_task) + return FALSE + + switch(param) + if("set_name") + if(!value) + return FALSE + target_task.name = sanitize_name(value, allow_numbers = TRUE) + return TRUE + + if("set_wait_time") + if(!istype(target_task, /datum/manipulator_task/simple/wait)) + return FALSE + var/datum/manipulator_task/simple/wait/t = target_task + t.time_seconds = clamp(text2num(value), 1, 60) + return TRUE + + if("remove_task") + tasks.Remove(target_task) + qdel(target_task) + return TRUE + + if("move_up") + var/idx = tasks.Find(target_task) + if(idx <= 1) + return FALSE + tasks.Swap(idx, idx - 1) + return TRUE + + if("move_down") + var/idx = tasks.Find(target_task) + if(idx >= length(tasks)) + return FALSE + tasks.Swap(idx, idx + 1) + return TRUE + + if("move_to") + if(!istype(target_task, /datum/manipulator_task/cargo)) + return FALSE + var/datum/manipulator_task/cargo/cargo_task = target_task + var/button_number = text2num(value["buttonNumber"]) + if(button_number < 1 || button_number > 9) + return + var/dx = ((button_number - 1) % 3) - 1 + var/dy = 1 - round((button_number - 1) / 3) + var/turf/new_turf = locate(x + dx, y + dy, z) + if(!new_turf || isclosedturf(new_turf)) + return FALSE + cargo_task.interaction_turf = new_turf + cargo_task.offset_dx = dx + cargo_task.offset_dy = dy + return TRUE + + if("toggle_filter_skip") + if(!istype(target_task, /datum/manipulator_task/cargo)) + return FALSE + var/datum/manipulator_task/cargo/ct = target_task + ct.should_use_filters = !ct.should_use_filters + return TRUE + + if("reset_atom_filters") + if(!istype(target_task, /datum/manipulator_task/cargo)) + return FALSE + var/datum/manipulator_task/cargo/ct = target_task + ct.atom_filters = list() + return TRUE + + if("add_atom_filter_from_held") + if(!istype(target_task, /datum/manipulator_task/cargo)) + return FALSE + var/datum/manipulator_task/cargo/ct = target_task + var/obj/item/held_item = user.get_active_held_item() + if(!held_item) + return FALSE + for(var/filter_path in ct.atom_filters) + if(istype(held_item, filter_path)) + return FALSE + ct.atom_filters += held_item.type + return TRUE + + if("delete_filter") + if(!istype(target_task, /datum/manipulator_task/cargo)) + return FALSE + var/datum/manipulator_task/cargo/ct = target_task + ct.atom_filters.Cut(value, value + 1) + return TRUE + + if("cycle_filtering_mode") + if(!istype(target_task, /datum/manipulator_task/cargo)) + return FALSE + var/datum/manipulator_task/cargo/ct = target_task + ct.filtering_mode = cycle_value(ct.filtering_mode, obj_flags & EMAGGED ? list(TAKE_ITEMS, TAKE_CLOSETS, TAKE_HUMANS) : list(TAKE_ITEMS, TAKE_CLOSETS)) + return TRUE + + if("toggle_priority") + if(!istype(target_task, /datum/manipulator_task/cargo)) + return FALSE + var/datum/manipulator_task/cargo/current_task = target_task + return current_task.tick_priority_by_index(value) + + if("priority_move_up") + if(!istype(target_task, /datum/manipulator_task/cargo)) + return FALSE + var/datum/manipulator_task/cargo/current_task = target_task + return current_task.move_priority_up_by_index(value) + + if("cycle_pickup_eagerness") + if(!istype(target_task, /datum/manipulator_task/cargo/pickup)) + return FALSE + var/datum/manipulator_task/cargo/pickup/cycle_target_task = target_task + cycle_target_task.pickup_eagerness = cycle_value(cycle_target_task.pickup_eagerness, list(PICKUP_CAN_WAIT, PICKUP_EAGER)) + return TRUE + + if("cycle_overflow_status") + if(!istype(target_task, /datum/manipulator_task/cargo/dropoff_base/drop)) + return FALSE + var/datum/manipulator_task/cargo/dropoff_base/drop/cycle_target_task = target_task + cycle_target_task.overflow_status = cycle_value(cycle_target_task.overflow_status, list(POINT_OVERFLOW_ALLOWED, POINT_OVERFLOW_FILTERS, POINT_OVERFLOW_HELD, POINT_OVERFLOW_FORBIDDEN)) + return TRUE + + if("cycle_throw_range") + if(!istype(target_task, /datum/manipulator_task/cargo/dropoff_base/throw)) + return FALSE + var/datum/manipulator_task/cargo/dropoff_base/throw/cycle_target_task = target_task + cycle_target_task.throw_range = cycle_value(cycle_target_task.throw_range, list(1, 2, 3, 4, 5, 6, 7)) + return TRUE + + if("cycle_worker_interaction") + var/list/vals = list(WORKER_NORMAL_USE, WORKER_SINGLE_USE, WORKER_EMPTY_USE) + if(istype(target_task, /datum/manipulator_task/cargo/dropoff_base/use)) + var/datum/manipulator_task/cargo/dropoff_base/use/cycle_target_task = target_task + cycle_target_task.worker_interaction = cycle_value(cycle_target_task.worker_interaction, vals) + return TRUE + if(istype(target_task, /datum/manipulator_task/cargo/interact)) + var/datum/manipulator_task/cargo/interact/cycle_target_task = target_task + cycle_target_task.worker_interaction = cycle_value(cycle_target_task.worker_interaction, vals) + return TRUE + return FALSE + + if("cycle_post_interaction") + var/list/vals = list(POST_INTERACTION_DROP_AT_POINT, POST_INTERACTION_DROP_AT_MACHINE, POST_INTERACTION_DROP_NEXT_FITTING, POST_INTERACTION_WAIT) + if(istype(target_task, /datum/manipulator_task/cargo/dropoff_base/use)) + var/datum/manipulator_task/cargo/dropoff_base/use/cycle_target_task = target_task + cycle_target_task.use_post_interaction = cycle_value(cycle_target_task.use_post_interaction, vals) + return TRUE + if(istype(target_task, /datum/manipulator_task/cargo/interact)) + var/datum/manipulator_task/cargo/interact/cycle_target_task = target_task + cycle_target_task.use_post_interaction = cycle_value(cycle_target_task.use_post_interaction, vals) + return TRUE + return FALSE + + if("toggle_worker_rmb") + if(istype(target_task, /datum/manipulator_task/cargo/dropoff_base/use)) + var/datum/manipulator_task/cargo/dropoff_base/use/cycle_target_task = target_task + cycle_target_task.worker_use_rmb = !cycle_target_task.worker_use_rmb + return TRUE + if(istype(target_task, /datum/manipulator_task/cargo/interact)) + var/datum/manipulator_task/cargo/interact/cycle_target_task = target_task + cycle_target_task.worker_use_rmb = !cycle_target_task.worker_use_rmb + return TRUE + return FALSE + + if("toggle_worker_combat") + if(istype(target_task, /datum/manipulator_task/cargo/dropoff_base/use)) + var/datum/manipulator_task/cargo/dropoff_base/use/cycle_target_task = target_task + cycle_target_task.worker_combat_mode = !cycle_target_task.worker_combat_mode + return TRUE + if(istype(target_task, /datum/manipulator_task/cargo/interact)) + var/datum/manipulator_task/cargo/interact/cycle_target_task = target_task + cycle_target_task.worker_combat_mode = !cycle_target_task.worker_combat_mode + return TRUE + return FALSE + +/// Cycles the given value in the given list. +/obj/machinery/big_manipulator/proc/cycle_value(current_value, list/possible_values) + var/current_index = possible_values.Find(current_value) + if(current_index == 0) + return possible_values[1] + return possible_values[(current_index % length(possible_values)) + 1] + +/// Retries the task loop if we're waiting for a signal and the machine is on. +/obj/machinery/big_manipulator/proc/maybe_wake() + if(on && !stopping && waiting_for_signal) + something_happened() + +/obj/machinery/big_manipulator/proc/update_strategies() + master_tasking = create_strategy(tasking_strategy) + +/obj/machinery/big_manipulator/proc/create_strategy(strategy_mode) + switch(strategy_mode) + if(TASKING_SEQUENTIAL) + return new /datum/tasking_strategy/sequential() + if(TASKING_STRICT) + return new /datum/tasking_strategy/strict() + return new /datum/tasking_strategy/sequential() + +/obj/machinery/big_manipulator/ui_interact(mob/user, datum/tgui/ui) + if(id_lock) + to_chat(user, span_warning("[src] is locked behind ID authentication!")) + ui?.close() + return + if(!anchored) + to_chat(user, span_warning("[src] isn't attached to the ground!")) + ui?.close() + return + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "BigManipulator") + ui.open() + +/obj/machinery/big_manipulator/ui_data(mob/user) + . = list( + "active" = on, + "stopping" = stopping, + "current_task" = current_task ? REF(current_task) : null, + "speed_multiplier" = speed_multiplier, + "min_speed_multiplier" = min_speed_multiplier, + "max_speed_multiplier" = max_speed_multiplier, + "manipulator_position" = "[x],[y]", + "tasking_strategy" = tasking_strategy, + "has_monkey" = !isnull(monkey_worker), + "disk_inserted" = !isnull(task_disk), + "disk_read_only" = task_disk?.read_only || FALSE, + "disk_task_count" = length(task_disk?.tasks_data || list()), + ) + + .["tasks_data"] = list() + for(var/datum/manipulator_task/task as anything in tasks) + var/task_type = "" + if(istype(task, /datum/manipulator_task/cargo/pickup)) + task_type = "pickup" + else if(istype(task, /datum/manipulator_task/cargo/dropoff_base/drop)) + task_type = "drop" + else if(istype(task, /datum/manipulator_task/cargo/dropoff_base/throw)) + task_type = "throw" + else if(istype(task, /datum/manipulator_task/cargo/dropoff_base/use)) + task_type = "use" + else if(istype(task, /datum/manipulator_task/cargo/interact)) + task_type = "interact" + else if(istype(task, /datum/manipulator_task/simple/wait)) + task_type = "wait" + + var/list/task_data = list( + "name" = task.name, + "id" = "[REF(task)]", + "task_type" = task_type, + ) + if(istype(task, /datum/manipulator_task/cargo)) + var/datum/manipulator_task/cargo/task_cargo = task + if(task_cargo.interaction_turf) + task_data["turf"] = "[task_cargo.interaction_turf.x - x],[task_cargo.interaction_turf.y - y]" + task_data["filters_status"] = task_cargo.should_use_filters + task_data["filtering_mode"] = task_cargo.filtering_mode + task_data["settings_list"] = list() + for(var/datum/manipulator_priority/priority as anything in task_cargo.interaction_priorities) + task_data["settings_list"] += list(list( + "name" = priority.name, + "active" = priority.active, + )) + task_data["item_filters"] = list() + for(var/atom/movable/filter_atom as anything in task_cargo.atom_filters) + task_data["item_filters"] += "[filter_atom]" + + if(istype(task, /datum/manipulator_task/cargo/pickup)) + var/datum/manipulator_task/cargo/pickup/task_pickup = task + task_data["pickup_eagerness"] = task_pickup.pickup_eagerness + + if(istype(task, /datum/manipulator_task/cargo/dropoff_base/drop)) + var/datum/manipulator_task/cargo/dropoff_base/drop/task_drop = task + task_data["overflow_status"] = task_drop.overflow_status + + if(istype(task, /datum/manipulator_task/cargo/dropoff_base/throw)) + var/datum/manipulator_task/cargo/dropoff_base/throw/task_throw = task + task_data["throw_range"] = task_throw.throw_range + + if(istype(task, /datum/manipulator_task/cargo/dropoff_base/use)) + var/datum/manipulator_task/cargo/dropoff_base/use/task_use = task + task_data["worker_interaction"] = task_use.worker_interaction + task_data["use_post_interaction"] = task_use.use_post_interaction + task_data["worker_use_rmb"] = task_use.worker_use_rmb + task_data["worker_combat_mode"] = task_use.worker_combat_mode + + if(istype(task, /datum/manipulator_task/cargo/interact)) + var/datum/manipulator_task/cargo/interact/task_interact = task + task_data["worker_interaction"] = task_interact.worker_interaction + task_data["use_post_interaction"] = task_interact.use_post_interaction + task_data["worker_use_rmb"] = task_interact.worker_use_rmb + task_data["worker_combat_mode"] = task_interact.worker_combat_mode + + if(istype(task, /datum/manipulator_task/simple/wait)) + var/datum/manipulator_task/simple/wait/task_wait = task + task_data["time"] = task_wait.time_seconds + + .["tasks_data"] += list(task_data) diff --git a/code/game/machinery/big_manipulator/big_manipulator_interactions.dm b/code/game/machinery/big_manipulator/big_manipulator_interactions.dm new file mode 100644 index 0000000000000..3adaca2b945b3 --- /dev/null +++ b/code/game/machinery/big_manipulator/big_manipulator_interactions.dm @@ -0,0 +1,366 @@ +/// We have no tasks to execute for some reason. Waits for a turf signal to retry. +/obj/machinery/big_manipulator/proc/nothing_ever_happens() + if(stopping) + complete_stopping_task() + return FALSE + + current_task = null + waiting_for_signal = TRUE + register_task_turf_signals() + + return FALSE + +/// A signal ran or some settings changed; checking if we can run the tasks now. +/obj/machinery/big_manipulator/proc/something_happened() + next_cycle_scheduled = FALSE + step_tasks() + +/// Runs the next task. Or doesn't. +/obj/machinery/big_manipulator/proc/step_tasks() + if(!on || stopping) + return + next_cycle_scheduled = FALSE + if(waiting_for_signal) + unregister_task_turf_signals() + waiting_for_signal = FALSE + if(!length(tasks)) + nothing_ever_happens() + return + var/datum/manipulator_task/next_task = master_tasking.get_next_task(tasks, src) + if(!next_task) + nothing_ever_happens() + return + current_task = next_task + SStgui.update_uis(src) + next_task.run_task(src) + +/// Attempts to launch the work cycle. Should only be ran on pressing the "Run" button. +/obj/machinery/big_manipulator/proc/try_kickstart(mob/user) + if(!on || !anchored || stopping || current_task != null) + return FALSE + + if(!use_energy(active_power_usage, force = FALSE)) + on = FALSE + balloon_alert_to_viewers("not enough power!") + return FALSE + + next_cycle_scheduled = FALSE + step_tasks() + +/// Safely schedules the next step to prevent overlapping. +/obj/machinery/big_manipulator/proc/schedule_next_cycle(time_seconds = BASE_INTERACTION_TIME) + if(next_cycle_scheduled || stopping) + return + + next_cycle_scheduled = TRUE + addtimer(CALLBACK(src, PROC_REF(step_tasks)), time_seconds) + +/// Rotates the manipulator arm to face the target task's turf. +/obj/machinery/big_manipulator/proc/rotate_to_point(datum/manipulator_task/cargo/target_task, callback_object, callback) + if(stopping) + return + + if(!target_task) + return FALSE + + var/task_dir = get_dir(get_turf(src), target_task.interaction_turf) + manipulator_arm.target_dir = task_dir + + var/target_angle = dir2angle(task_dir) + var/current_angle = manipulator_arm.transform.get_angle() + var/angle_diff = closer_angle_difference(current_angle, target_angle) + + var/num_rotations = round(abs(angle_diff) / 45) + + if(!num_rotations) + var/datum/callback/cb = CALLBACK(callback_object, callback, src) + cb.Invoke() + return TRUE + + var/rotation_step = 45 * sign(angle_diff) + do_step_rotation(target_task, callback_object, callback, current_angle, target_angle, rotation_step) + return TRUE + +/// Does a 45 degree step, animating the claw +/obj/machinery/big_manipulator/proc/do_step_rotation(datum/manipulator_task/cargo/target_task, callback_object, callback, current_angle, target_angle, rotation_step) + if(stopping) + return + + var/angle_diff = closer_angle_difference(current_angle, target_angle) + if(abs(angle_diff) < abs(rotation_step)) + var/matrix/final_matrix = matrix() + final_matrix.Turn(target_angle) + animate(manipulator_arm, transform = final_matrix, time = BASE_INTERACTION_TIME / speed_multiplier) + var/mob/living/carbon/human/species/monkey/monkey_resolve = monkey_worker?.resolve() + if(monkey_resolve && monkey_resolve.loc == src) + animate(monkey_resolve, transform = final_matrix, time = BASE_INTERACTION_TIME / speed_multiplier) + addtimer(CALLBACK(callback_object, callback, src), BASE_INTERACTION_TIME / speed_multiplier) + return + + var/next_angle = current_angle + rotation_step + var/matrix/next_matrix = matrix() + next_matrix.Turn(next_angle) + animate(manipulator_arm, transform = next_matrix, time = BASE_INTERACTION_TIME / speed_multiplier) + var/mob/living/carbon/human/species/monkey/monkey_resolve = monkey_worker?.resolve() + if(monkey_resolve && monkey_resolve.loc == src) + animate(monkey_resolve, transform = next_matrix, time = BASE_INTERACTION_TIME / speed_multiplier) + + addtimer(CALLBACK(src, PROC_REF(do_step_rotation), target_task, callback_object, callback, next_angle, target_angle, rotation_step), BASE_INTERACTION_TIME / speed_multiplier) + +/obj/machinery/big_manipulator/proc/try_drop_thing(datum/manipulator_task/cargo/dropoff_base/drop/destination_task) + var/drop_endpoint = destination_task.find_type_priority() + var/obj/actual_held_object = held_object?.resolve() + + if(!actual_held_object) + drop_held_atom() + return FALSE + + if(isnull(drop_endpoint)) + drop_held_atom() + return FALSE + + var/atom/drop_target = drop_endpoint + if(drop_target.atom_storage && actual_held_object && (!drop_target.atom_storage.attempt_insert(actual_held_object, override = TRUE))) + actual_held_object.forceMove(drop_target.drop_location()) + finish_manipulation() + return TRUE + + actual_held_object?.forceMove(drop_endpoint) + finish_manipulation() + return TRUE + +/obj/machinery/big_manipulator/proc/try_use_thing(datum/manipulator_task/cargo/interact/destination_task, work_done_at_point = FALSE) + if(stopping) + return + + var/obj/obj_resolve = held_object?.resolve() + var/mob/living/carbon/human/species/monkey/monkey_resolve = monkey_worker?.resolve() + var/destination_turf = destination_task.interaction_turf + + if(!obj_resolve || QDELETED(obj_resolve) || obj_resolve.loc != src) + drop_held_atom() + return FALSE + + if(!monkey_resolve || !destination_turf) + drop_held_atom() + return FALSE + + if(monkey_resolve.loc != src) + drop_held_atom() + return FALSE + + var/obj/item/held_item = obj_resolve + var/atom/type_to_use = destination_task.find_type_priority() + + if(isnull(type_to_use)) + drop_held_atom() + return FALSE + + if(isitem(type_to_use) && !destination_task.check_filters_for_atom(type_to_use)) + drop_held_atom() + return FALSE + + var/original_loc = held_item.loc + + monkey_resolve.put_in_active_hand(held_item) + if(held_item.GetComponent(/datum/component/two_handed)) + held_item.attack_self(monkey_resolve) + + if(destination_task.worker_combat_mode) + monkey_resolve.istate |= ISTATE_HARM + else + monkey_resolve.istate &= ~ISTATE_HARM + + if(destination_task.worker_use_rmb) + monkey_resolve.istate |= ISTATE_SECONDARY + else + monkey_resolve.istate &= ~ISTATE_SECONDARY + + held_item.melee_attack_chain(monkey_resolve, type_to_use, list(RIGHT_CLICK = destination_task.worker_use_rmb ? TRUE : FALSE)) + monkey_resolve.istate &= ~(ISTATE_HARM | ISTATE_SECONDARY) + do_attack_animation(destination_turf) + manipulator_arm.do_attack_animation(destination_turf) + + if(QDELETED(held_item) || !held_item || (held_item.loc != monkey_resolve && held_item.loc != original_loc)) + held_object = null + manipulator_arm.update_claw(null) + finish_manipulation() + return TRUE + + if(held_item.loc == monkey_resolve) + held_item.forceMove(original_loc) + + check_for_cycle_end_drop(destination_task, TRUE, TRUE) + +/obj/machinery/big_manipulator/proc/check_for_cycle_end_drop(datum/manipulator_task/cargo/interact/destination_task, item_used_this_iteration, work_done_at_point = FALSE) + var/obj/obj_resolve = held_object?.resolve() + + if(!obj_resolve || QDELETED(obj_resolve)) + finish_manipulation() + return + + if(obj_resolve.loc != src) + obj_resolve.forceMove(src) + + if(destination_task.worker_interaction == WORKER_SINGLE_USE && item_used_this_iteration) + current_task = null + schedule_next_cycle() + return + + if(!on) + finish_manipulation() + return + + if(item_used_this_iteration) + addtimer(CALLBACK(src, PROC_REF(try_use_thing), destination_task, TRUE), BASE_INTERACTION_TIME * 2) + return + + drop_held_after_use(destination_task) + +/obj/machinery/big_manipulator/proc/drop_held_after_use(datum/manipulator_task/cargo/interact/destination_task) + var/obj/obj_resolve = held_object?.resolve() + var/turf/drop_turf = destination_task.interaction_turf + + switch(destination_task.use_post_interaction) + if(POST_INTERACTION_DROP_AT_POINT) + obj_resolve.forceMove(drop_turf) + obj_resolve.dir = get_dir(get_turf(obj_resolve), get_turf(src)) + finish_manipulation() + + if(POST_INTERACTION_DROP_AT_MACHINE) + obj_resolve.forceMove(get_turf(src)) + finish_manipulation() + + if(POST_INTERACTION_DROP_NEXT_FITTING) + var/datum/manipulator_task/next = master_tasking.get_next_task(tasks, src) + if(istype(next, /datum/manipulator_task/cargo/dropoff_base)) + rotate_to_point(next, next, TYPE_PROC_REF(/datum/manipulator_task/cargo/dropoff_base, try_dropoff)) + return + obj_resolve.forceMove(drop_turf) + obj_resolve.dir = get_dir(get_turf(obj_resolve), get_turf(src)) + finish_manipulation() + else + schedule_next_cycle() + +/obj/machinery/big_manipulator/proc/throw_thing(datum/manipulator_task/cargo/dropoff_base/throw/throw_task) + var/drop_turf = throw_task.interaction_turf + var/atom/movable/held_atom = held_object?.resolve() + + held_atom.forceMove(drop_turf) + do_attack_animation(drop_turf) + manipulator_arm.do_attack_animation(drop_turf) + + if(isliving(held_atom) && !(obj_flags & EMAGGED)) + held_atom.dir = get_dir(get_turf(held_atom), get_turf(src)) + finish_manipulation() + return + + held_atom.throw_at(get_edge_target_turf(get_turf(src), get_dir(get_turf(src), get_turf(held_atom))), throw_task.throw_range, 2) + finish_manipulation() + +/obj/machinery/big_manipulator/proc/use_thing_with_empty_hand(datum/manipulator_task/cargo/interact/destination_task) + var/mob/living/carbon/human/species/monkey/monkey_resolve = monkey_worker?.resolve() + if(isnull(monkey_resolve)) + finish_manipulation() + return + + if(monkey_resolve.loc != src) + finish_manipulation() + return + + var/atom/type_to_use = destination_task.find_type_priority() + if(isnull(type_to_use)) + check_end_of_use_for_use_with_empty_hand(destination_task, FALSE) + return + + if(isitem(type_to_use)) + var/obj/item/interact_with_item = type_to_use + var/resolve_loc = interact_with_item.loc + monkey_resolve.put_in_active_hand(interact_with_item) + interact_with_item.attack_self(monkey_resolve) + interact_with_item.forceMove(resolve_loc) + else + if(destination_task.worker_combat_mode) + monkey_resolve.istate |= ISTATE_HARM + else + monkey_resolve.istate &= ~ISTATE_HARM + monkey_resolve.UnarmedAttack(type_to_use) + monkey_resolve.istate &= ~ISTATE_HARM + + var/turf/dest_turf = destination_task.interaction_turf + if(dest_turf) + do_attack_animation(dest_turf) + manipulator_arm.do_attack_animation(dest_turf) + + check_end_of_use_for_use_with_empty_hand(destination_task, TRUE) + +/obj/machinery/big_manipulator/proc/check_end_of_use_for_use_with_empty_hand(datum/manipulator_task/cargo/interact/destination_task, item_was_used = TRUE) + if(!on || destination_task.worker_interaction != WORKER_EMPTY_USE) + finish_manipulation() + return + + if(!item_was_used) + finish_manipulation() + return + + addtimer(CALLBACK(src, PROC_REF(use_thing_with_empty_hand), destination_task), BASE_INTERACTION_TIME) + +/// Completes the current manipulation action and schedules the next step. +/obj/machinery/big_manipulator/proc/finish_manipulation() + held_object = null + manipulator_arm.update_claw(null) + current_task = null + + SStgui.update_uis(src) + + if(stopping) + complete_stopping_task() + return + + schedule_next_cycle() + +/// Completes the stopping task and transitions to idle +/obj/machinery/big_manipulator/proc/complete_stopping_task() + on = FALSE + stopping = FALSE + next_cycle_scheduled = FALSE + current_task = null + unregister_task_turf_signals() + waiting_for_signal = FALSE + SStgui.update_uis(src) + +/// Registers enter/exit signals on all unique cargo task turfs. +/obj/machinery/big_manipulator/proc/register_task_turf_signals() + unregister_task_turf_signals() + for(var/datum/manipulator_task/cargo/task in tasks) + if(!task.interaction_turf || (task.interaction_turf in signal_turfs)) + continue + signal_turfs += task.interaction_turf + RegisterSignals(task.interaction_turf, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED), PROC_REF(on_task_turf_changed)) + +/// Unregisters all previously registered turf signals. +/obj/machinery/big_manipulator/proc/unregister_task_turf_signals() + for(var/turf/t in signal_turfs) + UnregisterSignal(t, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED)) + signal_turfs = list() + +/// Fires when something enters or leaves a watched task turf. +/obj/machinery/big_manipulator/proc/on_task_turf_changed(datum/source) + SIGNAL_HANDLER + if(!on || stopping || !waiting_for_signal) + return + something_happened() + +/// Drop the held atom and anything the monkey is holding. +/obj/machinery/big_manipulator/proc/drop_held_atom() + // Drop the manipulator's held object + var/obj/obj_resolve = held_object?.resolve() + if(obj_resolve) + obj_resolve.forceMove(drop_location()) + + // Also drop whatever the monkey is holding + var/mob/living/carbon/human/species/monkey/monkey_resolve = monkey_worker?.resolve() + if(monkey_resolve) + monkey_resolve.drop_all_held_items() + + finish_manipulation() diff --git a/code/game/machinery/big_manipulator/big_manipulator_items.dm b/code/game/machinery/big_manipulator/big_manipulator_items.dm new file mode 100644 index 0000000000000..07c197e983172 --- /dev/null +++ b/code/game/machinery/big_manipulator/big_manipulator_items.dm @@ -0,0 +1,28 @@ +/obj/item/circuitboard/machine/big_manipulator + name = "Big Manipulator" + greyscale_colors = CIRCUIT_COLOR_ENGINEERING + build_path = /obj/machinery/big_manipulator + req_components = list( + /datum/stock_part/manipulator = 1, + ) + +/obj/item/disk/manipulator + name = "manipulator task disk" + desc = "A floppy disk containing manipulator tasks." + icon = 'icons/obj/module.dmi' + icon_state = "datadisk1" + var/list/tasks_data = list() + var/read_only = FALSE + +/obj/item/disk/manipulator/proc/set_tasks(list/new_tasks_data) + if(read_only) + return FALSE + tasks_data = islist(new_tasks_data) ? new_tasks_data : list() + return TRUE + +/obj/item/disk/manipulator/proc/get_tasks() + return tasks_data?.Copy() || list() + +/obj/item/disk/manipulator/examine(mob/user) + . = ..() + . += span_notice("It has [length(tasks_data)] task data chunk\s stored.") diff --git a/code/game/machinery/big_manipulator/interaction_priorities.dm b/code/game/machinery/big_manipulator/interaction_priorities.dm new file mode 100644 index 0000000000000..0540c1f59275f --- /dev/null +++ b/code/game/machinery/big_manipulator/interaction_priorities.dm @@ -0,0 +1,37 @@ +// Prioritizes the type of atom that the manipulator interact with. Interaction lists get built on the points themselves. + +/datum/manipulator_priority + /// The name of the priority for the UI display. + var/name + /// Which typepath does this priority handle. + var/atom_typepath + /// Is this priority active? If not, it will be ignored. + var/active = TRUE + +/datum/manipulator_priority/drop/on_floor + name = "DROP ON FLOOR" + atom_typepath = /turf + +/datum/manipulator_priority/drop/in_storage + name = "DROP IN STORAGE" + atom_typepath = /obj/item/storage + +/datum/manipulator_priority/interact/with_living + name = "USE ON LIVING" + atom_typepath = /mob/living + +/datum/manipulator_priority/interact/with_structure + name = "USE ON STRUCTURE" + atom_typepath = /obj/structure + +/datum/manipulator_priority/interact/with_machinery + name = "USE ON MACHINERY" + atom_typepath = /obj/machinery + +/datum/manipulator_priority/interact/with_items + name = "USE ON ITEM" + atom_typepath = /obj/item + +/datum/manipulator_priority/interact/with_vehicles + name = "USE ON VEHICLES" + atom_typepath = /obj/vehicle diff --git a/code/game/machinery/big_manipulator/manipulator_arm.dm b/code/game/machinery/big_manipulator/manipulator_arm.dm new file mode 100644 index 0000000000000..8cafd6568b0c1 --- /dev/null +++ b/code/game/machinery/big_manipulator/manipulator_arm.dm @@ -0,0 +1,53 @@ +/// Manipulator hand. Effect we animate to show that the manipulator is working and moving something. +/obj/effect/big_manipulator_arm + name = "mechanical claw" + desc = "Takes and drops objects." + icon = 'icons/obj/machines/big_manipulator_parts/big_manipulator_hand.dmi' + icon_state = "hand" + layer = LOW_ITEM_LAYER + appearance_flags = KEEP_TOGETHER | LONG_GLIDE | TILE_BOUND | PIXEL_SCALE + anchored = TRUE + greyscale_config = /datum/greyscale_config/manipulator_arm + pixel_x = -32 + pixel_y = -32 + /// The actual target direction (may be diagonal) used for offset calculations. + var/target_dir = SOUTH + /// We get item from big manipulator and takes its icon to create overlay. + var/datum/weakref/item_in_my_claw + /// Var to icon that used as overlay on manipulator claw to show what item it grabs. + var/mutable_appearance/icon_overlay + + +/obj/effect/big_manipulator_arm/update_overlays() + . = ..() + . += update_item_overlay() + +/obj/effect/big_manipulator_arm/proc/update_item_overlay() + if(isnull(item_in_my_claw)) + return icon_overlay = null + var/atom/movable/item_data = item_in_my_claw.resolve() + icon_overlay = mutable_appearance(item_data.icon, item_data.icon_state, item_data.layer, src, item_data.plane, item_data.alpha, item_data.appearance_flags) + icon_overlay.color = item_data.color + icon_overlay.appearance = item_data.appearance + icon_overlay.pixel_w = 32 + calculate_item_offset(is_x = TRUE) + icon_overlay.pixel_z = 32 + calculate_item_offset(is_x = FALSE) + return icon_overlay + +/// Updates item that is in the claw. +/obj/effect/big_manipulator_arm/proc/update_claw(clawed_item) + item_in_my_claw = clawed_item + update_appearance() + +/// Calculate x and y coordinates so that the item icon appears in the claw and not somewhere in the corner. +/obj/effect/big_manipulator_arm/proc/calculate_item_offset(is_x = TRUE, pixels_to_offset = 32) + var/offset + switch(dir) + if(NORTH) + offset = is_x ? 0 : pixels_to_offset + if(SOUTH) + offset = is_x ? 0 : -pixels_to_offset + if(EAST) + offset = is_x ? pixels_to_offset : 0 + if(WEST) + offset = is_x ? -pixels_to_offset : 0 + return offset diff --git a/code/game/machinery/big_manipulator/manipulator_tasks.dm b/code/game/machinery/big_manipulator/manipulator_tasks.dm new file mode 100644 index 0000000000000..6935ffe8f0cc6 --- /dev/null +++ b/code/game/machinery/big_manipulator/manipulator_tasks.dm @@ -0,0 +1,503 @@ +/datum/manipulator_task + var/name = "task" + +/datum/manipulator_task/proc/can_run(obj/machinery/big_manipulator/manipulator) + return FALSE + +/datum/manipulator_task/proc/run_task(obj/machinery/big_manipulator/manipulator) + return + +/datum/manipulator_task/proc/serialize() + return list("type" = type) + +/datum/manipulator_task/New(...) + ..() + return + +// ===== WAIT ===== + +/datum/manipulator_task/simple/wait + name = "wait" + var/time_seconds = 1 + +/datum/manipulator_task/simple/wait/can_run(obj/machinery/big_manipulator/manipulator) + for(var/datum/manipulator_task/cargo/task in manipulator.tasks) + if(task.can_run(manipulator)) + return TRUE + return FALSE + +/datum/manipulator_task/simple/wait/run_task(obj/machinery/big_manipulator/manipulator) + manipulator.schedule_next_cycle(time_seconds SECONDS) + +/datum/manipulator_task/simple/wait/serialize() + var/list/data = ..() + data["time_seconds"] = time_seconds + return data + +/datum/manipulator_task/simple/wait/New(..., serialized_data) + ..() + if(serialized_data) + time_seconds = serialized_data["time_seconds"] + return + +// ===== BASE CARGO ===== + +/datum/manipulator_task/cargo + var/turf/interaction_turf + var/offset_dx + var/offset_dy + var/should_use_filters = FALSE + var/list/atom_filters = list() + var/filtering_mode = TAKE_ITEMS + var/list/type_filters = list( + /obj/item, + /obj/structure/closet, + ) + var/list/interaction_priorities = list() + +/datum/manipulator_task/cargo/New(turf/new_turf, manipulator_tier, serialized_data) + if(serialized_data) + var/list/offset = serialized_data["offset"] + if(islist(offset)) + offset_dx = offset["dx"] + offset_dy = offset["dy"] + if(new_turf) + interaction_turf = new_turf + + should_use_filters = !!serialized_data["should_use_filters"] + atom_filters = serialized_data["atom_filters"] || list() + filtering_mode = serialized_data["filtering_mode"] + type_filters = serialized_data["type_filters"] || list() + + var/list/prios_data = serialized_data["interaction_priorities"] + if(islist(prios_data)) + interaction_priorities = list() + for(var/list/prio_data as anything in prios_data) + if(!islist(prio_data)) + continue + var/prio_type = prio_data["type"] + if(!ispath(prio_type, /datum/manipulator_priority)) + continue + var/datum/manipulator_priority/prio = new prio_type + prio.active = !!prio_data["active"] + interaction_priorities += prio + + return ..() + + if(!new_turf) + stack_trace("New manipulator task created with no valid turf reference passed.") + qdel(src) + return + + if(isclosedturf(new_turf)) + qdel(src) + return + + interaction_turf = new_turf + interaction_priorities = fill_priority_list(manipulator_tier) + return ..() + +/datum/manipulator_task/cargo/proc/fill_priority_list(manipulator_tier) + return list() + +/datum/manipulator_task/cargo/proc/find_type_priority() + var/atom/movable/best_candidate = null + var/best_priority_index = INFINITY + + if(!interaction_turf) + return null + for(var/atom/movable/thing as anything in interaction_turf.contents) + for(var/i in 1 to length(interaction_priorities)) + if(i >= best_priority_index) + break + + var/datum/manipulator_priority/prio = interaction_priorities[i] + + if(!prio.active || ispath(prio, /turf)) + continue + + if(!istype(thing, prio.atom_typepath)) + continue + + if(isliving(thing)) + var/mob/living/living_mob = thing + if(living_mob.stat == DEAD) + continue + + best_candidate = thing + best_priority_index = i + + if(best_priority_index == 1) + return best_candidate + break + + for(var/i in 1 to length(interaction_priorities)) + if(i >= best_priority_index) + break + var/datum/manipulator_priority/prio = interaction_priorities[i] + if(prio.active && prio.atom_typepath == /turf) + best_candidate = interaction_turf + best_priority_index = i + break + + return best_candidate + +/datum/manipulator_task/cargo/proc/move_priority_up_by_index(index) + if(!index) + return FALSE + interaction_priorities.Swap(index, index + 1) + return TRUE + +/datum/manipulator_task/cargo/proc/tick_priority_by_index(index, reset = FALSE) + var/datum/manipulator_priority/target_priority = interaction_priorities[index + 1] + if(reset) + target_priority.active = TRUE + else + target_priority.active = !target_priority.active + return TRUE + +/datum/manipulator_task/cargo/proc/is_valid() + if(!interaction_turf) + return FALSE + return !isclosedturf(interaction_turf) + +/datum/manipulator_task/cargo/proc/check_filters_for_atom(atom/movable/target) + if(!target || target.anchored || HAS_TRAIT(target, TRAIT_NODROP)) + return FALSE + + switch(filtering_mode) + if(TAKE_CLOSETS) + return istype(target, /obj/structure/closet) + if(TAKE_HUMANS) + return ishuman(target) + if(TAKE_ITEMS) + if(!should_use_filters) + return isitem(target) + for(var/filter_path in atom_filters) + if(istype(target, filter_path)) + return TRUE + return FALSE + + return FALSE + +/datum/manipulator_task/cargo/can_run(obj/machinery/big_manipulator/manipulator) + return is_valid() + +/datum/manipulator_task/cargo/serialize() + var/list/data = ..() + data["offset"] = list( + "dx" = offset_dx, + "dy" = offset_dy, + ) + data["should_use_filters"] = should_use_filters + data["atom_filters"] = atom_filters + data["filtering_mode"] = filtering_mode + data["type_filters"] = type_filters + data["interaction_priorities"] = list() + for(var/datum/manipulator_priority/prio as anything in interaction_priorities) + data["interaction_priorities"] += list(list( + "type" = prio.type, + "active" = prio.active, + )) + return data + + +/datum/manipulator_task/cargo/Destroy() + interaction_turf = null + QDEL_LIST(interaction_priorities) + return ..() + +// ===== PICKUP ===== + +/datum/manipulator_task/cargo/pickup + name = "pickup" + var/pickup_eagerness = PICKUP_CAN_WAIT + +/datum/manipulator_task/cargo/pickup/fill_priority_list(manipulator_tier) + return list() + +/datum/manipulator_task/cargo/pickup/can_run(obj/machinery/big_manipulator/manipulator) + if(!..()) + return FALSE + if(manipulator.held_object) + return FALSE + for(var/atom/movable/candidate as anything in interaction_turf.contents) + if(!check_filters_for_atom(candidate)) + continue + if(pickup_eagerness == PICKUP_EAGER) + return TRUE + for(var/datum/manipulator_task/cargo/dropoff_base/dest in manipulator.tasks) + if(dest.can_accept(candidate)) + return TRUE + return FALSE + +/datum/manipulator_task/cargo/pickup/run_task(obj/machinery/big_manipulator/manipulator) + manipulator.rotate_to_point(src, src, PROC_REF(try_pickup)) + +/datum/manipulator_task/cargo/pickup/proc/try_pickup(obj/machinery/big_manipulator/manipulator) + var/atom/movable/selected = find_pickup_candidate(manipulator) + if(!selected) + manipulator.nothing_ever_happens() + return + + if(selected.anchored || HAS_TRAIT(selected, TRAIT_NODROP)) + manipulator.nothing_ever_happens() + return + + if(isitem(selected)) + var/obj/item/selected_item = selected + if(selected_item.item_flags & (ABSTRACT|DROPDEL)) + manipulator.nothing_ever_happens() + return + + selected.forceMove(manipulator) + manipulator.held_object = WEAKREF(selected) + manipulator.manipulator_arm.update_claw(manipulator.held_object) + manipulator.schedule_next_cycle() + +/datum/manipulator_task/cargo/pickup/serialize() + var/list/data = ..() + data["pickup_eagerness"] = pickup_eagerness + return data + +/datum/manipulator_task/cargo/pickup/New(turf/new_turf, manipulator_tier, serialized_data) + ..(new_turf, manipulator_tier, serialized_data) + if(serialized_data) + pickup_eagerness = serialized_data["pickup_eagerness"] + return + +/datum/manipulator_task/cargo/pickup/proc/find_pickup_candidate(obj/machinery/big_manipulator/manipulator) + var/list/candidates = list() + + for(var/atom/movable/candidate as anything in interaction_turf.contents) + if(candidate.anchored || HAS_TRAIT(candidate, TRAIT_NODROP)) + continue + if(!check_filters_for_atom(candidate)) + continue + if(pickup_eagerness == PICKUP_EAGER) + candidates += candidate + continue + for(var/datum/manipulator_task/cargo/dropoff_base/dest in manipulator.tasks) + if(dest.can_accept(candidate)) + candidates += candidate + break + + if(!length(candidates)) + return null + + return manipulator.master_tasking.get_next_candidate(candidates) + +// ===== BASE DROPOFF ===== +// Base type for anything that accepts a `held_object`: drop, throw, use. +// Pickup iterates by this type to find a target point. + +/datum/manipulator_task/cargo/dropoff_base + name = "dropoff" + +/datum/manipulator_task/cargo/dropoff_base/proc/can_accept(atom/movable/target) + if(!is_valid()) + return FALSE + if(should_use_filters && !check_filters_for_atom(target)) + return FALSE + return TRUE + +/datum/manipulator_task/cargo/dropoff_base/can_run(obj/machinery/big_manipulator/manipulator) + if(!..()) + return FALSE + var/atom/movable/target = manipulator.held_object?.resolve() + if(!target) + return FALSE + return can_accept(target) + +/datum/manipulator_task/cargo/dropoff_base/run_task(obj/machinery/big_manipulator/manipulator) + manipulator.rotate_to_point(src, src, PROC_REF(try_dropoff)) + +/datum/manipulator_task/cargo/dropoff_base/proc/try_dropoff(obj/machinery/big_manipulator/manipulator) + var/obj/actual_held_object = manipulator.held_object?.resolve() + if(!actual_held_object || actual_held_object.loc != manipulator) + manipulator.nothing_ever_happens() + return FALSE + do_dropoff(manipulator) + return TRUE + +/datum/manipulator_task/cargo/dropoff_base/serialize() + var/list/data = ..() + return data + + +/datum/manipulator_task/cargo/dropoff_base/proc/do_dropoff(obj/machinery/big_manipulator/manipulator) + return + +// ===== DROP ===== + +/datum/manipulator_task/cargo/dropoff_base/drop + name = "drop" + var/overflow_status = POINT_OVERFLOW_ALLOWED + +/datum/manipulator_task/cargo/dropoff_base/drop/fill_priority_list(manipulator_tier) + return list( + new /datum/manipulator_priority/drop/in_storage, + new /datum/manipulator_priority/drop/on_floor + ) + +/datum/manipulator_task/cargo/dropoff_base/drop/can_accept(atom/movable/target) + if(!..()) + return FALSE + + var/list/atoms_on_the_turf = interaction_turf.contents + switch(overflow_status) + if(POINT_OVERFLOW_ALLOWED) + return TRUE + if(POINT_OVERFLOW_FILTERS) + for(var/atom/movable/movable_atom as anything in atoms_on_the_turf) + if(check_filters_for_atom(movable_atom)) + return FALSE + if(POINT_OVERFLOW_HELD) + for(var/atom/movable/movable_atom as anything in atoms_on_the_turf) + if(istype(movable_atom, target?.type)) + return FALSE + if(POINT_OVERFLOW_FORBIDDEN) + if(locate(/obj/item) in atoms_on_the_turf) + return FALSE + + return TRUE + +/datum/manipulator_task/cargo/dropoff_base/drop/serialize() + var/list/data = ..() + data["overflow_status"] = overflow_status + return data + +/datum/manipulator_task/cargo/dropoff_base/drop/New(turf/new_turf, manipulator_tier, serialized_data) + ..(new_turf, manipulator_tier, serialized_data) + if(serialized_data) + overflow_status = serialized_data["overflow_status"] + return + +/datum/manipulator_task/cargo/dropoff_base/drop/do_dropoff(obj/machinery/big_manipulator/manipulator) + manipulator.try_drop_thing(src) + +// ===== THROW ===== + +/datum/manipulator_task/cargo/dropoff_base/throw + name = "throw" + var/throw_range = 1 + +/datum/manipulator_task/cargo/dropoff_base/throw/can_accept(atom/movable/target) + if(!is_valid()) + return FALSE + if(should_use_filters && !check_filters_for_atom(target)) + return FALSE + return TRUE + +/datum/manipulator_task/cargo/dropoff_base/throw/serialize() + var/list/data = ..() + data["throw_range"] = throw_range + return data + +/datum/manipulator_task/cargo/dropoff_base/throw/New(turf/new_turf, manipulator_tier, serialized_data) + ..(new_turf, manipulator_tier, serialized_data) + if(serialized_data) + throw_range = serialized_data["throw_range"] + return + +/datum/manipulator_task/cargo/dropoff_base/throw/do_dropoff(obj/machinery/big_manipulator/manipulator) + manipulator.throw_thing(src) + +// ===== USE ===== + +/datum/manipulator_task/cargo/dropoff_base/use + name = "use" + var/worker_interaction = WORKER_NORMAL_USE + var/use_post_interaction = POST_INTERACTION_DROP_AT_POINT + var/worker_combat_mode = FALSE + var/worker_use_rmb = FALSE + +/datum/manipulator_task/cargo/dropoff_base/use/fill_priority_list(manipulator_tier) + var/list/priorities = list( + new /datum/manipulator_priority/interact/with_living, + new /datum/manipulator_priority/interact/with_structure, + new /datum/manipulator_priority/interact/with_machinery, + new /datum/manipulator_priority/interact/with_items, + ) + if(manipulator_tier == 4) + priorities += new /datum/manipulator_priority/interact/with_vehicles + return priorities + +/datum/manipulator_task/cargo/dropoff_base/use/can_accept(atom/movable/target) + if(!is_valid()) + return FALSE + if(should_use_filters && !check_filters_for_atom(target)) + return FALSE + return TRUE + +/datum/manipulator_task/cargo/dropoff_base/use/serialize() + var/list/data = ..() + data["worker_interaction"] = worker_interaction + data["use_post_interaction"] = use_post_interaction + data["worker_combat_mode"] = worker_combat_mode + data["worker_use_rmb"] = worker_use_rmb + return data + +/datum/manipulator_task/cargo/dropoff_base/use/New(turf/new_turf, manipulator_tier, serialized_data) + ..(new_turf, manipulator_tier, serialized_data) + if(serialized_data) + worker_interaction = serialized_data["worker_interaction"] + use_post_interaction = serialized_data["use_post_interaction"] + worker_combat_mode = !!serialized_data["worker_combat_mode"] + worker_use_rmb = !!serialized_data["worker_use_rmb"] + return + +/datum/manipulator_task/cargo/dropoff_base/use/do_dropoff(obj/machinery/big_manipulator/manipulator) + manipulator.try_use_thing(src) + +// ===== INTERACT (empty hand) ===== + +/datum/manipulator_task/cargo/interact + name = "interact" + var/worker_interaction = WORKER_EMPTY_USE + var/use_post_interaction = POST_INTERACTION_DROP_AT_POINT + var/worker_combat_mode = FALSE + var/worker_use_rmb = FALSE + +/datum/manipulator_task/cargo/interact/fill_priority_list(manipulator_tier) + var/list/priorities = list( + new /datum/manipulator_priority/interact/with_living, + new /datum/manipulator_priority/interact/with_structure, + new /datum/manipulator_priority/interact/with_machinery, + new /datum/manipulator_priority/interact/with_items, + ) + if(manipulator_tier == 4) + priorities += new /datum/manipulator_priority/interact/with_vehicles + return priorities + +/datum/manipulator_task/cargo/interact/can_run(obj/machinery/big_manipulator/manipulator) + if(!..()) + return FALSE + return find_type_priority() != null + +/datum/manipulator_task/cargo/interact/run_task(obj/machinery/big_manipulator/manipulator) + manipulator.rotate_to_point(src, src, PROC_REF(try_interact)) + +/datum/manipulator_task/cargo/interact/serialize() + var/list/data = ..() + data["worker_interaction"] = worker_interaction + data["use_post_interaction"] = use_post_interaction + data["worker_combat_mode"] = worker_combat_mode + data["worker_use_rmb"] = worker_use_rmb + return data + +/datum/manipulator_task/cargo/interact/New(turf/new_turf, manipulator_tier, serialized_data) + ..(new_turf, manipulator_tier, serialized_data) + if(serialized_data) + worker_interaction = serialized_data["worker_interaction"] + use_post_interaction = serialized_data["use_post_interaction"] + worker_combat_mode = !!serialized_data["worker_combat_mode"] + worker_use_rmb = !!serialized_data["worker_use_rmb"] + return + +/datum/manipulator_task/cargo/interact/proc/try_interact(obj/machinery/big_manipulator/manipulator) + var/atom/movable/held = manipulator.held_object?.resolve() + if(held) + manipulator.try_use_thing(src) + else + manipulator.use_thing_with_empty_hand(src) diff --git a/code/game/machinery/big_manipulator/tasking.dm b/code/game/machinery/big_manipulator/tasking.dm new file mode 100644 index 0000000000000..cc91434363e58 --- /dev/null +++ b/code/game/machinery/big_manipulator/tasking.dm @@ -0,0 +1,69 @@ +/datum/tasking_strategy + var/current_index = 1 + +/// Returns the next task to run, or null if nothing is available. +/datum/tasking_strategy/proc/get_next_task(list/tasks) + return null + +/// Picks a next candidate from a list of eligible atoms. +/datum/tasking_strategy/proc/get_next_candidate(list/candidates) + if(!length(candidates)) + return null + return candidates[1] + +// Moves through the list, skipping tasks that can't run. +/datum/tasking_strategy/sequential + +/datum/tasking_strategy/sequential/get_next_task(list/tasks, obj/machinery/big_manipulator/manipulator) + if(!length(tasks)) + return null + if(current_index < 1 || current_index > length(tasks)) + current_index = 1 + var/start = current_index + while(TRUE) + var/datum/manipulator_task/task = tasks[current_index] + current_index++ + if(current_index > length(tasks)) + current_index = 1 + if(task.can_run(manipulator)) + return task + if(current_index == start) + return null + +/datum/tasking_strategy/sequential/get_next_candidate(list/candidates) + if(!length(candidates)) + return null + if(current_index < 1 || current_index > length(candidates)) + current_index = 1 + var/candidate = candidates[current_index] + current_index++ + if(current_index > length(candidates)) + current_index = 1 + return candidate + +// Stays on the current task until it can run. +/datum/tasking_strategy/strict + +/datum/tasking_strategy/strict/get_next_task(list/tasks, obj/machinery/big_manipulator/manipulator) + if(!length(tasks)) + return null + if(current_index < 1 || current_index > length(tasks)) + current_index = 1 + var/datum/manipulator_task/task = tasks[current_index] + if(!task.can_run(manipulator)) + return null + current_index++ + if(current_index > length(tasks)) + current_index = 1 + return task + +/datum/tasking_strategy/strict/get_next_candidate(list/candidates) + if(!length(candidates)) + return null + if(current_index < 1 || current_index > length(candidates)) + current_index = 1 + var/candidate = candidates[current_index] + current_index++ + if(current_index > length(candidates)) + current_index = 1 + return candidate diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm index 8d977fd18994b..5b5e4176c797a 100644 --- a/code/modules/research/designs/machine_designs.dm +++ b/code/modules/research/designs/machine_designs.dm @@ -1279,6 +1279,16 @@ ) departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING +/datum/design/board/big_manipulator + name = "Big Manipulator Board" + desc = "The circuit board for a big manipulator." + id = "big_manipulator" + build_path = /obj/item/circuitboard/machine/big_manipulator + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING + ) + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SERVICE + /datum/design/board/flatpacker name = "Flatpacker Machine Board" desc = "The circuit board for a Flatpacker." diff --git a/code/modules/research/designs/tool_designs.dm b/code/modules/research/designs/tool_designs.dm index 1a9bfacc53e1d..ed47745bccb47 100644 --- a/code/modules/research/designs/tool_designs.dm +++ b/code/modules/research/designs/tool_designs.dm @@ -448,6 +448,18 @@ RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_JANITORIAL ) +/datum/design/manipulator_task_disk + name = "Manipulator Task Disk" + desc = "A floppy disk for storing and loading manipulator tasks." + id = "manipulator_task_disk" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 0.5) + build_path = /obj/item/disk/manipulator + category = list( + RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_CARGO + ) + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO + /datum/design/bolter_wrench name = "Bolter Wrench" desc = "A wrench that can unbolt airlocks regardless of power status." diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index c1cb06c032ee6..d35f623b0b6a8 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -16,11 +16,7 @@ "basic_scanning", "blast", "ignition", - "big_manipulator", "assembler", - "manipulator_filter", - "manipulator_filter_cargo", - "manipulator_filter_internal", "bodybag", "bounced_radio", "bowl", diff --git a/code/modules/research/techweb/engi_nodes.dm b/code/modules/research/techweb/engi_nodes.dm index ffa0d193248d3..ae7f44c3413d0 100644 --- a/code/modules/research/techweb/engi_nodes.dm +++ b/code/modules/research/techweb/engi_nodes.dm @@ -64,6 +64,8 @@ "tram_display", "crossing_signal", "guideway_sensor", + "big_manipulator", + "manipulator_task_disk", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS) discount_experiments = list(/datum/experiment/scanning/random/material/easy = TECHWEB_TIER_3_POINTS) diff --git a/monkestation/code/modules/factory_type_beat/icons/big_manipulator.dmi b/icons/obj/machines/big_manipulator.dmi similarity index 100% rename from monkestation/code/modules/factory_type_beat/icons/big_manipulator.dmi rename to icons/obj/machines/big_manipulator.dmi diff --git a/monkestation/code/modules/factory_type_beat/icons/big_manipulator_core.dmi b/icons/obj/machines/big_manipulator_parts/big_manipulator_core.dmi similarity index 100% rename from monkestation/code/modules/factory_type_beat/icons/big_manipulator_core.dmi rename to icons/obj/machines/big_manipulator_parts/big_manipulator_core.dmi diff --git a/icons/obj/machines/big_manipulator_parts/big_manipulator_hand.dmi b/icons/obj/machines/big_manipulator_parts/big_manipulator_hand.dmi new file mode 100644 index 0000000000000..37dfd7d82f53c Binary files /dev/null and b/icons/obj/machines/big_manipulator_parts/big_manipulator_hand.dmi differ diff --git a/monkestation/code/modules/factory_type_beat/circuits.dm b/monkestation/code/modules/factory_type_beat/circuits.dm index da657bcc424af..418c0915dd24b 100644 --- a/monkestation/code/modules/factory_type_beat/circuits.dm +++ b/monkestation/code/modules/factory_type_beat/circuits.dm @@ -28,14 +28,6 @@ /datum/stock_part/matter_bin = 2, ) -/obj/item/circuitboard/machine/big_manipulator - name = "Big Manipulator" - greyscale_colors = CIRCUIT_COLOR_ENGINEERING - build_path = /obj/machinery/big_manipulator - req_components = list( - /datum/stock_part/manipulator = 1, - ) - /obj/item/circuitboard/machine/assembler name = "Assembler" greyscale_colors = CIRCUIT_COLOR_ENGINEERING diff --git a/monkestation/code/modules/factory_type_beat/designs.dm b/monkestation/code/modules/factory_type_beat/designs.dm index 59652a61d3148..e562dad9cce5b 100644 --- a/monkestation/code/modules/factory_type_beat/designs.dm +++ b/monkestation/code/modules/factory_type_beat/designs.dm @@ -1,41 +1,5 @@ #define FABRICATOR_SUBCATEGORY_MATERIALS "/Materials" -/datum/design/manipulator_filter - name = "Manipulator Filter" - desc = "This can be inserted into a manipulator to give it filters." - id = "manipulator_filter" - build_path = /obj/item/manipulator_filter - build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE | COLONY_FABRICATOR - category = list( - RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_CARGO - ) - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT) - departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SERVICE - -/datum/design/manipulator_filter_cargo - name = "Manipulator Filter (Department)" - desc = "This can be inserted into a manipulator to give it filters." - id = "manipulator_filter_cargo" - build_path = /obj/item/manipulator_filter/cargo - build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE | COLONY_FABRICATOR - category = list( - RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_CARGO - ) - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT) - departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SERVICE - -/datum/design/manipulator_filter_internal - name = "Manipulator Filter (Internal)" - desc = "This can be inserted into a manipulator to give it filters." - id = "manipulator_filter_internal" - build_path = /obj/item/manipulator_filter/internal_filter - build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE | COLONY_FABRICATOR - category = list( - RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_CARGO - ) - materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT) - departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SERVICE - /datum/design/board/bookbinder name = "Book Binder" desc = "The circuit board for a book binder" @@ -56,17 +20,6 @@ ) departmental_flags = DEPARTMENT_BITFLAG_SERVICE -/datum/design/board/big_manipulator - name = "Big Manipulator Board" - desc = "The circuit board for a big manipulator." - id = "big_manipulator" - build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE | COLONY_FABRICATOR | IMPRINTER - build_path = /obj/item/circuitboard/machine/big_manipulator - category = list( - RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING - ) - departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SERVICE - /datum/design/board/assembler name = "Assembler Board" desc = "The circuit board for an assembler." diff --git a/monkestation/code/modules/factory_type_beat/icons/big_manipulator_hand.dmi b/monkestation/code/modules/factory_type_beat/icons/big_manipulator_hand.dmi deleted file mode 100644 index e165441e8052e..0000000000000 Binary files a/monkestation/code/modules/factory_type_beat/icons/big_manipulator_hand.dmi and /dev/null differ diff --git a/monkestation/code/modules/factory_type_beat/icons/items.dmi b/monkestation/code/modules/factory_type_beat/icons/items.dmi deleted file mode 100644 index 9084cf3f8d28e..0000000000000 Binary files a/monkestation/code/modules/factory_type_beat/icons/items.dmi and /dev/null differ diff --git a/monkestation/code/modules/factory_type_beat/machinery/assembler.dm b/monkestation/code/modules/factory_type_beat/machinery/assembler.dm index 0c87fc6550c24..d0b9a88f77ec0 100644 --- a/monkestation/code/modules/factory_type_beat/machinery/assembler.dm +++ b/monkestation/code/modules/factory_type_beat/machinery/assembler.dm @@ -544,3 +544,21 @@ recipe_icon.icon = initial(atom.icon) recipe_icon.icon_state = initial(atom.icon_state) + +/turf/proc/can_drop_off(atom/movable/target) + if(isclosedturf(src)) + return FALSE + for(var/obj/structure/listed in contents) + if(!listed.can_drop_off(target)) + return FALSE + for(var/obj/machinery/listed in contents) + if(!listed.can_drop_off(target)) + return FALSE + + return TRUE + +/obj/structure/proc/can_drop_off(atom/movable/target) + return TRUE + +/obj/machinery/proc/can_drop_off(atom/movable/target) + return TRUE diff --git a/monkestation/code/modules/factory_type_beat/machinery/grabber.dm b/monkestation/code/modules/factory_type_beat/machinery/grabber.dm deleted file mode 100644 index 357dee64db320..0000000000000 --- a/monkestation/code/modules/factory_type_beat/machinery/grabber.dm +++ /dev/null @@ -1,507 +0,0 @@ -/// Manipulator Core. Main part of the mechanism that carries out the entire process. -/obj/machinery/big_manipulator - name = "Big Manipulator" - desc = "Take and drop objects. Innovation..." - icon = 'monkestation/code/modules/factory_type_beat/icons/big_manipulator_core.dmi' - icon_state = "core" - density = TRUE - circuit = /obj/item/circuitboard/machine/big_manipulator - greyscale_colors = "#d8ce13" - greyscale_config = /datum/greyscale_config/big_manipulator - /// How many time manipulator need to take and drop item. - var/working_speed = 2 SECONDS - /// Using high tier manipulators speeds up big manipulator and requires more energy. - var/power_use_lvl = 0.2 - /// When manipulator already working with item inside he don't take any new items. - var/on_work = FALSE - /// Activate mechanism. - var/on = FALSE - /// Dir to get turf where we take items. - var/take_here = NORTH - /// Dir to get turf where we drop items. - var/drop_here = SOUTH - /// Turf where we take items. - var/turf/take_turf - /// Turf where we drop items. - var/turf/drop_turf - /// Obj inside manipulator. - var/datum/weakref/containment_obj - /// Other manipulator component. - var/obj/effect/big_manipulator_hand/manipulator_hand - ///are we hacked? - var/hacked = FALSE - ///our installed filter - var/obj/item/manipulator_filter/filter - ///our failed attempts - var/failed_attempts = 0 - var/atom/movable/failed_item - -/obj/machinery/big_manipulator/Initialize(mapload) - . = ..() - take_turf = get_step(src, take_here) - drop_turf = get_step(src, drop_here) - create_manipulator_hand() - RegisterSignal(manipulator_hand, COMSIG_QDELETING, PROC_REF(on_hand_qdel)) - manipulator_lvl() - -/obj/machinery/big_manipulator/Destroy(force) - . = ..() - failed_item = null - if(filter) - filter.forceMove(get_turf(src)) - filter = null - qdel(manipulator_hand) - if(isnull(containment_obj)) - return - var/obj/obj_resolve = containment_obj?.resolve() - if(isnull(obj_resolve)) - return - obj_resolve.forceMove(get_turf(obj_resolve)) - - -/obj/machinery/big_manipulator/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) - . = ..() - take_and_drop_turfs_check() - if(isnull(get_turf(src))) - qdel(manipulator_hand) - return - if(!manipulator_hand) - create_manipulator_hand() - manipulator_hand.forceMove(get_turf(src)) - -/obj/machinery/big_manipulator/wrench_act(mob/living/user, obj/item/tool) - . = ..() - default_unfasten_wrench(user, tool, time = 1 SECONDS) - return TRUE - -/obj/machinery/big_manipulator/wrench_act_secondary(mob/living/user, obj/item/tool) - . = ..() - if(on_work || on) - to_chat(user, span_warning("[src] is activated!")) - return - rotate_big_hand() - playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) - return TRUE - -/obj/machinery/big_manipulator/can_be_unfasten_wrench(mob/user, silent) - if(on_work || on) - to_chat(user, span_warning("[src] is activated!")) - return FAILED_UNFASTEN - return ..() - -/obj/machinery/big_manipulator/default_unfasten_wrench(mob/user, obj/item/wrench, time) - . = ..() - if(. == SUCCESSFUL_UNFASTEN) - take_and_drop_turfs_check() - -/obj/machinery/big_manipulator/screwdriver_act(mob/living/user, obj/item/tool) - if(default_deconstruction_screwdriver(user, icon_state, icon_state, tool)) - return TRUE - return TRUE - -/obj/machinery/big_manipulator/crowbar_act(mob/living/user, obj/item/tool) - . = ..() - if(default_deconstruction_crowbar(tool)) - return TRUE - return TRUE - -/obj/machinery/big_manipulator/RefreshParts() - . = ..() - - manipulator_lvl() - -/// Creat manipulator hand effect on manipulator core. -/obj/machinery/big_manipulator/proc/create_manipulator_hand() - manipulator_hand = new/obj/effect/big_manipulator_hand(get_turf(src)) - manipulator_hand.dir = take_here - -/// Check servo tier and change manipulator speed, power_use and colour. -/obj/machinery/big_manipulator/proc/manipulator_lvl() - var/datum/stock_part/manipulator/locate_servo = locate() in component_parts - if(!locate_servo) - return - switch(locate_servo.tier) - if(1) - working_speed = 2 SECONDS - power_use_lvl = 0.02 - set_greyscale(COLOR_YELLOW) - manipulator_hand?.set_greyscale(COLOR_YELLOW) - if(2) - working_speed = 1.4 SECONDS - power_use_lvl = 0.04 - set_greyscale(COLOR_ORANGE) - manipulator_hand?.set_greyscale(COLOR_ORANGE) - if(3) - working_speed = 0.8 SECONDS - power_use_lvl = 0.06 - set_greyscale(COLOR_RED) - manipulator_hand?.set_greyscale(COLOR_RED) - if(4) - working_speed = 0.2 SECONDS - power_use_lvl = 0.08 - set_greyscale(COLOR_PURPLE) - manipulator_hand?.set_greyscale(COLOR_PURPLE) - - active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * power_use_lvl - -/// Changing take and drop turf tiles when we anchore manipulator or if manipulator not in turf. -/obj/machinery/big_manipulator/proc/take_and_drop_turfs_check() - if(anchored && isturf(src.loc)) - take_turf = get_step(src, take_here) - drop_turf = get_step(src, drop_here) - else - take_turf = null - drop_turf = null - -/// Changing take and drop turf dirs and also changing manipulator hand sprite dir. -/obj/machinery/big_manipulator/proc/rotate_big_hand() - switch(take_here) - if(NORTH) - manipulator_hand.item_x = 64 - manipulator_hand.item_y = 32 - take_here = EAST - drop_here = WEST - if(EAST) - manipulator_hand.item_x = 32 - manipulator_hand.item_y = 0 - take_here = SOUTH - drop_here = NORTH - if(SOUTH) - manipulator_hand.item_x = 0 - manipulator_hand.item_y = 32 - take_here = WEST - drop_here = EAST - if(WEST) - manipulator_hand.item_x = -32 - manipulator_hand.item_y = 0 - take_here = NORTH - drop_here = SOUTH - manipulator_hand.dir = take_here - take_and_drop_turfs_check() - -/// Deliting hand will destroy our manipulator core. -/obj/machinery/big_manipulator/proc/on_hand_qdel() - SIGNAL_HANDLER - - deconstruct(TRUE) - -/// Pre take and drop proc from [take and drop procs loop]: -/// Check if we have item on take_turf to start take and drop loop -/obj/machinery/big_manipulator/proc/is_work_check() - if(filter) - var/atom/movable = filter_return() - if(movable) - try_take_thing(take_turf, movable) - return - - if(hacked) - for(var/mob/living/take_item in take_turf.contents) - try_take_thing(take_turf, take_item) - break - for(var/obj/take_item in take_turf.contents) - if(take_item.anchored) - continue - try_take_thing(take_turf, take_item) - break - -/obj/machinery/big_manipulator/proc/filter_return() - if(!filter) - return null - for(var/atom/movable/listed in take_turf.contents) - if(filter.check_filter(listed)) - return listed - -/// First take and drop proc from [take and drop procs loop]: -/// Check if we can take item from take_turf to work with him. This proc also calling from ATOM_ENTERED signal. -/obj/machinery/big_manipulator/proc/try_take_thing(datum/source, atom/movable/target) - SIGNAL_HANDLER - if(target == failed_item) - failed_item = null - return - - if(!on) - return - if(!anchored) - return - if(QDELETED(source) || QDELETED(target)) - return - if(on_work) - return - if(!directly_use_energy(active_power_usage)) - on = FALSE - say("Not enough energy!") - return - failed_item = null - - if(filter) - if(passes_filter(target)) - start_work(target) - return - - if(isitem(target) || (isliving(target) && hacked) || (isobj(target) && !target.anchored)) - start_work(target) - -/obj/machinery/big_manipulator/proc/passes_filter(atom/movable/target) - if(!filter) - return FALSE - return filter.check_filter(target) - -/// Second take and drop proc from [take and drop procs loop]: -/// Taking our item and start manipulator hand rotate animation. -/obj/machinery/big_manipulator/proc/start_work(atom/movable/target) - target.forceMove(src) - containment_obj = WEAKREF(target) - manipulator_hand.picked = target - manipulator_hand.update_appearance() - on_work = TRUE - do_rotate_animation(1) - addtimer(CALLBACK(src, PROC_REF(drop_thing), target), working_speed) - -/// Third take and drop proc from [take and drop procs loop]: -/// Drop our item and start manipulator hand backward animation. -/obj/machinery/big_manipulator/proc/drop_thing(atom/movable/target) - if(!drop_turf.can_drop_off(target)) - failed_attempts++ - if(failed_attempts >= 10) - do_rotate_animation(0) - addtimer(CALLBACK(src, PROC_REF(end_work_failed), target), working_speed) - return - addtimer(CALLBACK(src, PROC_REF(drop_thing), target), working_speed) - return - failed_attempts = 0 - target.forceMove(drop_turf) - manipulator_hand.picked = null - manipulator_hand.update_appearance() - do_rotate_animation(0) - addtimer(CALLBACK(src, PROC_REF(end_work)), working_speed) - -/// Fourth and last take and drop proc from take and drop procs loop: -/// Finishes work and begins to look for a new item for [take and drop procs loop]. -/obj/machinery/big_manipulator/proc/end_work() - on_work = FALSE - is_work_check() - -/obj/machinery/big_manipulator/proc/end_work_failed(atom/movable/target) - target.forceMove(take_turf) - failed_item = target - manipulator_hand.picked = null - manipulator_hand.update_appearance() - on_work = FALSE - failed_attempts = 0 - is_work_check() - -/// Rotates manipulator hand 90 degrees. -/obj/machinery/big_manipulator/proc/do_rotate_animation(backward) - animate(manipulator_hand, transform = matrix(90, MATRIX_ROTATE), working_speed*0.5) - addtimer(CALLBACK(src, PROC_REF(finish_rotate_animation), backward), working_speed*0.5) - -/// Rotates manipulator hand from 90 degrees to 180 or 0 if backward. -/obj/machinery/big_manipulator/proc/finish_rotate_animation(backward) - animate(manipulator_hand, transform = matrix(180 * backward, MATRIX_ROTATE), working_speed*0.5) - -/obj/machinery/big_manipulator/ui_interact(mob/user, datum/tgui/ui) - if(!anchored) - to_chat(user, span_warning("[src] isn't attached to the ground!")) - return - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "BigManipulator") - ui.open() - -/obj/machinery/big_manipulator/click_alt(mob/living/user) - if(!filter) - return CLICK_ACTION_BLOCKING - filter.forceMove(get_turf(src)) - filter = null - return CLICK_ACTION_SUCCESS - -/obj/machinery/big_manipulator/ui_data(mob/user) - var/list/data = list() - data["active"] = on - return data - -/obj/machinery/big_manipulator/ui_act(action, params, datum/tgui/ui) - . = ..() - if(.) - return - switch(action) - if("on") - on = !on - if(on) - RegisterSignal(take_turf, COMSIG_ATOM_ENTERED, PROC_REF(try_take_thing)) - else - UnregisterSignal(take_turf, COMSIG_ATOM_ENTERED) - is_work_check() - return TRUE - -/// Manipulator hand. Effect we animate to show that the manipulator is working and moving something. -/obj/effect/big_manipulator_hand - name = "Manipulator claw" - desc = "Take and drop objects. Innovation..." - icon = 'monkestation/code/modules/factory_type_beat/icons/big_manipulator_hand.dmi' - icon_state = "hand" - layer = LOW_ITEM_LAYER - anchored = TRUE - appearance_flags = KEEP_TOGETHER | LONG_GLIDE | TILE_BOUND | PIXEL_SCALE - greyscale_config = /datum/greyscale_config/manipulator_hand - pixel_x = -32 - pixel_y = -32 - - ///item offset x - var/item_x = 32 - var/item_y = 64 - var/atom/movable/picked - -/obj/effect/big_manipulator_hand/update_overlays() - . = ..() - if(picked) - var/mutable_appearance/ma = mutable_appearance(picked.icon, picked.icon_state, picked.layer, src, appearance_flags = KEEP_TOGETHER) - ma.color = picked.color - ma.appearance = picked.appearance - ma.appearance_flags = appearance_flags - ma.plane = plane - ma.pixel_x = item_x - ma.pixel_y = item_y - . += ma - -/turf/proc/can_drop_off(atom/movable/target) - if(isclosedturf(src)) - return FALSE - for(var/obj/structure/listed in contents) - if(!listed.can_drop_off(target)) - return FALSE - for(var/obj/machinery/listed in contents) - if(!listed.can_drop_off(target)) - return FALSE - - return TRUE - -/obj/structure/proc/can_drop_off(atom/movable/target) - return TRUE - -/obj/machinery/proc/can_drop_off(atom/movable/target) - return TRUE - - -/obj/item/manipulator_filter - name = "manipulator filter" - desc = "A filter specifically designed to work inside of a manipulator." - - icon = 'monkestation/code/modules/factory_type_beat/icons/items.dmi' - icon_state = "filter" - - var/list/filtered_items = list() - var/max_filtered_items = 5 - - -/obj/item/manipulator_filter/afterattack(atom/target, mob/user, proximity_flag, click_parameters) - if(istype(target, /obj/machinery/big_manipulator)) - try_attach(target) - return - - if(target == src) - return ..() - if(!proximity_flag) - return ..() - if(!ismovable(target)) - return ..() - if(istype(target, /obj/effect/decal/conveyor_sorter)) - return - if(is_type_in_list(target, filtered_items)) - to_chat(user, span_warning("[target] is already in [src]'s sorting list!")) - return - if(length(filtered_items) >= max_filtered_items) - to_chat(user, span_warning("[src] already has [max_filtered_items] things within the sorting list!")) - return - filtered_items += target.type - to_chat(user, span_notice("[target] has been added to [src]'s sorting list.")) - -/obj/item/manipulator_filter/examine(mob/user) - . = ..() - . += span_notice("This sorter can sort up to [max_filtered_items] Items.") - . += span_notice("Use Alt-Click to reset the sorting list.") - . += span_notice("Attack things to attempt to add to the sorting list.") - -/obj/item/manipulator_filter/click_alt(mob/user) - visible_message("[src] pings, resetting its sorting list!") - playsound(src, 'sound/machines/ping.ogg', 30, TRUE) - filtered_items = list() - return CLICK_ACTION_SUCCESS - -/obj/item/manipulator_filter/proc/try_attach(obj/machinery/big_manipulator/target) - if(target.filter) - return FALSE - target.filter = src - src.forceMove(target) - return TRUE - -/obj/item/manipulator_filter/proc/check_filter(atom/movable/target) - if(target.type in filtered_items) - return TRUE - return FALSE - - -/obj/item/manipulator_filter/cargo - name = "manipulator filter" - desc = "A filter specifically designed to work inside of a manipulator." - -/obj/item/manipulator_filter/cargo/afterattack(atom/target, mob/user, proximity_flag, click_parameters) - if(istype(target, /obj/machinery/big_manipulator)) - try_attach(target) - return - -/obj/item/manipulator_filter/attack_self(mob/user, modifiers) - . = ..() - var/choice = tgui_input_list(user, "Add a destination to check", name, GLOB.TAGGERLOCATIONS - filtered_items) - if(!choice) - return - - if(length(filtered_items) >= max_filtered_items) - return - - filtered_items |= choice - -/obj/item/manipulator_filter/cargo/check_filter(atom/movable/target) - if(istype(target, /obj/item/delivery)) - var/obj/item/delivery/item = target - var/name_tag = GLOB.TAGGERLOCATIONS[item.sort_tag] - if(name_tag in filtered_items) - return TRUE - - if(istype(target, /obj/item/mail)) - var/obj/item/mail/item = target - var/name_tag = GLOB.TAGGERLOCATIONS[item.sort_tag] - if(name_tag in filtered_items) - return TRUE - - if(SEND_SIGNAL(target, COMSIG_FILTER_CHECK, filtered_items)) - return TRUE - - return FALSE - - -/obj/item/manipulator_filter/internal_filter - name = "internal filter" - desc = "Checks the contents inside of an object and if it matches any of the filters grabs the object" - -/obj/item/manipulator_filter/internal_filter/check_filter(atom/movable/target) - for(var/atom/movable/listed in target.contents) - if(listed.type in filtered_items) - return TRUE - return FALSE - - -/proc/departmental_destination_to_tag(destination) - switch(destination) - if(/area/station/engineering/main) - return "Engineering" - if(/area/station/science/research) - return "Research" - if(/area/station/hallway/secondary/service) - return "Hydroponics" - if(/area/station/service/bar/atrium) - return "Bar" - if(/area/station/security/office, /area/station/security/brig, /area/station/security/brig/upper) - return "Security" - if(/area/station/medical/medbay/central, /area/station/medical/medbay, /area/station/medical/treatment_center, /area/station/medical/storage) - return "Medbay" diff --git a/tgstation.dme b/tgstation.dme index 6388f2135cb56..af0126bf34260 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1969,6 +1969,7 @@ #include "code\datums\wires\airlock.dm" #include "code\datums\wires\apc.dm" #include "code\datums\wires\autolathe.dm" +#include "code\datums\wires\big_manipulator.dm" #include "code\datums\wires\conveyor.dm" #include "code\datums\wires\ecto_sniffer.dm" #include "code\datums\wires\emitter.dm" @@ -2134,6 +2135,14 @@ #include "code\game\machinery\transformer.dm" #include "code\game\machinery\washing_machine.dm" #include "code\game\machinery\wishgranter.dm" +#include "code\game\machinery\big_manipulator\_defines.dm" +#include "code\game\machinery\big_manipulator\big_manipulator.dm" +#include "code\game\machinery\big_manipulator\big_manipulator_interactions.dm" +#include "code\game\machinery\big_manipulator\big_manipulator_items.dm" +#include "code\game\machinery\big_manipulator\interaction_priorities.dm" +#include "code\game\machinery\big_manipulator\manipulator_arm.dm" +#include "code\game\machinery\big_manipulator\manipulator_tasks.dm" +#include "code\game\machinery\big_manipulator\tasking.dm" #include "code\game\machinery\camera\camera.dm" #include "code\game\machinery\camera\camera_construction.dm" #include "code\game\machinery\camera\motion.dm" @@ -7847,7 +7856,6 @@ #include "monkestation\code\modules\factory_type_beat\ai_behaviours\latch_onto.dm" #include "monkestation\code\modules\factory_type_beat\machinery\assembler.dm" #include "monkestation\code\modules\factory_type_beat\machinery\brine_chamber.dm" -#include "monkestation\code\modules\factory_type_beat\machinery\grabber.dm" #include "monkestation\code\modules\factory_type_beat\machinery\splitter.dm" #include "monkestation\code\modules\factory_type_beat\machinery\test_boulder_spawner.dm" #include "monkestation\code\modules\factory_type_beat\machinery\atmos_chem\chemical_infuser.dm" diff --git a/tgui/packages/tgui/interfaces/BigManipulator.tsx b/tgui/packages/tgui/interfaces/BigManipulator.tsx deleted file mode 100644 index f72d750546723..0000000000000 --- a/tgui/packages/tgui/interfaces/BigManipulator.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { BooleanLike } from 'common/react'; - -import { useBackend } from '../backend'; -import { Button, Section, Stack } from '../components'; -import { Window } from '../layouts'; - -type ManipulatorData = { - active: BooleanLike; -}; - -export const BigManipulator = (props) => { - const { data, act } = useBackend(); - const { active } = data; - return ( - - -
- -
-
-
- ); -}; diff --git a/tgui/packages/tgui/interfaces/BigManipulator/index.tsx b/tgui/packages/tgui/interfaces/BigManipulator/index.tsx new file mode 100644 index 0000000000000..23ac8d84c9905 --- /dev/null +++ b/tgui/packages/tgui/interfaces/BigManipulator/index.tsx @@ -0,0 +1,656 @@ +import { useEffect, useState } from 'react'; +import { + BlockQuote, + Box, + Button, + Dropdown, + Icon, + Modal, + Section, + Slider, + Stack, + Table, +} from 'tgui-core/components'; +import type { BooleanLike } from 'tgui-core/react'; + +import { useBackend } from '../../backend'; +import { Window } from '../../layouts'; +import type { ManipulatorData, ManipulatorTask } from './types'; + +const TASK_TYPE_LABELS: Record = { + pickup: 'Pick up...', + drop: 'Drop...', + throw: 'Throw...', + use: 'Use held...', + interact: 'Interact...', + wait: 'Wait...', +}; + +const TASK_TYPE_ICONS: Record = { + pickup: 'hand', + drop: 'box-open', + interact: 'bolt', + wait: 'hourglass-half', +}; + +const TASKING_STRATEGY_ICONS: Record = { + Sequential: 'list-ol', + 'Strict order': 'lock', +}; + +const buttonNumberToIcon: Record = { + 1: '', + 2: 'arrow-up', + 3: '', + 4: 'arrow-left', + 5: 'arrows-to-dot', + 6: 'arrow-right', + 7: '', + 8: 'arrow-down', + 9: '', +}; + +function MasterControls() { + const { act, data } = useBackend(); + const { speed_multiplier, min_speed_multiplier, max_speed_multiplier } = data; + + return ( + + + + + + + + + ); +}; + +type ConfigRowProps = { + label: string; + content: string; + onClick: () => void; + tooltip?: string; + selected?: BooleanLike; +}; + +const ConfigRow = (props: ConfigRowProps) => { + const { label, content, onClick, tooltip = '', selected = false } = props; + + return ( + + + {label} + + + + + + ); +}; + +const getPointButtonNumber = (offset: string): number | null => { + const [dx, dy] = offset.split(',').map(Number); + if (!Number.isFinite(dx) || !Number.isFinite(dy)) return null; + if (dx < -1 || dx > 1 || dy < -1 || dy > 1) return null; + if (dx === 0 && dy === 0) return null; + const xIndex = dx + 1; + const yIndex = 1 - dy; + return yIndex * 3 + xIndex + 1; +}; + +const getFilteringModeText = (mode: number) => { + switch (mode) { + case 1: return 'Items'; + case 2: return 'Closets'; + case 3: return 'Humans'; + default: return 'Unknown'; + } +}; + +type TaskEditModalProps = { + task: ManipulatorTask; + onClose: () => void; +}; + +function TaskEditModal(props: TaskEditModalProps) { + const { act, data } = useBackend(); + const { task, onClose } = props; + + const adjust = (param: string, value?: any) => + act('adjust_task_param', { taskId: task.id, param, value }); + + const isCargo = !!task.turf; + const isPickup = task.task_type.includes('pickup'); + const isDropoff = task.task_type.includes('dropoff'); + const isInteract = task.task_type.includes('interact'); + + const currentButton = task.turf + ? getPointButtonNumber(task.turf) + : null; + + return ( + +
+ } + > + {task.task_type.includes('wait') && ( + + + + Wait Time + + + adjust('set_wait_time', value)} + /> + + +
+ )} + {isCargo && ( + + + + {[1, 2, 3, 4, 5, 6, 7, 8, 9].map((n) => ( +
+ + {isCargo && ( + <> +
+ + adjust('reset_atom_filters')} + confirmContent="Reset?" + icon="trash" + /> + + } + > + + {(task.item_filters ?? []).map((name, index) => ( + + +
{name}
+
+ +
+ + {(task.settings_list ?? []).length > 0 && ( +
+ + {task.settings_list!.map((setting, index) => ( + + + {index + 1} + + + adjust('toggle_priority', index)} + checked={!!setting.active} + fluid + > + {setting.name} + + + +
+
+ )} + + )} +
+ ); +}; + +const TaskList = () => { + const { act, data } = useBackend(); + const { tasks_data, current_task, tasking_strategy } = data; + + const [editingTask, setEditingTask] = useState(null); + const [editingNameId, setEditingNameId] = useState(null); + const [newName, setNewName] = useState(''); + const [selectedType, setSelectedType] = useState('pickup'); + + const adjust = (taskId: string, param: string, value?: any) => + act('adjust_task_param', { taskId, param, value }); + + const handleSaveName = (taskId: string) => { + adjust(taskId, 'set_name', newName); + setEditingNameId(null); + setNewName(''); + }; + + // keep modal in sync with live data + useEffect(() => { + if (!editingTask) return; + const updated = tasks_data.find((t) => t.id === editingTask.id); + if (updated) setEditingTask(updated); + }, [tasks_data]); + + const strategyIcon = + TASKING_STRATEGY_ICONS[tasking_strategy] ?? 'list-ol'; + + return ( + <> +
+ + + + } + > + + {tasks_data.map((task, index) => { + const isActive = current_task === task.id; + const taskTypeKey = Object.keys(TASK_TYPE_LABELS).find((k) => + task.task_type.includes(k), + ) ?? 'wait'; + + return ( + + + + + {index + 1} + + + + + + + + + {TASK_TYPE_LABELS[taskTypeKey] ?? task.task_type} + + + + {task.item_filters && task.item_filters.length > 0 && ( + + {'...any of: ' + + task.item_filters.slice(0, 3).join(', ') + + (task.item_filters.length > 3 ? ` and ${task.item_filters.length - 3} more` : '') + + '...'} + + )} + {task.turf && ...at [{task.turf}]...} + {task.time && ...for {task.time} second{task.time > 1 && "s"}...} + + + + +
+ +
+ + + ({ + value: k, + displayText: TASK_TYPE_LABELS[k], + }))} + selected={TASK_TYPE_LABELS[selectedType]} + onSelected={(val) => setSelectedType(val)} + /> + + + + + +
+ + {editingTask && ( + setEditingTask(null)} + /> + )} + + ); +}; + +export const BigManipulator = () => { + const { data, act } = useBackend(); + const { active, stopping } = data; + + return ( + + +
act('run_cycle')} + > + {!active ? 'Run' : stopping ? 'Stopping' : 'Stop'} + + } + > + +
+
+ + + + + + + + + + + + act('disk_clear')} + >Clear + + + +
+ +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/BigManipulator/types.ts b/tgui/packages/tgui/interfaces/BigManipulator/types.ts new file mode 100644 index 0000000000000..e220a453ffe96 --- /dev/null +++ b/tgui/packages/tgui/interfaces/BigManipulator/types.ts @@ -0,0 +1,55 @@ +import type { BooleanLike } from 'tgui-core/react'; + +export interface PrioritySettings { + name: string; + active: BooleanLike; +} + +export type TaskType = + | 'Pick up...' + | 'Drop...' + | 'Throw...' + | 'Use...' + | 'Interact with...' + | 'Wait for...'; + +export interface ManipulatorTask { + name: string; + id: string; + task_type: string; + // cargo fields + turf?: string; + item_filters?: string[]; + filters_status?: BooleanLike; + filtering_mode?: number; + settings_list?: PrioritySettings[]; + // pickup only + pickup_eagerness?: string; + // dropoff only + interaction_mode?: string; + overflow_status?: string; + throw_range?: number; + worker_interaction?: string; + use_post_interaction?: string; + worker_use_rmb?: BooleanLike; + worker_combat_mode?: BooleanLike; + // interact only + // (worker_interaction, use_post_interaction, worker_use_rmb, worker_combat_mode shared with dropoff) + time?: number; +} + +export interface ManipulatorData { + active: BooleanLike; + stopping: BooleanLike; + current_task: string | null; + speed_multiplier: number; + min_speed_multiplier: number; + max_speed_multiplier: number; + tasks_data: ManipulatorTask[]; + manipulator_position: string; + tasking_strategy: string; + has_monkey: BooleanLike; + disk_inserted: BooleanLike; + disk_read_only: BooleanLike; + disk_task_count: number; +}