From bc668e9c53f52cd042425393ffbeda47a5682744 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 23 Jan 2026 14:09:58 -0600 Subject: [PATCH 1/5] Epic embedd success --- .../signals/signals_mob/signals_mob_carbon.dm | 4 - code/__DEFINES/living.dm | 3 + code/datums/components/embedded.dm | 94 +++--- code/datums/embed_data.dm | 311 +++++++++++++++++- code/game/objects/items.dm | 1 + code/game/objects/items/robot/items/food.dm | 2 +- code/game/objects/items/shrapnel.dm | 4 +- code/game/objects/items/spear.dm | 1 + .../heretic/structures/carving_knife.dm | 2 +- code/modules/mob/inventory.dm | 4 +- code/modules/mob/living/carbon/carbon.dm | 5 +- code/modules/mob/living/carbon/examine.dm | 14 +- .../guns/ballistic/bows/bow_arrows.dm | 2 +- .../modules/projectiles/projectile/bullets.dm | 2 +- .../projectile/bullets/dart_syringe.dm | 2 +- .../projectile/bullets/revolver.dm | 4 +- .../projectiles/projectile/bullets/rifle.dm | 6 +- code/modules/surgery/bodyparts/_bodyparts.dm | 2 + code/modules/surgery/bodyparts/helpers.dm | 13 +- .../loadout_inhand_items.dm | 2 +- .../projectile/bullets/revolver.dm | 4 +- maplestation_modules/icons/hud/embed.dmi | Bin 0 -> 1253 bytes 22 files changed, 395 insertions(+), 87 deletions(-) create mode 100644 maplestation_modules/icons/hud/embed.dmi diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index 01b8bcd576e8..d31556a57555 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -68,10 +68,6 @@ #define COMSIG_CARBON_GAIN_ORGAN "carbon_gain_organ" ///from /item/organ/proc/Remove() (/obj/item/organ/) #define COMSIG_CARBON_LOSE_ORGAN "carbon_lose_organ" -///defined twice, in carbon and human's topics, fired when interacting with a valid embedded_object to pull it out (mob/living/carbon/target, /obj/item, /obj/item/bodypart/L) -#define COMSIG_CARBON_EMBED_RIP "item_embed_start_rip" -///called when removing a given item from a mob, from mob/living/carbon/remove_embedded_object(mob/living/carbon/target, /obj/item) -#define COMSIG_CARBON_EMBED_REMOVAL "item_embed_remove_safe" ///Called when someone attempts to cuff a carbon #define COMSIG_CARBON_CUFF_ATTEMPTED "carbon_attempt_cuff" #define COMSIG_CARBON_CUFF_PREVENT (1<<0) diff --git a/code/__DEFINES/living.dm b/code/__DEFINES/living.dm index 56cc96b506f0..1110c654a153 100644 --- a/code/__DEFINES/living.dm +++ b/code/__DEFINES/living.dm @@ -211,3 +211,6 @@ #define examining_span_normal(msg) span_infoplain(span_italics(msg)) /// For consistent examine span formatting (small size) #define examining_span_small(msg) span_slightly_smaller(span_infoplain(span_italics(msg))) + +/// When a bodypart has something embedded in it +#define COMSIG_BODYPART_ON_EMBEDDED "bodypart_on_embedded" diff --git a/code/datums/components/embedded.dm b/code/datums/components/embedded.dm index 9742de845bd0..809aef9ced23 100644 --- a/code/datums/components/embedded.dm +++ b/code/datums/components/embedded.dm @@ -81,13 +81,11 @@ /datum/component/embedded/RegisterWithParent() RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(jostleCheck)) - RegisterSignal(parent, COMSIG_CARBON_EMBED_RIP, PROC_REF(ripOut)) - RegisterSignal(parent, COMSIG_CARBON_EMBED_REMOVAL, PROC_REF(safeRemove)) RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(checkTweeze)) RegisterSignal(parent, COMSIG_MAGIC_RECALL, PROC_REF(magic_pull)) /datum/component/embedded/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_CARBON_EMBED_RIP, COMSIG_CARBON_EMBED_REMOVAL, COMSIG_ATOM_ATTACKBY, COMSIG_MAGIC_RECALL)) + UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_ATOM_ATTACKBY, COMSIG_MAGIC_RECALL)) /datum/component/embedded/process(seconds_per_tick) var/mob/living/carbon/victim = parent @@ -176,10 +174,11 @@ if(I != weapon || src.limb != limb) return - var/mob/living/carbon/victim = parent - var/datum/embed_data/embed_data = weapon.get_embed() - var/time_taken = embed_data.rip_time * weapon.w_class * 2 // melbert todo : remove this *2 when other people can rip out things from you - INVOKE_ASYNC(src, PROC_REF(complete_rip_out), victim, I, limb, time_taken) + limb?.open_embed_interface(usr) + // var/mob/living/carbon/victim = parent + // var/datum/embed_data/embed_data = weapon.get_embed() + // var/time_taken = embed_data.rip_time * weapon.w_class * 2 // melbert todo : remove this *2 when other people can rip out things from you + // INVOKE_ASYNC(src, PROC_REF(complete_rip_out), victim, I, limb, time_taken) /// everything async that ripOut used to do /datum/component/embedded/proc/complete_rip_out(mob/living/carbon/victim, obj/item/I, obj/item/bodypart/limb, time_taken) @@ -241,55 +240,52 @@ if(!istype(victim) || (possible_tweezers.tool_behaviour != TOOL_HEMOSTAT && possible_tweezers.tool_behaviour != TOOL_WIRECUTTER) || user.zone_selected != limb.body_zone) return - if(weapon != limb.embedded_objects[1]) // just pluck the first one, since we can't easily coordinate with other embedded components affecting this limb who is highest priority - return - if(ishuman(victim)) // check to see if the limb is actually exposed var/mob/living/carbon/human/victim_human = victim if(!victim_human.try_inject(user, limb.body_zone, INJECT_CHECK_IGNORE_SPECIES | INJECT_TRY_SHOW_ERROR_MESSAGE)) - return TRUE + return COMPONENT_NO_AFTERATTACK - INVOKE_ASYNC(src, PROC_REF(tweezePluck), possible_tweezers, user) + limb.open_embed_interface(user) return COMPONENT_NO_AFTERATTACK /// The actual action for pulling out an embedded object with a hemostat -/datum/component/embedded/proc/tweezePluck(obj/item/possible_tweezers, mob/user) - var/mob/living/carbon/victim = parent - var/datum/embed_data/embed_data = weapon.get_embed() - var/self_pluck = (user == victim) - // quality of the tool we're using - var/tweezer_speed = possible_tweezers.toolspeed - // is this an actual piece of medical equipment - var/tweezer_safe = (possible_tweezers.tool_behaviour == TOOL_HEMOSTAT) - var/pluck_time = embed_data.rip_time * (weapon.w_class * 0.3) * (self_pluck ? 1.5 : 1) * tweezer_speed * (tweezer_safe ? 1 : 1.5) - - user.visible_message( - span_danger("[user] begins plucking [weapon] from [user == victim ? user.p_their() : "[victim]'s"] [limb.plaintext_zone] with [possible_tweezers]..."), - span_notice("You start plucking [weapon] from [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [possible_tweezers]... (It will take [DisplayTimeText(pluck_time)].)"), - vision_distance = COMBAT_MESSAGE_RANGE - ) - - playsound(user, 'sound/surgery/hemostat1.ogg', 50, TRUE, falloff_exponent = 12, falloff_distance = 1) - if(!do_after(user, pluck_time, victim)) - return - if(QDELETED(src)) - return - - user.visible_message( - span_danger("[user] plucks [weapon] from [victim]'s [limb.plaintext_zone][tweezer_safe ? "." : ", but hurt [victim.p_them()] in the process."]"), - span_notice("You pluck [weapon] from [victim]'s [limb.plaintext_zone][tweezer_safe ? "." : ", but it's not perfect."]"), - vision_distance = COMBAT_MESSAGE_RANGE, - ) - - var/obj/item/bodypart/our_limb = limb // because we null after removing - - if(!tweezer_safe) - // sure it still hurts but it sucks less - damaging_removal(victim, weapon, limb, (0.4 * possible_tweezers.w_class)) - safeRemove(user) - - if(length(our_limb.embedded_objects)) - victim.attackby(possible_tweezers, user) // loop if we can +// /datum/component/embedded/proc/tweezePluck(obj/item/possible_tweezers, mob/user) +// var/mob/living/carbon/victim = parent +// var/datum/embed_data/embed_data = weapon.get_embed() +// var/self_pluck = (user == victim) +// // quality of the tool we're using +// var/tweezer_speed = possible_tweezers.toolspeed +// // is this an actual piece of medical equipment +// var/tweezer_safe = (possible_tweezers.tool_behaviour == TOOL_HEMOSTAT) +// var/pluck_time = embed_data.rip_time * (weapon.w_class * 0.3) * (self_pluck ? 1.5 : 1) * tweezer_speed * (tweezer_safe ? 1 : 1.5) + +// user.visible_message( +// span_danger("[user] begins plucking [weapon] from [user == victim ? user.p_their() : "[victim]'s"] [limb.plaintext_zone] with [possible_tweezers]..."), +// span_notice("You start plucking [weapon] from [user == victim ? "your" : "[victim]'s"] [limb.plaintext_zone] with [possible_tweezers]... (It will take [DisplayTimeText(pluck_time)].)"), +// vision_distance = COMBAT_MESSAGE_RANGE +// ) + +// playsound(user, 'sound/surgery/hemostat1.ogg', 50, TRUE, falloff_exponent = 12, falloff_distance = 1) +// if(!do_after(user, pluck_time, victim)) +// return +// if(QDELETED(src)) +// return + +// user.visible_message( +// span_danger("[user] plucks [weapon] from [victim]'s [limb.plaintext_zone][tweezer_safe ? "." : ", but hurt [victim.p_them()] in the process."]"), +// span_notice("You pluck [weapon] from [victim]'s [limb.plaintext_zone][tweezer_safe ? "." : ", but it's not perfect."]"), +// vision_distance = COMBAT_MESSAGE_RANGE, +// ) + +// var/obj/item/bodypart/our_limb = limb // because we null after removing + +// if(!tweezer_safe) +// // sure it still hurts but it sucks less +// damaging_removal(victim, weapon, limb, (0.4 * possible_tweezers.w_class)) +// safeRemove(user) + +// if(length(our_limb.embedded_objects)) +// victim.attackby(possible_tweezers, user) // loop if we can /// Called when an object is ripped out of someone's body by magic or other abnormal means /datum/component/embedded/proc/magic_pull(datum/source, mob/living/caster, obj/marked_item) diff --git a/code/datums/embed_data.dm b/code/datums/embed_data.dm index b72d88ec15ca..3de2c4619647 100644 --- a/code/datums/embed_data.dm +++ b/code/datums/embed_data.dm @@ -27,8 +27,6 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) var/impact_pain_mult = 4 /// Coefficient of multiplication for the damage the item does when it falls out or is removed without a surgery (this*item.w_class) var/remove_pain_mult = 6 - /// Time in ticks, total removal time = (this*item.w_class) - var/rip_time = 30 /// If this should ignore throw speed threshold of 4 var/ignore_throwspeed_threshold = FALSE /// Chance for embedded objects to cause pain every time they move (jostle) @@ -42,6 +40,8 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) var/stealthy_embed = FALSE /// How much blood is lost per life tick while embedded var/blood_loss = 0.25 + /// Max speed we can pull the embedded object out without causing damage + var/max_pull_speed = 2 /datum/embed_data/proc/generate_with_values( embed_chance = src.embed_chance, @@ -50,7 +50,7 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) pain_mult = src.pain_mult, impact_pain_mult = src.impact_pain_mult, remove_pain_mult = src.remove_pain_mult, - rip_time = src.rip_time, + max_pull_speed = src.max_pull_speed, ignore_throwspeed_threshold = src.ignore_throwspeed_threshold, jostle_chance = src.jostle_chance, jostle_pain_mult = src.jostle_pain_mult, @@ -66,7 +66,7 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) data.pain_mult = pain_mult data.impact_pain_mult = impact_pain_mult data.remove_pain_mult = remove_pain_mult - data.rip_time = rip_time + data.max_pull_speed = max_pull_speed data.ignore_throwspeed_threshold = ignore_throwspeed_threshold data.jostle_chance = jostle_chance data.jostle_pain_mult = jostle_pain_mult @@ -86,3 +86,306 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) ) if(harmful) playsound(victim, 'sound/weapons/bladeslice.ogg', 40) + + +/obj/item/bodypart + /// The embed interface for this limb, shared between all users viewing it + VAR_FINAL/atom/movable/screen/embed_interface/embed_interface + +/obj/item/bodypart/proc/open_embed_interface(mob/living/user = usr) + embed_interface ||= new(null, null, src) + embed_interface.open(user) + +/obj/effect/appearance_clone/embedded_item + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + /// Progress towards removal, or in other words y-pos + var/remove_progress = 0 + /// Max speed we can pull this object out without causing damage + var/max_speed = INFINITY + +#define GET_REMOVAL_TOOL(whom, target) (whom.get_active_held_item()?.get_proxy_attacker_for(target, whom)) + +/atom/movable/screen/embed_interface + icon = 'maplestation_modules/icons/hud/embed.dmi' + icon_state = "base" + screen_loc = "CENTER+2.1,CENTER-1.6" + maptext_x = 2 + maptext_y = 160 + maptext_width = 120 + maptext_height = 200 + + /// Limb that owns us + VAR_PRIVATE/obj/item/bodypart/target_limb + /// Assoc list of currently tracked embedded objects to the embed holder + VAR_PRIVATE/list/tracked_embeds + /// Assoc list of mob vieweing the interface to their data + VAR_PRIVATE/list/viewers + +/atom/movable/screen/embed_interface/Initialize(mapload, datum/hud/hud_owner, obj/item/bodypart/limb) + . = ..() + maptext += "" + maptext += MAPTEXT_TINY_UNICODE(\ + "Click on an object, then move your mouse to move it. \ + Once it enters the green area, it will be removed from the body.

