diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 572a75459647..95dda12cb3ce 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -241,9 +241,9 @@ If you're feeling frisky, examine yourself and click the underlined item to pull icon_state = "embeddedobject" /obj/screen/alert/embeddedobject/Click() - if(isliving(usr)) - var/mob/living/carbon/human/M = usr - return M.help_shake_act(M) + if(iscarbon(usr)) + var/mob/living/carbon/C = usr + return C.try_remove_embedded_object(C) /obj/screen/alert/weightless name = "Weightless" diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 1b625bd68f16..42722ca178c9 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -878,3 +878,20 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) if(ismob(loc)) var/mob/mob_loc = loc mob_loc.regenerate_icons() +/** + * Called when this object is first embedded into a carbon + */ +/obj/item/proc/on_embed(mob/living/carbon/human/embedde, obj/item/bodypart/part) + return TRUE + +/** + * Called when this object is no longer embedded into a carbon + */ +/obj/item/proc/on_embed_removal(mob/living/carbon/human/embedde) + return TRUE + +/** + * Called every life tick when the object is embedded in a carbon + */ +/obj/item/proc/embed_tick(mob/living/carbon/human/embedde, obj/item/bodypart/part) + return diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 91c2c08c3ca7..fc4be77f34a1 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -273,6 +273,26 @@ visible_message(span_danger("[usr] [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name]."), \ span_userdanger("[usr] [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name].")) + // Embed Stuff + if(href_list["embedded_object"] && usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) + 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 + var/time_taken = I.embedding.embedded_unsafe_removal_time*I.w_class + usr.visible_message(span_warning("[usr] attempts to remove [I] from [usr.p_their()] [L.name]."),span_notice("You attempt to remove [I] from your [L.name]... (It will take [DisplayTimeText(time_taken)].)")) + if(do_after(usr, time_taken, needhand = 1, target = src)) + if(!I || !L || I.loc != src) + return + var/damage_amount = I.embedding.embedded_unsafe_removal_pain_multiplier * I.w_class + L.receive_damage(damage_amount, sharpness = SHARP_EDGED)//It hurts to rip it out, get surgery you dingus. + if(remove_embedded_object(I, get_turf(src), damage_amount)) + usr.put_in_hands(I) + usr.visible_message("[usr] successfully rips [I] out of [usr.p_their()] [L.name]!", span_notice("You successfully remove [I] from your [L.name].")) + return + /mob/living/carbon/fall(forced) if(loc) loc.handle_fall(src, forced)//it's loc so it doesn't call the mob's handle_fall which does nothing diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index dc226b36efd4..569f80b1732f 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -50,10 +50,15 @@ return TRUE /mob/living/carbon/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) - if(!skipcatch) //ugly, but easy - if(can_catch_item()) - if(istype(AM, /obj/item)) - var/obj/item/I = AM + var/obj/item/I = AM + if(istype(I, /obj/item)) + if(((throwingdatum ? throwingdatum.speed : I.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || I.embedding.embedded_ignore_throwspeed_threshold) + var/obj/item/bodypart/body_part = pick(bodyparts) + if(prob(clamp(I.embedding.embed_chance - run_armor_check(body_part, MELEE), 0, 100)) && embed_object(I, deal_damage = TRUE)) + hitpush = FALSE + skipcatch = TRUE //can't catch the now embedded item + if(!skipcatch) //ugly, but easy + if(can_catch_item()) if(I.item_flags & UNCATCHABLE) return FALSE if(isturf(I.loc)) @@ -68,6 +73,83 @@ return TRUE ..() +/** + * Embeds an object into this carbon + */ +/mob/living/carbon/proc/embed_object(obj/item/embedding, part, deal_damage, silent, forced) + if(!(forced || (can_embed(embedding) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE)))) + return FALSE + var/obj/item/bodypart/body_part = part + // In case its a zone + if(!istype(body_part) && body_part) + body_part = get_bodypart(body_part) + // Otherwise pick one + if(!istype(body_part)) + body_part = pick(bodyparts) + // Thats probably not good + if(!istype(body_part)) + return FALSE + if(!embedding.on_embed(src, body_part)) + return + body_part.embedded_objects |= embedding + embedding.add_mob_blood(src)//it embedded itself in you, of course it's bloody! + embedding.forceMove(src) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded) + if(deal_damage) + body_part.receive_damage(embedding.w_class*embedding.embedding.embedded_impact_pain_multiplier, wound_bonus=-30, sharpness = TRUE) + if(!silent) + throw_alert("embeddedobject", /obj/screen/alert/embeddedobject) + visible_message(span_danger("[embedding] embeds itself in [src]'s [body_part.name]!"), span_userdanger("[embedding] embeds itself in your [body_part.name]!")) + return TRUE + +/** + * Removes the given embedded object from this carbon + */ +/mob/living/carbon/proc/remove_embedded_object(obj/item/embedded, new_loc, silent, forced) + var/obj/item/bodypart/body_part + for(var/obj/item/bodypart/part in bodyparts) + if(embedded in part.embedded_objects) + body_part = part + if(!body_part) + return + if(!embedded.on_embed_removal(src)) + return + body_part.embedded_objects -= embedded + if(!silent) + emote("scream") + if(!has_embedded_objects()) + clear_alert("embeddedobject") + SEND_SIGNAL(usr, COMSIG_CLEAR_MOOD_EVENT, "embedded") + if(new_loc) + embedded.forceMove(new_loc) + return TRUE + +/** + * Called when a mob tries to remove an embedded object from this carbon + */ +/mob/living/carbon/proc/try_remove_embedded_object(mob/user) + var/list/choice_list = list() + var/obj/item/bodypart/body_part + for(var/obj/item/bodypart/part in bodyparts) + for(var/obj/item/embedded in part.embedded_objects) + choice_list[embedded] = image(embedded) + var/obj/item/choice = show_radial_menu(user, src, choice_list, tooltips = TRUE) + for(var/obj/item/bodypart/part in bodyparts) + if(choice in part.embedded_objects) + body_part = part + if(!istype(choice) || !(choice in choice_list)) + return + var/time_taken = choice.embedding.embedded_unsafe_removal_time * choice.w_class + user.visible_message(span_warning("[user] attempts to remove [choice] from [usr.p_their()] [body_part.name]."),span_notice("You attempt to remove [choice] from your [body_part.name]... (It will take [DisplayTimeText(time_taken)].)")) + if(!do_after(user, time_taken, needhand = 1, target = src) && !(choice in body_part.embedded_objects)) + return + var/damage_amount = choice.embedding.embedded_unsafe_removal_pain_multiplier * choice.w_class + body_part.receive_damage(damage_amount > 0, sharpness = SHARP_EDGED)//It hurts to rip it out, get surgery you dingus. + if(remove_embedded_object(choice, get_turf(src), damage_amount)) + user.put_in_hands(choice) + user.visible_message("[user] successfully rips [choice] out of [user == src? p_their() : "[src]'s"] [body_part.name]!", span_notice("You successfully remove [choice] from your [body_part.name].")) + return TRUE + /mob/living/carbon/proc/get_interaction_efficiency(zone) var/obj/item/bodypart/limb = get_bodypart(zone) if(!limb) diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index f563234e99ac..75a3cb178ae5 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -32,6 +32,17 @@ else if(get_bodypart(BODY_ZONE_HEAD)) . += span_deadsay("It appears that [t_his] brain is missing...") + var/list/disabled = list() + for(var/X in bodyparts) + var/obj/item/bodypart/body_part = X + if(body_part.bodypart_disabled) + disabled += body_part + for(var/obj/item/I in body_part.embedded_objects) + . += "[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [body_part.name]!\n" + for(var/i in body_part.wounds) + var/datum/wound/iter_wound = i + . += "[iter_wound.get_examine_description(user)]\n" + var/list/missing = get_missing_limbs() for(var/t in missing) if(t==BODY_ZONE_HEAD) @@ -68,12 +79,6 @@ else msg += "[t_He] [t_is] severely deformed!\n" - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - for(var/i in BP.wounds) - var/datum/wound/W = i - msg += "[W.get_examine_description(user)]\n" - if(HAS_TRAIT(src, TRAIT_DUMB)) msg += "[t_He] seem[p_s()] to be clumsy and unable to think.\n" diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 405c177e1ba3..8d8b545aef56 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -231,29 +231,6 @@ spreadFire(AM) /mob/living/carbon/human/Topic(href, href_list) - if(href_list["embedded_object"] && usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) - 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 - var/time_taken = I.embedding.embedded_unsafe_removal_time*I.w_class - usr.visible_message(span_warning("[usr] attempts to remove [I] from [usr.p_their()] [L.name]."),span_notice("You attempt to remove [I] from your [L.name]... (It will take [DisplayTimeText(time_taken)].)")) - if(do_after(usr, time_taken, needhand = 1, target = src)) - if(!I || !L || I.loc != src || !(I in L.embedded_objects)) - return - L.embedded_objects -= I - L.receive_damage(I.embedding.embedded_unsafe_removal_pain_multiplier*I.w_class, sharpness=SHARP_EDGED)//It hurts to rip it out, get surgery you dingus. - I.forceMove(get_turf(src)) - usr.put_in_hands(I) - usr.emote("scream") - usr.visible_message("[usr] successfully rips [I] out of [usr.p_their()] [L.name]!",span_notice("You successfully remove [I] from your [L.name].")) - if(!has_embedded_objects()) - clear_alert("embeddedobject") - SEND_SIGNAL(usr, COMSIG_CLEAR_MOOD_EVENT, "embedded") - return - if(href_list["item"]) //canUseTopic check for this is handled by mob/Topic() var/slot = text2num(href_list["item"]) if(slot in check_obscured_slots(TRUE)) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index de8cc108f01b..e5e3a041939b 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -169,21 +169,6 @@ hitpush = FALSE skipcatch = TRUE blocked = TRUE - else if(I) - if(((throwingdatum ? throwingdatum.speed : I.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || I.embedding.embedded_ignore_throwspeed_threshold) - if(can_embed(I)) - if(prob(I.embedding.embed_chance) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE)) - throw_alert("embeddedobject", /obj/screen/alert/embeddedobject) - var/obj/item/bodypart/L = pick(bodyparts) - L.embedded_objects |= I - I.add_mob_blood(src)//it embedded itself in you, of course it's bloody! - I.forceMove(src) - L.receive_damage(I.w_class*I.embedding.embedded_impact_pain_multiplier, wound_bonus=-30, sharpness = TRUE) - visible_message(span_danger("[I] embeds itself in [src]'s [L.name]!"),span_userdanger("[I] embeds itself in your [L.name]!")) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded) - hitpush = FALSE - skipcatch = TRUE //can't catch the now embedded item - return ..() /mob/living/carbon/human/grippedby(mob/living/user, instant = FALSE) diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index ef6081b5d79e..a685a17423ce 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -38,10 +38,6 @@ //heart attack stuff handle_heart() - if(stat != DEAD) - //Stuff jammed in your limbs hurts - handle_embedded_objects() - dna.species.spec_life(src) // for mutantraces else for(var/i in all_wounds) @@ -307,31 +303,6 @@ return TRUE return ..() - -/mob/living/carbon/human/proc/handle_embedded_objects() - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - for(var/obj/item/I in BP.embedded_objects) - var/pain_chance_current = I.embedding.embedded_pain_chance - if(!(mobility_flags & MOBILITY_STAND)) - pain_chance_current *= 0.2 - if(prob(pain_chance_current)) - BP.receive_damage(I.w_class*I.embedding.embedded_pain_multiplier, wound_bonus = CANT_WOUND) - to_chat(src, span_userdanger("[I] embedded in your [BP.name] hurts!")) - - var/fall_chance_current = I.embedding.embedded_fall_chance - if(!(mobility_flags & MOBILITY_STAND)) - fall_chance_current *= 0.2 - - if(prob(fall_chance_current)) - BP.receive_damage(I.w_class*I.embedding.embedded_fall_pain_multiplier, wound_bonus = CANT_WOUND) // can wound - BP.embedded_objects -= I - I.forceMove(drop_location()) - visible_message(span_danger("[I] falls out of [name]'s [BP.name]!"),span_userdanger("[I] falls out of your [BP.name]!")) - if(!has_embedded_objects()) - clear_alert("embeddedobject") - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "embedded") - /mob/living/carbon/human/proc/handle_heart() var/we_breath = !HAS_TRAIT_FROM(src, TRAIT_NOBREATH, SPECIES_TRAIT) diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 2d9411c6c62f..d4d5cd9c0c4c 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -37,6 +37,10 @@ if(stat != DEAD) handle_liver() + if(stat != DEAD) + //Stuff jammed in your limbs hurts + handle_embedded_objects() + else . = ..() @@ -650,6 +654,35 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put var/datum/brain_trauma/BT = T BT.on_life() + +//////////// +// EMBEDS // +//////////// + +/mob/living/carbon/proc/handle_embedded_objects() + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + for(var/obj/item/I in BP.embedded_objects) + I.embed_tick(src, BP) + var/pain_chance_current = I.embedding.embedded_pain_chance + if(!(mobility_flags & MOBILITY_STAND)) + pain_chance_current *= 0.2 + if(prob(pain_chance_current)) + BP.receive_damage(I.w_class*I.embedding.embedded_pain_multiplier, wound_bonus = CANT_WOUND) + to_chat(src, span_userdanger("[I] embedded in your [BP.name] hurts!")) + + var/fall_chance_current = I.embedding.embedded_fall_chance + if(!(mobility_flags & MOBILITY_STAND)) + fall_chance_current *= 0.2 + + if(prob(fall_chance_current)) + BP.receive_damage(I.w_class*I.embedding.embedded_fall_pain_multiplier, wound_bonus = CANT_WOUND) // can wound + remove_embedded_object(I, drop_location(), FALSE) + visible_message(span_danger("[I] falls out of [name]'s [BP.name]!"), span_userdanger("[I] falls out of your [BP.name]!")) + if(!has_embedded_objects()) + clear_alert("embeddedobject") + SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "embedded") + ///////////////////////////////////// //MONKEYS WITH TOO MUCH CHOLOESTROL// ///////////////////////////////////// diff --git a/code/modules/spells/spell_types/summonitem.dm b/code/modules/spells/spell_types/summonitem.dm index 6bee83f5488c..0382ccc81738 100644 --- a/code/modules/spells/spell_types/summonitem.dm +++ b/code/modules/spells/spell_types/summonitem.dm @@ -86,11 +86,8 @@ for(var/X in C.bodyparts) var/obj/item/bodypart/part = X if(item_to_retrieve in part.embedded_objects) - part.embedded_objects -= item_to_retrieve + C.remove_embedded_object(item_to_retrieve, silent = TRUE, forced = TRUE) to_chat(C, span_warning("The [item_to_retrieve] that was embedded in your [L] has mysteriously vanished. How fortunate!")) - if(!C.has_embedded_objects()) - C.clear_alert("embeddedobject") - SEND_SIGNAL(C, COMSIG_CLEAR_MOOD_EVENT, "embedded") break else diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index a3c5b18e1cec..9e5d282cc961 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -104,8 +104,7 @@ break for(var/obj/item/I in embedded_objects) - embedded_objects -= I - I.forceMove(src) + phantom_owner.remove_embedded_object(I, src, TRUE, TRUE) if(!phantom_owner.has_embedded_objects()) phantom_owner.clear_alert("embeddedobject") SEND_SIGNAL(phantom_owner, COMSIG_CLEAR_MOOD_EVENT, "embedded") diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm index d8896ad88966..594f36c97ca9 100644 --- a/code/modules/surgery/bodyparts/helpers.dm +++ b/code/modules/surgery/bodyparts/helpers.dm @@ -161,6 +161,7 @@ for(var/X in bodyparts) var/obj/item/bodypart/L = X for(var/obj/item/I in L.embedded_objects) + remove_embedded_object(I, T, TRUE, TRUE) L.embedded_objects -= I I.forceMove(T) @@ -168,11 +169,11 @@ SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "embedded") /mob/living/carbon/proc/has_embedded_objects() - . = 0 + . = FALSE for(var/X in bodyparts) var/obj/item/bodypart/L = X for(var/obj/item/I in L.embedded_objects) - return 1 + return TRUE //Helper for quickly creating a new limb - used by augment code in species.dm spec_attacked_by diff --git a/code/modules/surgery/remove_embedded_object.dm b/code/modules/surgery/remove_embedded_object.dm index f6994a28a0d3..3b4f542f5d3a 100644 --- a/code/modules/surgery/remove_embedded_object.dm +++ b/code/modules/surgery/remove_embedded_object.dm @@ -1,14 +1,16 @@ /datum/surgery/embedded_removal name = "Removal of embedded objects" - steps = list(/datum/surgery_step/incise, /datum/surgery_step/remove_object) + steps = list(/datum/surgery_step/incise, /datum/surgery_step/remove_object, /datum/surgery_step/close) possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD) /datum/surgery_step/remove_object name = "remove embedded objects" - time = 32 - accept_hand = 1 + time = 1.5 SECONDS + accept_hand = TRUE fuckup_damage = 0 + repeatable = TRUE + var/obj/item/target_item = null var/obj/item/bodypart/L = null @@ -25,25 +27,20 @@ /datum/surgery_step/remove_object/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) if(L) - if(ishuman(target)) - var/mob/living/carbon/human/H = target - var/objects = 0 - for(var/obj/item/I in L.embedded_objects) - objects++ - I.forceMove(get_turf(H)) - L.embedded_objects -= I - if(!H.has_embedded_objects()) - H.clear_alert("embeddedobject") - SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "embedded") - - if(objects > 0) - display_results(user, target, span_notice("You successfully remove [objects] objects from [H]'s [L.name]."), - "[user] successfully removes [objects] objects from [H]'s [L]!", - "[user] successfully removes [objects] objects from [H]'s [L]!") + if(iscarbon(target)) + if(L.embedded_objects) + var/mob/living/carbon/C = target + var/obj/item/I = pick(L.embedded_objects) + if(C.remove_embedded_object(I, C.drop_location(), TRUE)) + display_results(user, target, span_notice("You successfully remove \the [I] from [C]'s [L.name]."), + "[user] successfully removes \the [I] from [C]'s [L]!", + "[user] successfully removes \the [I] from [C]'s [L]!") + else + to_chat(user, span_warning("You fail to remove \the [I] from [C]'s [L.name]!")) else - to_chat(user, span_warning("You find no objects embedded in [H]'s [L]!")) + to_chat(user, span_warning("You find no objects embedded in [target]'s [L]!")) else to_chat(user, span_warning("You can't find [target]'s [parse_zone(user.zone_selected)], let alone any objects embedded in it!")) - return 1 + return FALSE