\ + Be careful, moving too fast will cause damage and drop the object \ + if you are not using precision tools!"\ + ) + maptext += "
" + + target_limb = limb + tracked_embeds = list() + viewers = list() + for (var/obj/item/embed as anything in target_limb.embedded_objects) + register_embedded_object(embed) + RegisterSignals(target_limb, list(COMSIG_QDELETING, COMSIG_BODYPART_REMOVED), PROC_REF(on_limb_deleted)) + RegisterSignal(target_limb, COMSIG_BODYPART_ON_EMBEDDED, PROC_REF(new_embed_registered)) + + // var/mutable_appearance/limb_underlay = new(target_limb) + // limb_underlay.appearance_flags |= PIXEL_SCALE + // limb_underlay.transform = target_limb.transform.Scale(8, 8) + // limb_underlay.plane = src.plane + // limb_underlay.layer = src.layer - 1 + // limb_underlay.pixel_x = 45 + // limb_underlay.pixel_y = 55 + // limb_underlay.color = COLOR_MATRIX_GRAYSCALE + // underlays += limb_underlay + +/atom/movable/screen/embed_interface/Destroy() + UnregisterSignal(target_limb, list(COMSIG_QDELETING, COMSIG_BODYPART_REMOVED, COMSIG_BODYPART_ON_EMBEDDED)) + target_limb.embed_interface = null + target_limb = null + for(var/mob/viewer as anything in viewers) + close(viewer) + for(var/obj/item/embed as anything in tracked_embeds) + unregister_embedded_object(embed) + return ..() + +/atom/movable/screen/embed_interface/proc/on_limb_deleted(datum/source) + SIGNAL_HANDLER + + qdel(src) + +/atom/movable/screen/embed_interface/proc/new_embed_registered(datum/source, obj/item/embedded_item) + SIGNAL_HANDLER + + register_embedded_object(embedded_item) + +/atom/movable/screen/embed_interface/proc/register_embedded_object(obj/item/embedded_item) + + var/obj/effect/appearance_clone/embedded_item/embed_holder = new(null, embedded_item) + embed_holder.vis_flags |= VIS_INHERIT_ID + embed_holder.appearance_flags |= PIXEL_SCALE + embed_holder.transform = embedded_item.transform.Scale(2, 2) + embed_holder.pixel_x = 6 + 36 * (length(tracked_embeds) % 3) + embed_holder.pixel_y = 2 * rand(1, 4) + embed_holder.layer = src.layer + 1 + embed_holder.plane = src.plane + + var/datum/embed_data/embed_data = embedded_item.get_embed() + embed_holder.max_speed = embed_data.max_pull_speed + + tracked_embeds[embedded_item] = embed_holder + RegisterSignal(embedded_item, COMSIG_ITEM_UNEMBEDDED, PROC_REF(embedded_object_removed)) + vis_contents += embed_holder + +/atom/movable/screen/embed_interface/proc/embedded_object_removed(obj/item/source, mob/living/owner) + SIGNAL_HANDLER + unregister_embedded_object(source) + if(!length(tracked_embeds)) + qdel(src) + return + + for(var/mob/viewer as anything in viewers) + if(viewers[viewer]["selected"] == source) + set_currently_selected(null, viewer) + +/atom/movable/screen/embed_interface/proc/unregister_embedded_object(obj/item/source) + var/obj/effect/appearance_clone/embedded_item/embed_holder = tracked_embeds[source] + vis_contents -= embed_holder + qdel(embed_holder) + + tracked_embeds -= source + UnregisterSignal(source, COMSIG_ITEM_UNEMBEDDED) + +/atom/movable/screen/embed_interface/proc/open(mob/user) + if(viewers[user]) + return // already open + if(!isliving(user) || !check_state(user)) + return + + RegisterSignals(user, list(COMSIG_QDELETING, COMSIG_MOB_LOGOUT), PROC_REF(on_viewer_deleted)) + RegisterSignals(user, list(SIGNAL_ADDTRAIT(TRAIT_INCAPACITATED), COMSIG_MOVABLE_MOVED), PROC_REF(on_viewer_state_update)) + RegisterSignals(user, list(COMSIG_MOB_DROPPING_ITEM, COMSIG_MOB_SWAP_HANDS), PROC_REF(on_viewer_hand_update)) + user.client.screen += src + viewers[user] = list( + "selected" = null, + "last_move_world_time" = null, + "last_move_x_num" = null, + "last_move_y_num" = null, + "last_fail" = null, + "bypass_fail" = can_bypass_speed_check(user), + ) + +/atom/movable/screen/embed_interface/proc/close(mob/user) + UnregisterSignal(user, list( + COMSIG_MOB_DROPPING_ITEM, + COMSIG_MOB_LOGOUT, + COMSIG_MOB_SWAP_HANDS, + COMSIG_MOVABLE_MOVED, + COMSIG_QDELETING, + SIGNAL_ADDTRAIT(TRAIT_INCAPACITATED), + )) + user.client?.screen -= src + viewers -= user + +/atom/movable/screen/embed_interface/proc/check_state(mob/user) + if(user.incapacitated(IGNORE_STASIS|IGNORE_GRAB)) + return FALSE + if(user.CanReach(target_limb.owner, GET_REMOVAL_TOOL(user, target_limb) )) + return TRUE + if(!iscarbon(user)) + return FALSE + var/mob/living/carbon/carbon_mob = user + if(!carbon_mob.dna?.check_mutation(/datum/mutation/human/telekinesis)) + return FALSE + if(!tkMaxRangeCheck(carbon_mob, target_limb.owner)) + return FALSE + return TRUE + +/atom/movable/screen/embed_interface/proc/on_viewer_state_update(mob/source, ...) + SIGNAL_HANDLER + + if(!check_state(source)) + close(source) + +/atom/movable/screen/embed_interface/proc/on_viewer_deleted(mob/source, ...) + SIGNAL_HANDLER + + close(source) + +/atom/movable/screen/embed_interface/proc/on_viewer_hand_update(mob/source, ...) + SIGNAL_HANDLER + + viewers[source]["bypass_fail"] = can_bypass_speed_check(source) + +/atom/movable/screen/embed_interface/proc/set_currently_selected(obj/item/embed, mob/user) + if(!isnull(viewers[user]["selected"])) + var/obj/effect/appearance_clone/embedded_item/embed_holder = tracked_embeds[viewers[user]["selected"]] + embed_holder?.remove_filter("selected") // it's fine if this one is null + + viewers[user]["selected"] = embed + if(!isnull(embed)) + var/obj/effect/appearance_clone/embedded_item/embed_holder = tracked_embeds[embed] + if(isnull(embed_holder)) // but if this one is null we secrewed up + stack_trace("Embed appearance for [embed] is null in embed interface!") + viewers[user]["selected"] = null + else + embed_holder.add_filter("selected", 1, outline_filter(1, COLOR_YELLOW)) + +/atom/movable/screen/embed_interface/MouseMove(location, control, params) + if(isnull(viewers?[usr]?["selected"])) + return + + var/list/modifiers = params2list(params) + var/cursor_x = text2num(LAZYACCESS(modifiers, ICON_X)) + var/cursor_y = text2num(LAZYACCESS(modifiers, ICON_Y)) + update_effect(cursor_x, cursor_y) + +/atom/movable/screen/embed_interface/Click(location, control, params) + var/list/modifiers = params2list(params) + var/cursor_x = text2num(LAZYACCESS(modifiers, ICON_X)) + var/cursor_y = text2num(LAZYACCESS(modifiers, ICON_Y)) + if(cursor_x > 110 && cursor_y > 150) + close(usr) + return + + var/obj/item/clicked = find_clicked_embed(cursor_x, cursor_y) + if(isnull(clicked)) + return + if(clicked == viewers[usr]["selected"]) + // to_chat(usr, span_notice("You release [clicked].")) + set_currently_selected(null, usr) + return + + set_currently_selected(clicked, usr) + // var/tool = can_bypass_speed_check(usr) + // to_chat(usr, span_notice("You grab [clicked][tool ? " with [tool]" : ""].")) + update_effect(cursor_x, cursor_y) + +/atom/movable/screen/embed_interface/proc/damage_limb(obj/item/from_what, multiplier = 1) + var/datum/embed_data/stats = from_what.get_embed() + target_limb.owner.sharp_pain( + target_zones = target_limb.body_zone, + amount = multiplier * clamp(from_what.w_class * stats.pain_mult * 4, 24, 48) * max(0.5, stats.pain_stam_pct), + dam_type = BRUTE, + duration = 20 SECONDS, + return_mod = 0.5, + ) + target_limb.receive_damage( + brute = multiplier * clamp(from_what.w_class * stats.pain_mult * 2, 12, 24) * max(0.5, 1 - stats.pain_stam_pct), + wound_bonus = max(from_what.wound_bonus, 10), + sharpness = from_what.get_sharpness(), + damage_source = from_what, + ) + +/atom/movable/screen/embed_interface/proc/update_effect(cursor_x, cursor_y) + var/last_move_world_time = viewers[usr]["last_move_world_time"] + var/last_move_x_num = viewers[usr]["last_move_x_num"] + var/last_move_y_num = viewers[usr]["last_move_y_num"] + + if(!isnull(last_move_x_num) && !isnull(last_move_y_num)) + var/obj/item/currently_selected = viewers[usr]["selected"] + var/dx = cursor_x - last_move_x_num + var/dy = cursor_y - last_move_y_num + + var/obj/effect/appearance_clone/embedded_item/embed_holder = tracked_embeds[currently_selected] + if(isnull(embed_holder)) + stack_trace("Embed appearance for [currently_selected] is null in embed interface!") + set_currently_selected(null, usr) + + // if you move too fast, it causes damage and drops the embed + var/time_diff = max(1, world.time - last_move_world_time) + var/speed = sqrt((dx * dx) + (dy * dy)) / time_diff + if (speed > embed_holder.max_speed && !viewers[usr]["bypass_fail"]) + // flick("[icon_state]_fast", src) + icon_state = "[initial(icon_state)]_fast" + addtimer(VARSET_CALLBACK(src, icon_state, initial(icon_state)), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) + if(embed_holder.remove_progress > 36) // damage is not applied until out of the red zone + embed_holder.remove_progress *= 0.9 + damage_limb(currently_selected, (COOLDOWN_FINISHED(src, viewers[usr]["last_fail"]) ? 1 : 0.2)) + log_combat(usr, target_limb.owner, "damaged limb by moving embedded object too quickly") + COOLDOWN_START(src, viewers[usr]["last_fail"], 10 SECONDS) + set_currently_selected(null, usr) + + else + embed_holder.remove_progress += dy + if(embed_holder.remove_progress > 120) + var/obj/item/tool = GET_REMOVAL_TOOL(usr, target_limb) + if(tool?.tool_behaviour == TOOL_WIRECUTTER || tool?.get_sharpness()) + damage_limb(currently_selected, 0.5) // you can bypass the speed limit but it still applies a bit of damage + log_combat(usr, target_limb.owner, "damaged limb by removing embedded object with improvised tool", tool) + target_limb.owner.remove_embedded_object(currently_selected, usr) + return + + embed_holder?.pixel_x = clamp(cursor_x - 16, 8, 120) + embed_holder?.pixel_y = clamp(embed_holder.remove_progress, 0, 180) + + viewers[usr]["last_move_world_time"] = world.time + viewers[usr]["last_move_x_num"] = cursor_x + viewers[usr]["last_move_y_num"] = cursor_y + +/atom/movable/screen/embed_interface/proc/can_bypass_speed_check(mob/who) + var/obj/item/holding = GET_REMOVAL_TOOL(who, target_limb) + return holding?.tool_behaviour == TOOL_HEMOSTAT || holding?.tool_behaviour == TOOL_WIRECUTTER || holding?.get_sharpness() + +/atom/movable/screen/embed_interface/proc/find_clicked_embed(x_num, y_num) + for (var/u_embed_datum, u_embed_holder in tracked_embeds) + var/obj/effect/appearance_clone/embedded_item/embed_holder = u_embed_holder + if (x_num > embed_holder.pixel_x + 52 || x_num < embed_holder.pixel_x + 12) + continue + if (y_num > embed_holder.pixel_y + 52 || y_num < embed_holder.pixel_y + 12) + continue + return u_embed_datum + return null + +#undef GET_REMOVAL_TOOL diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index e93ac7ac3ca1..bc8c90ab9abf 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -1244,6 +1244,7 @@ /obj/item/proc/embedded(atom/embedded_target, obj/item/bodypart/part) SHOULD_CALL_PARENT(TRUE) SEND_SIGNAL(src, COMSIG_ITEM_EMBEDDED, embedded_target, part) + SEND_SIGNAL(part, COMSIG_BODYPART_ON_EMBEDDED, src) /obj/item/proc/unembedded() if(item_flags & DROPDEL && !QDELETED(src)) diff --git a/code/game/objects/items/robot/items/food.dm b/code/game/objects/items/robot/items/food.dm index 0920d6210113..d4c94136f178 100644 --- a/code/game/objects/items/robot/items/food.dm +++ b/code/game/objects/items/robot/items/food.dm @@ -238,7 +238,7 @@ ignore_throwspeed_threshold = TRUE pain_stam_pct = 0.5 pain_mult = 3 - rip_time = 2 SECONDS + max_pull_speed = 10 /obj/projectile/bullet/lollipop/Initialize(mapload) . = ..() diff --git a/code/game/objects/items/shrapnel.dm b/code/game/objects/items/shrapnel.dm index 01d4bfb613fc..c23626ec5523 100644 --- a/code/game/objects/items/shrapnel.dm +++ b/code/game/objects/items/shrapnel.dm @@ -99,7 +99,7 @@ pain_stam_pct = 0.7 pain_mult = 3 jostle_pain_mult = 3 - rip_time = 6 SECONDS + max_pull_speed = 1.5 /obj/projectile/bullet/pellet/stingball/on_ricochet(atom/A) hit_prone_targets = TRUE // ducking will save you from the first wave, but not the rebounds @@ -132,7 +132,7 @@ pain_stam_pct = 0.7 pain_mult = 5 jostle_pain_mult = 6 - rip_time = 6 SECONDS + max_pull_speed = 1.5 /obj/item/shrapnel/capmine name = "\improper AP shrapnel shard" diff --git a/code/game/objects/items/spear.dm b/code/game/objects/items/spear.dm index 69e2f8ec5f58..bcefbe3ac537 100644 --- a/code/game/objects/items/spear.dm +++ b/code/game/objects/items/spear.dm @@ -40,6 +40,7 @@ impact_pain_mult = 2 remove_pain_mult = 4 jostle_chance = 2.5 + max_pull_speed = 3 /datum/armor/item_spear fire = 50 diff --git a/code/modules/antagonists/heretic/structures/carving_knife.dm b/code/modules/antagonists/heretic/structures/carving_knife.dm index a1b0907ea9ba..876598ce29e0 100644 --- a/code/modules/antagonists/heretic/structures/carving_knife.dm +++ b/code/modules/antagonists/heretic/structures/carving_knife.dm @@ -33,7 +33,7 @@ jostle_pain_mult = 5 pain_stam_pct = 0.4 pain_mult = 3 - rip_time = 15 + max_pull_speed = 8 /obj/item/melee/rune_carver/examine(mob/user) . = ..() diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 2d7c3afb71d4..4e52fb73815b 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -2,13 +2,13 @@ //as they handle all relevant stuff like adding it to the player's screen and updating their overlays. ///Returns the thing we're currently holding -/mob/proc/get_active_held_item() +/mob/proc/get_active_held_item() as /obj/item return get_item_for_held_index(active_hand_index) //Finds the opposite limb for the active one (eg: upper left arm will find the item in upper right arm) //So we're treating each "pair" of limbs as a team, so "both" refers to them -/mob/proc/get_inactive_held_item() +/mob/proc/get_inactive_held_item() as /obj/item return get_item_for_held_index(get_inactive_hand_index()) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index bbc8dbe64729..34c3699084f7 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -237,10 +237,7 @@ var/obj/item/bodypart/L = locate(href_list["embedded_limb"]) in bodyparts if(!L) return - var/obj/item/I = locate(href_list["embedded_object"]) in L.embedded_objects - if(!I || I.loc != src) //no item, no limb, or item is not in limb or in the person anymore - return - SEND_SIGNAL(src, COMSIG_CARBON_EMBED_RIP, I, L) + L.open_embed_interface(usr) return if(href_list["gauze_limb"]) diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index 2796f58aa4de..c23d4f2bba96 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -59,7 +59,12 @@ var/list/missing = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) var/list/disabled = list() - var/adjacent = user.Adjacent(src) + var/can_reach = isliving(user) && user.CanReach(src) + if(!can_reach && iscarbon(user)) + var/mob/living/carbon/carbon_user = user + if(carbon_user.dna?.check_mutation(/datum/mutation/human/telekinesis) && tkMaxRangeCheck(user, src)) + can_reach = TRUE + for(var/obj/item/bodypart/body_part as anything in bodyparts) if(body_part.bodypart_disabled) disabled += body_part @@ -70,11 +75,14 @@ var/harmless = embedded.is_embed_harmless() var/stuck_wordage = harmless ? "stuck to" : "embedded in" var/span_to_use = harmless ? "notice" : "boldwarning" - . += "[t_He] [t_has] [icon2html(embedded, user)] \a [embedded] [stuck_wordage] [t_his] [body_part.plaintext_zone]!" + var/embedded_href = "\a [embedded]" + if(can_reach) // only shows the href if we're adjacent + embedded_href = "[embedded]" + . += "[t_He] [t_has] [icon2html(embedded, user)] \a [embedded_href] [stuck_wordage] [t_his] [body_part.plaintext_zone]!" if(body_part.current_gauze) var/gauze_href = body_part.current_gauze.name - if(adjacent && isliving(user)) // only shows the href if we're adjacent + if(can_reach) // only shows the href if we're adjacent gauze_href = "[gauze_href]" . += span_notice("There is some [icon2html(body_part.current_gauze, user)] [gauze_href] wrapped around [t_his] [body_part.plaintext_zone].") diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm index 8595e810c3a6..d436a7a3f7a8 100644 --- a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm +++ b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm @@ -43,7 +43,7 @@ pain_stam_pct = 0.5 pain_mult = 3 jostle_pain_mult = 3 - rip_time = 1 SECONDS + max_pull_speed = 20 /// holy arrows /obj/item/ammo_casing/arrow/holy diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm index 88e1273a4fd1..7cb162bf9072 100644 --- a/code/modules/projectiles/projectile/bullets.dm +++ b/code/modules/projectiles/projectile/bullets.dm @@ -25,7 +25,7 @@ ignore_throwspeed_threshold = TRUE pain_stam_pct = 0.5 pain_mult = 3 - rip_time = 10 SECONDS + max_pull_speed = 1.5 stealthy_embed = TRUE blood_loss = 0.05 diff --git a/code/modules/projectiles/projectile/bullets/dart_syringe.dm b/code/modules/projectiles/projectile/bullets/dart_syringe.dm index c0a0c4c783e6..ad5e29afa3c8 100644 --- a/code/modules/projectiles/projectile/bullets/dart_syringe.dm +++ b/code/modules/projectiles/projectile/bullets/dart_syringe.dm @@ -65,7 +65,7 @@ /datum/embed_data/syringe embed_chance = 0 // only when forced fall_chance = 0 // only when edited - rip_time = 1.5 SECONDS + max_pull_speed = 5 pain_stam_pct = 0.75 impact_pain_mult = 8 // half this if syringe w class goes up. remove_pain_mult = 8 // same diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm index 182bfbddb816..e7e11e60f830 100644 --- a/code/modules/projectiles/projectile/bullets/revolver.dm +++ b/code/modules/projectiles/projectile/bullets/revolver.dm @@ -34,7 +34,7 @@ pain_stam_pct = 0.4 pain_mult = 3 jostle_pain_mult = 5 - rip_time = 8 SECONDS + max_pull_speed = 2 /obj/projectile/bullet/c38/match name = ".38 Match bullet" @@ -78,7 +78,7 @@ jostle_chance = 4 pain_mult = 5 jostle_pain_mult = 6 - rip_time = 5 SECONDS + max_pull_speed = 2.5 /obj/projectile/bullet/c38/trac name = ".38 TRAC bullet" diff --git a/code/modules/projectiles/projectile/bullets/rifle.dm b/code/modules/projectiles/projectile/bullets/rifle.dm index 2a908199d9dc..92f1eecedc41 100644 --- a/code/modules/projectiles/projectile/bullets/rifle.dm +++ b/code/modules/projectiles/projectile/bullets/rifle.dm @@ -58,7 +58,7 @@ pain_stam_pct = 0.4 pain_mult = 5 jostle_pain_mult = 6 - rip_time = 10 SECONDS + max_pull_speed = 10 // Rebar (Rebar Crossbow) /obj/projectile/bullet/rebar @@ -83,7 +83,7 @@ pain_stam_pct = 0.4 pain_mult = 4 jostle_pain_mult = 2 - rip_time = 5 SECONDS + max_pull_speed = 2.75 /obj/projectile/bullet/rebarsyndie name = "rebar" @@ -104,4 +104,4 @@ fall_chance = 0.0006 jostle_chance = 3 pain_mult = 3 - rip_time = 6 SECONDS + max_pull_speed = 3 diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index fd01d4da1e15..b71e41c55dc4 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -1018,6 +1018,7 @@ . += image('icons/mob/effects/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_0[burnstate]", -DAMAGE_LAYER) var/image/limb = image(layer = -BODYPARTS_LAYER) + limb.appearance_flags |= PIXEL_SCALE var/image/aux // Handles invisibility (not alpha or actual invisibility but invisibility) @@ -1044,6 +1045,7 @@ if(aux_zone) //Hand shit aux = image(limb.icon, "[limb_id]_[aux_zone]", -aux_layer) + aux.appearance_flags |= PIXEL_SCALE . += aux draw_color = variable_color if(should_draw_greyscale) //Should the limb be colored outside of a forced color? diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm index 3eafc75b0459..4254e612b1ab 100644 --- a/code/modules/surgery/bodyparts/helpers.dm +++ b/code/modules/surgery/bodyparts/helpers.dm @@ -118,14 +118,15 @@ return disabled ///Remove a specific embedded item from the carbon mob -/mob/living/carbon/proc/remove_embedded_object(obj/item/embedded) - SEND_SIGNAL(src, COMSIG_CARBON_EMBED_REMOVAL, embedded) +/mob/living/carbon/proc/remove_embedded_object(obj/item/embedded, mob/remover) + for(var/datum/component/embedded/embedcomp as anything in GetComponents(/datum/component/embedded)) + if(embedcomp.weapon == embedded) + embedcomp.safeRemove(remover) // evil embed component fuck youuu fuck youuuu ///Remove all embedded objects from all limbs on the carbon mob -/mob/living/carbon/proc/remove_all_embedded_objects() - for(var/obj/item/bodypart/bodypart as anything in bodyparts) - for(var/obj/item/embedded in bodypart.embedded_objects) - remove_embedded_object(embedded) +/mob/living/carbon/proc/remove_all_embedded_objects(mob/remover) + for(var/datum/component/embedded/embedcomp as anything in GetComponents(/datum/component/embedded)) + embedcomp.safeRemove(remover) // evil embed component fuck youuu fuck youuuu /mob/living/carbon/proc/has_embedded_objects(include_harmless=FALSE) for(var/obj/item/bodypart/bodypart as anything in bodyparts) diff --git a/maplestation_modules/code/game/objects/items/other_loadout_items/loadout_inhand_items.dm b/maplestation_modules/code/game/objects/items/other_loadout_items/loadout_inhand_items.dm index ddfbc3c38c4a..fb3fcc3e7a40 100644 --- a/maplestation_modules/code/game/objects/items/other_loadout_items/loadout_inhand_items.dm +++ b/maplestation_modules/code/game/objects/items/other_loadout_items/loadout_inhand_items.dm @@ -22,4 +22,4 @@ jostle_chance = 2 pain_mult = 1 jostle_pain_mult = 1.2 - rip_time = 0.5 SECONDS + max_pull_speed = 20 diff --git a/maplestation_modules/code/modules/projectiles/projectile/bullets/revolver.dm b/maplestation_modules/code/modules/projectiles/projectile/bullets/revolver.dm index b279fbe4e254..207c63f9121e 100644 --- a/maplestation_modules/code/modules/projectiles/projectile/bullets/revolver.dm +++ b/maplestation_modules/code/modules/projectiles/projectile/bullets/revolver.dm @@ -15,7 +15,7 @@ embed_chance = 20 pain_mult = 2 jostle_pain_mult = 4 - rip_time = 4 SECONDS + max_pull_speed = 2.5 /obj/projectile/bullet/c38/dual_stage/fire(angle, atom/direct_target) . = ..() @@ -60,7 +60,7 @@ pain_stam_pct = 0.2 pain_mult = 1 jostle_pain_mult = 2 - rip_time = 1 SECONDS + max_pull_speed = 5 /obj/item/shrapnel/bullet/maginull var/mob/living/carbon/spiked_mob diff --git a/maplestation_modules/icons/hud/embed.dmi b/maplestation_modules/icons/hud/embed.dmi new file mode 100644 index 0000000000000000000000000000000000000000..0dd55fd3952a1defe672d02c1857a02a76ec5d4a GIT binary patch literal 1253 zcmeAS@N?(olHy`uVBq!ia0vp^AAooP2OE$yy8SQ}NYz$_M3lIs7AF^F7L;V>=P@u; z%qcy!k?W9w0NVr0?;`eZ^*TFl>rPUVycIrSN}O+7*a_KhUuEaW+}Zc=b)odTOeY!P zy-PD|-zLQP&ASsQuAR`bSv`D-U3a74x_MVNaZ22s6KYlDoy+B>uzlN}lh^HEZcN)+ zzxlX<>k-3??8lPSe0FS&H)mjAQTB9k45^s&_O4^ztpt(Qi_f(VziZ=7F_cVoJh`O% zBh#roqjGiAqTn80#RtEO`$S9|96j{oy8-;DB zr{&(>`B}7JfoXKulbz>gu5Z5};B$a#=*@?VTDup1`@TmxCNli}<;l-(&)ENYYop?g zMCSLqZ{7XeS5W%DX2;B*X2;uSJ6bV18(>2lmOph)_}%vF|K5ACTmQc^mX>JZoKf-k z+uK!TkFS6EGmR6(&$#^7+V+3owhax6Ga5Qgx7WJ&Z)p81*_r0!T-#!=7<%(SOVq7> zi?3~mDA-)~yD#eAA*QwjCbd&LZyWp{*DpWA@B{AL^glr|$paZ=ao4{O-ouR%;=Hvp2JH?mHd`*g5}|Lc+VG zmkU2{yu4g*_ZKt&*!-89bOj8`4Zq*{^!evypy=#*a=NzRal7uV{Jm3@=~?0&;T$f; z%X8M|oqn&7VD|Eg+MaLId~WXwW_qS~ZQp|}-!>KJZC)zAanDqtCQ!gq56Rj|YyM=O zXYs1G>QK$>&vB{D&RY(%-TSqCL*cxIuTraS)!a5ZcbyFwBXmLS4ehI@SKkv{_xy&= z+3m8IBkj({9{9#P`Tg$F!Lj`$IPA{)?%=a*M&!)z4*}Q$iB}0dz`d literal 0 HcmV?d00001 From 959cb126f9d92da81d18dddd880e01f1c4fb283d Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 23 Jan 2026 14:12:41 -0600 Subject: [PATCH 2/5] Style --- code/datums/embed_data.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/datums/embed_data.dm b/code/datums/embed_data.dm index 3de2c4619647..27b46cdf63c2 100644 --- a/code/datums/embed_data.dm +++ b/code/datums/embed_data.dm @@ -123,14 +123,14 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) /atom/movable/screen/embed_interface/Initialize(mapload, datum/hud/hud_owner, obj/item/bodypart/limb) . = ..() - maptext += "" + maptext += "" maptext += MAPTEXT_TINY_UNICODE(\ "Click on an object, then move your mouse to move it. \ Once it enters the green area, it will be removed from the body.

\ Be careful, moving too fast will cause damage and drop the object \ if you are not using precision tools!"\ ) - maptext += "
" + maptext += "" target_limb = limb tracked_embeds = list() From 724d8965cdcad9dabb4004bcf6fed6f5e66dc569 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 23 Jan 2026 14:27:02 -0600 Subject: [PATCH 3/5] Dupes --- code/datums/embed_data.dm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/code/datums/embed_data.dm b/code/datums/embed_data.dm index 27b46cdf63c2..084e97234893 100644 --- a/code/datums/embed_data.dm +++ b/code/datums/embed_data.dm @@ -93,6 +93,14 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) VAR_FINAL/atom/movable/screen/embed_interface/embed_interface /obj/item/bodypart/proc/open_embed_interface(mob/living/user = usr) + if(isnull(user?.client)) + return + + for(var/atom/movable/screen/embed_interface/embed_interface in user.client.screen) + if(embed_interface.target_limb == src) + return // already open + embed_interface.close(user) + embed_interface ||= new(null, null, src) embed_interface.open(user) @@ -210,7 +218,7 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) /atom/movable/screen/embed_interface/proc/open(mob/user) if(viewers[user]) return // already open - if(!isliving(user) || !check_state(user)) + if(!isliving(user) || !user.client || !check_state(user)) return RegisterSignals(user, list(COMSIG_QDELETING, COMSIG_MOB_LOGOUT), PROC_REF(on_viewer_deleted)) From a3e3f235993ab709375b723baa298452df993e9a Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 23 Jan 2026 14:51:38 -0600 Subject: [PATCH 4/5] Tweaks --- code/datums/embed_data.dm | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/code/datums/embed_data.dm b/code/datums/embed_data.dm index 084e97234893..eaf9f6c07d74 100644 --- a/code/datums/embed_data.dm +++ b/code/datums/embed_data.dm @@ -96,10 +96,10 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) if(isnull(user?.client)) return - for(var/atom/movable/screen/embed_interface/embed_interface in user.client.screen) - if(embed_interface.target_limb == src) + for(var/atom/movable/screen/embed_interface/other_embed_interface in user.client.screen) + if(other_embed_interface == embed_interface) return // already open - embed_interface.close(user) + other_embed_interface.close(user) embed_interface ||= new(null, null, src) embed_interface.open(user) @@ -129,14 +129,17 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) /// Assoc list of mob vieweing the interface to their data VAR_PRIVATE/list/viewers + /// Track the last time we attempted to pick something up while dragging + VAR_PRIVATE/last_drag_attempt = 0 + /atom/movable/screen/embed_interface/Initialize(mapload, datum/hud/hud_owner, obj/item/bodypart/limb) . = ..() maptext += "" maptext += MAPTEXT_TINY_UNICODE(\ - "Click on an object, then move your mouse to move it. \ + "Drag an object to move it. \ Once it enters the green area, it will be removed from the body.

\ - Be careful, moving too fast will cause damage and drop the object \ - if you are not using precision tools!"\ + Be careful, moving too fast will cause damage \ + if you are not using tools!"\ ) maptext += "
" @@ -291,7 +294,7 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) embed_holder.add_filter("selected", 1, outline_filter(1, COLOR_YELLOW)) /atom/movable/screen/embed_interface/MouseMove(location, control, params) - if(isnull(viewers?[usr]?["selected"])) + if(isnull(viewers[usr]?["selected"])) return var/list/modifiers = params2list(params) @@ -299,6 +302,21 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) var/cursor_y = text2num(LAZYACCESS(modifiers, ICON_Y)) update_effect(cursor_x, cursor_y) +/atom/movable/screen/embed_interface/MouseDrag(over_object, src_location, over_location, src_control, over_control, params) + var/list/modifiers = params2list(params) + var/cursor_x = text2num(LAZYACCESS(modifiers, ICON_X)) + var/cursor_y = text2num(LAZYACCESS(modifiers, ICON_Y)) + if(isnull(viewers[usr]?["selected"])) + if(last_drag_attempt == world.time) + return // drag can be sent like 100 times a tick so limit re-attempts + last_drag_attempt = world.time + var/obj/item/clicked = find_clicked_embed(cursor_x, cursor_y) + if(isnull(clicked)) + return + set_currently_selected(clicked, usr) + + update_effect(cursor_x, cursor_y) + /atom/movable/screen/embed_interface/Click(location, control, params) var/list/modifiers = params2list(params) var/cursor_x = text2num(LAZYACCESS(modifiers, ICON_X)) @@ -311,13 +329,10 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) if(isnull(clicked)) return if(clicked == viewers[usr]["selected"]) - // to_chat(usr, span_notice("You release [clicked].")) set_currently_selected(null, usr) return set_currently_selected(clicked, usr) - // var/tool = can_bypass_speed_check(usr) - // to_chat(usr, span_notice("You grab [clicked][tool ? " with [tool]" : ""].")) update_effect(cursor_x, cursor_y) /atom/movable/screen/embed_interface/proc/damage_limb(obj/item/from_what, multiplier = 1) From 43e137bb64e4fd6526bb028c357d35dec1e3e140 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 23 Jan 2026 18:13:46 -0600 Subject: [PATCH 5/5] a --- code/datums/embed_data.dm | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/code/datums/embed_data.dm b/code/datums/embed_data.dm index eaf9f6c07d74..847e077a7019 100644 --- a/code/datums/embed_data.dm +++ b/code/datums/embed_data.dm @@ -134,6 +134,9 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) /atom/movable/screen/embed_interface/Initialize(mapload, datum/hud/hud_owner, obj/item/bodypart/limb) . = ..() + if(isnull(limb)) + return INITIALIZE_HINT_QDEL + maptext += "" maptext += MAPTEXT_TINY_UNICODE(\ "Drag an object to move it. \ @@ -141,8 +144,8 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) Be careful, moving too fast will cause damage \ if you are not using tools!"\ ) - maptext += "" + maptext += "" target_limb = limb tracked_embeds = list() viewers = list() @@ -162,9 +165,10 @@ GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) // underlays += limb_underlay /atom/movable/screen/embed_interface/Destroy() - UnregisterSignal(target_limb, list(COMSIG_QDELETING, COMSIG_BODYPART_REMOVED, COMSIG_BODYPART_ON_EMBEDDED)) - target_limb.embed_interface = null - target_limb = null + if(target_limb) + UnregisterSignal(target_limb, list(COMSIG_QDELETING, COMSIG_BODYPART_REMOVED, COMSIG_BODYPART_ON_EMBEDDED)) + target_limb.embed_interface = null + target_limb = null for(var/mob/viewer as anything in viewers) close(viewer) for(var/obj/item/embed as anything in tracked_embeds)