From 3499727a6dc2d19c009885347ad1a7e0c04b0bd2 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Fri, 3 Jan 2025 02:18:27 +0300 Subject: [PATCH] Implements datumized embedding handlers in place of element-component-datum triad (#88511) ## About The Pull Request This PR completely rewrites our embedding system in favor of embedding datum handlers which acts as containers for all embedding-related data and logic. Currently embedding logic relies on an element-component-datum triad, where elements on the items handle embedding logic, singleton datums store embedding data and components (which get assigned to ***mobs*** in whom the item embedded) handle pain and the item being ripped out. How do we access all the procs? By using comsigs as procs, which is really bad. This code was written back in 2020 when DCS was hot stuff but in hindsight this implementation was a mistake, as it heavily restricts custom embedding behaviors unless you're willing to constantly run GetComponent (bad, ugly, incarnation of evil) This PR rewrites all that logic to be handled by lazyloaded ``/datum/embedding``, which is stored similarly to current ``/datum/embed_data``. Upon being requested, it is initialized and assigned to a parent from whom all the logic is handled, from being embedded to pain and having the item ripped out. On projectiles this only handles one proc, after which it copies itself down to the shrapnel item instead and runs the chain further from there. Ideally, most embedding-related logic now should be handled purely datum-side - in most cases items should not be hooking up to themselves like they did before (unless said logic is for when the item is made sticky or smth) and instead the code should be handled by the embedding datum (see sholean grapes implementation in this PR). This should allow us to do fancy stuff like syringe guns embedding syringes into targets and injecting them that way, and fix some bugs along the way. Closes #88115 Closes #87946 Also fixed a bug with scars not displaying when examined closely from #86506 because i was in the area anyways --- .../signals_atom/signals_atom_movable.dm | 1 + .../signals/signals_mob/signals_mob_carbon.dm | 4 - code/__DEFINES/dcs/signals/signals_object.dm | 22 +- code/_onclick/hud/alert.dm | 3 +- code/datums/components/dart_insert.dm | 2 +- code/datums/components/embedded.dm | 377 ----------- code/datums/components/tackle.dm | 2 +- code/datums/elements/caseless.dm | 9 +- code/datums/elements/embed.dm | 183 ------ code/datums/embed_data.dm | 58 -- code/datums/embedding.dm | 599 ++++++++++++++++++ code/datums/mutations/tongue_spike.dm | 91 ++- code/game/objects/effects/posters/poster.dm | 5 +- code/game/objects/items.dm | 149 ++--- code/game/objects/items/grenades/plastic.dm | 3 +- code/game/objects/items/knives.dm | 12 +- code/game/objects/items/melee/energy.dm | 4 +- code/game/objects/items/robot/items/food.dm | 4 +- code/game/objects/items/shrapnel.dm | 12 +- code/game/objects/items/spear.dm | 4 +- code/game/objects/items/stacks/rods.dm | 4 +- .../game/objects/items/stacks/sheets/glass.dm | 6 +- code/game/objects/items/stacks/tape.dm | 59 +- code/game/objects/items/tail_pin.dm | 4 +- code/game/objects/items/weaponry.dm | 16 +- .../heretic/structures/carving_knife.dm | 4 +- code/modules/events/wizard/embeddies.dm | 8 +- code/modules/fishing/fish/types/rift.dm | 16 +- code/modules/hydroponics/hydroitemdefines.dm | 4 +- code/modules/hydroponics/plant_genes.dm | 20 +- code/modules/mob/living/carbon/carbon.dm | 10 +- .../mob/living/carbon/carbon_defense.dm | 18 +- code/modules/mob/living/carbon/examine.dm | 18 +- .../mob/living/carbon/human/human_helpers.dm | 3 +- code/modules/mob/living/living_defense.dm | 2 +- .../mob/living/simple_animal/hostile/ooze.dm | 38 +- .../mod/modules/modules_engineering.dm | 4 +- code/modules/paperwork/pen.dm | 20 +- .../guns/ballistic/bows/bow_arrows.dm | 10 +- code/modules/projectiles/projectile.dm | 48 +- .../modules/projectiles/projectile/bullets.dm | 4 +- .../projectiles/projectile/bullets/junk.dm | 40 +- .../projectiles/projectile/bullets/pistol.dm | 24 +- .../projectile/bullets/revolver.dm | 40 +- .../projectiles/projectile/bullets/rifle.dm | 36 +- .../spells/spell_types/self/summonitem.dm | 26 +- code/modules/surgery/bodyparts/_bodyparts.dm | 15 +- .../surgery/bodyparts/dismemberment.dm | 2 - code/modules/surgery/bodyparts/helpers.dm | 12 +- code/modules/unit_tests/embedding.dm | 9 +- code/modules/vehicles/vehicle_key.dm | 4 +- code/modules/vending/_vending.dm | 2 +- tgstation.dme | 4 +- 53 files changed, 1018 insertions(+), 1056 deletions(-) delete mode 100644 code/datums/components/embedded.dm delete mode 100644 code/datums/elements/embed.dm delete mode 100644 code/datums/embed_data.dm create mode 100644 code/datums/embedding.dm diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm index f0024072218..ead6717bcbe 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm @@ -29,6 +29,7 @@ #define COMSIG_MOVABLE_IMPACT "movable_impact" ///from base of mob/living/hitby(): (mob/living/target, hit_zone, blocked, datum/thrownthing/throwingdatum) #define COMSIG_MOVABLE_IMPACT_ZONE "item_impact_zone" + #define MOVABLE_IMPACT_ZONE_OVERRIDE (1<<0) ///from /atom/movable/proc/buckle_mob(): (mob/living/M, force, check_loc, buckle_mob_flags) #define COMSIG_MOVABLE_PREBUCKLE "prebuckle" // this is the last chance to interrupt and block a buckle before it finishes #define COMPONENT_BLOCK_BUCKLE (1<<0) 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 ee157b9d51a..5d9ce528c65 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -73,10 +73,6 @@ ///from /mob/living/carbon/doUnEquip(obj/item/I, force, newloc, no_move, invdrop, silent) #define COMSIG_CARBON_UNEQUIP_SHOECOVER "carbon_unequip_shoecover" #define COMSIG_CARBON_EQUIP_SHOECOVER "carbon_equip_shoecover" -///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/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index a83badb9ee0..96ef802cc87 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -198,8 +198,6 @@ #define COMSIG_TOOL_START_USE "tool_start_use" /// From /obj/item/multitool/remove_buffer(): (buffer) #define COMSIG_MULTITOOL_REMOVE_BUFFER "multitool_remove_buffer" -///from [/obj/item/proc/disableEmbedding]: -#define COMSIG_ITEM_DISABLE_EMBED "item_disable_embed" ///from [/obj/effect/mine/proc/triggermine]: #define COMSIG_MINE_TRIGGERED "minegoboom" ///from [/obj/structure/closet/supplypod/proc/preOpen]: @@ -404,10 +402,7 @@ #define COMSIG_PROJECTILE_RANGE_OUT "projectile_range_out" ///from the base of /obj/projectile/process(): () #define COMSIG_PROJECTILE_BEFORE_MOVE "projectile_before_move" -///from [/obj/item/proc/tryEmbed] sent when trying to force an embed (mainly for projectiles and eating glass) -#define COMSIG_EMBED_TRY_FORCE "item_try_embed" - #define COMPONENT_EMBED_SUCCESS (1<<1) -// FROM [/obj/item/proc/updateEmbedding] sent when an item's embedding properties are changed : () +// FROM [/obj/item/proc/set_embed] sent when an item's embedding properties are changed : () #define COMSIG_ITEM_EMBEDDING_UPDATE "item_embedding_update" ///sent to targets during the process_hit proc of projectiles @@ -418,9 +413,9 @@ ///sent to the projectile after an item is spawned by the projectile_drop element: (new_item) #define COMSIG_PROJECTILE_ON_SPAWN_DROP "projectile_on_spawn_drop" -///sent to the projectile when spawning the item (shrapnel) that may be embedded: (new_item) +///sent to the projectile when spawning the item (shrapnel) that may be embedded: (new_item, victim) #define COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED "projectile_on_spawn_embedded" -///sent to the projectile when successfully embedding into something +///sent to the projectile when successfully embedding into something: (new_item, victim) #define COMSIG_PROJECTILE_ON_EMBEDDED "projectile_on_embedded" // /obj/vehicle/sealed/car/vim signals @@ -477,12 +472,12 @@ #define COMSIG_ITEM_ATTACK_SECONDARY "item_attack_secondary" ///from base of [obj/item/attack()]: (atom/target, mob/user, proximity_flag, click_parameters) #define COMSIG_ITEM_AFTERATTACK "item_afterattack" -///from base of obj/item/embedded(): (atom/target, obj/item/bodypart/part) +///from base of datum/embedding/proc/embed_into(): (mob/living/carbon/victim, obj/item/bodypart/limb) #define COMSIG_ITEM_EMBEDDED "item_embedded" -///from base of datum/component/embedded/safeRemove(): (mob/living/carbon/victim) +///from base of datum/embedding/proc/remove_embedding(): (mob/living/carbon/victim, obj/item/bodypart/limb) #define COMSIG_ITEM_UNEMBEDDED "item_unembedded" -/// from base of obj/item/failedEmbed() -#define COMSIG_ITEM_FAILED_EMBED "item_failed_embed" +///from base of datum/embedding/proc/failed_embed(): (mob/living/carbon/victim, hit_zone) +#define COMSIG_ITEM_FAILED_EMBED "item_unembedded" /// from base of datum/element/disarm_attack/secondary_attack(), used to prevent shoving: (victim, user, send_message) #define COMSIG_ITEM_CAN_DISARM_ATTACK "item_pre_disarm_attack" @@ -501,9 +496,6 @@ #define COMSIG_SPEED_POTION_APPLIED "speed_potion" #define SPEED_POTION_STOP (1<<0) -/// from /obj/structure/sign/poster/trap_succeeded() : (mob/user) -#define COMSIG_POSTER_TRAP_SUCCEED "poster_trap_succeed" - /// from /obj/item/detective_scanner/scan(): (mob/user, list/extra_data) #define COMSIG_DETECTIVE_SCANNED "det_scanned" diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 373e9534ec5..e0f1472b6a8 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -266,8 +266,7 @@ return var/mob/living/carbon/carbon_owner = owner - - return carbon_owner.help_shake_act(carbon_owner) + return carbon_owner.check_self_for_injuries() /atom/movable/screen/alert/negative name = "Negative Gravity" diff --git a/code/datums/components/dart_insert.dm b/code/datums/components/dart_insert.dm index 459da9d217c..42bf777b519 100644 --- a/code/datums/components/dart_insert.dm +++ b/code/datums/components/dart_insert.dm @@ -133,7 +133,7 @@ new_overlays += mutable_appearance(projectile_overlay_icon, projectile_overlay_icon_state) /datum/component/dart_insert/proc/apply_var_modifiers(obj/projectile/projectile) - var_modifiers = istype(modifier_getter) ? modifier_getter.Invoke() : list() + var_modifiers = istype(modifier_getter) ? modifier_getter.Invoke(projectile) : list() projectile.damage += var_modifiers["damage"] projectile.speed += var_modifiers["speed"] projectile.armour_penetration += var_modifiers["armour_penetration"] diff --git a/code/datums/components/embedded.dm b/code/datums/components/embedded.dm deleted file mode 100644 index f6ee85c2372..00000000000 --- a/code/datums/components/embedded.dm +++ /dev/null @@ -1,377 +0,0 @@ -/* - This component is responsible for handling individual instances of embedded objects. The embeddable element is what allows an item to be embeddable and stores its embedding stats, - and when it impacts and meets the requirements to stick into something, it instantiates an embedded component. Once the item falls out, the component is destroyed, while the - element survives to embed another day. - - - Carbon embedding has all the classical embedding behavior, and tracks more events and signals. The main behaviors and hooks to look for are: - -- Every process tick, there is a chance to randomly proc pain, controlled by pain_chance. There may also be a chance for the object to fall out randomly, per fall_chance - -- Every time the mob moves, there is a chance to proc jostling pain, controlled by jostle_chance (and only 50% as likely if the mob is walking or crawling) - -- Various signals hooking into carbon topic() and the embed removal surgery in order to handle removals. - - - In addition, there are 2 cases of embedding: embedding, and sticking - - - Embedding involves harmful and dangerous embeds, whether they cause brute damage, stamina damage, or a mix. This is the default behavior for embeddings, for when something is "pointy" - - - Sticking occurs when an item should not cause any harm while embedding (imagine throwing a sticky ball of tape at someone, rather than a shuriken). An item is considered "sticky" - when it has 0 for both pain multiplier and jostle pain multiplier. It's a bit arbitrary, but fairly straightforward. - - Stickables differ from embeds in the following ways: - -- Text descriptors use phrasing like "X is stuck to Y" rather than "X is embedded in Y" - -- There is no slicing sound on impact - -- All damage checks and bloodloss are skipped - -*/ - -/datum/component/embedded - dupe_mode = COMPONENT_DUPE_ALLOWED - var/obj/item/bodypart/limb - var/obj/item/weapon - ///if both our pain multiplier and jostle pain multiplier are 0, we're harmless and can omit most of the damage related stuff - var/harmful - -/datum/component/embedded/Initialize(obj/item/weapon, - datum/thrownthing/throwingdatum, - obj/item/bodypart/part) - - if(!iscarbon(parent) || !isitem(weapon)) - return COMPONENT_INCOMPATIBLE - - src.weapon = weapon - - if(part) - limb = part - - if(!weapon.is_embed_harmless()) - harmful = TRUE - - weapon.embedded(parent, part) - START_PROCESSING(SSdcs, src) - var/mob/living/carbon/victim = parent - var/datum/embed_data/embed_data = weapon.get_embed() - limb._embed_object(weapon) // on the inside... on the inside... - weapon.forceMove(victim) - RegisterSignals(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING), PROC_REF(weaponDeleted)) - victim.visible_message(span_danger("[weapon] [harmful ? "embeds" : "sticks"] itself [harmful ? "in" : "to"] [victim]'s [limb.plaintext_zone]!"), span_userdanger("[weapon] [harmful ? "embeds" : "sticks"] itself [harmful ? "in" : "to"] your [limb.plaintext_zone]!")) - - var/damage = weapon.throwforce - if(harmful) - victim.throw_alert(ALERT_EMBEDDED_OBJECT, /atom/movable/screen/alert/embeddedobject) - playsound(victim,'sound/items/weapons/bladeslice.ogg', 40) - if (limb.can_bleed()) - weapon.add_mob_blood(victim)//it embedded itself in you, of course it's bloody! - damage += weapon.w_class * embed_data.impact_pain_mult - victim.add_mood_event("embedded", /datum/mood_event/embedded) - - if(damage > 0) - var/armor = victim.run_armor_check(limb.body_zone, MELEE, "Your armor has protected your [limb.plaintext_zone].", "Your armor has softened a hit to your [limb.plaintext_zone].", weapon.armour_penetration, weak_against_armour = weapon.weak_against_armour) - victim.apply_damage( - damage = (1 - embed_data.pain_stam_pct) * damage, - damagetype = BRUTE, - def_zone = limb, - blocked = armor, - wound_bonus = weapon.wound_bonus, - bare_wound_bonus = weapon.bare_wound_bonus, - sharpness = weapon.get_sharpness(), - attacking_item = weapon, - ) - victim.apply_damage( - damage = embed_data.pain_stam_pct * damage, - damagetype = STAMINA, - ) - -/datum/component/embedded/Destroy() - var/mob/living/carbon/victim = parent - if(victim && !victim.has_embedded_objects()) - victim.clear_alert(ALERT_EMBEDDED_OBJECT) - victim.clear_mood_event("embedded") - if(weapon) - UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING)) - weapon = null - limb = null - return ..() - -/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)) - RegisterSignal(parent, COMSIG_ATOM_EX_ACT, PROC_REF(on_ex_act)) - -/datum/component/embedded/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_CARBON_EMBED_RIP, COMSIG_CARBON_EMBED_REMOVAL, COMSIG_ATOM_ATTACKBY, COMSIG_MAGIC_RECALL, COMSIG_ATOM_EX_ACT)) - -/datum/component/embedded/process(seconds_per_tick) - var/mob/living/carbon/victim = parent - - if(!victim || !limb) // in case the victim and/or their limbs exploded (say, due to a sticky bomb) - weapon.forceMove(get_turf(weapon)) - qdel(src) - return - - if(victim.stat == DEAD) - return - - var/datum/embed_data/embed_data = weapon.get_embed() - var/damage = weapon.w_class * embed_data.pain_mult - var/pain_chance_current = SPT_PROB_RATE(embed_data.pain_chance / 100, seconds_per_tick) * 100 - if(embed_data.pain_stam_pct && HAS_TRAIT_FROM(victim, TRAIT_INCAPACITATED, STAMINA)) //if it's a less-lethal embed, give them a break if they're already stamcritted - pain_chance_current *= 0.2 - damage *= 0.5 - else if(victim.body_position == LYING_DOWN) - pain_chance_current *= 0.2 - - if(harmful && prob(pain_chance_current)) - victim.apply_damage( - damage = (1 - embed_data.pain_stam_pct) * damage, - damagetype = BRUTE, - def_zone = limb, - wound_bonus = CANT_WOUND, - sharpness = weapon.get_sharpness(), - attacking_item = weapon, - ) - victim.apply_damage( - damage = embed_data.pain_stam_pct * damage, - damagetype = STAMINA, - ) - to_chat(victim, span_userdanger("[weapon] embedded in your [limb.plaintext_zone] hurts!")) - - var/fall_chance_current = SPT_PROB_RATE(embed_data.fall_chance / 100, seconds_per_tick) * 100 - if(victim.body_position == LYING_DOWN) - fall_chance_current *= 0.2 - - if(prob(fall_chance_current)) - fallOut() - -/datum/component/embedded/proc/on_ex_act(atom/source, severity) - SIGNAL_HANDLER - // In the process of parent's ex_act - if (QDELETED(weapon)) - return - switch(severity) - if(EXPLODE_DEVASTATE) - SSexplosions.high_mov_atom += weapon - if(EXPLODE_HEAVY) - SSexplosions.med_mov_atom += weapon - if(EXPLODE_LIGHT) - SSexplosions.low_mov_atom += weapon - -//////////////////////////////////////// -////////////BEHAVIOR PROCS////////////// -//////////////////////////////////////// - - -/// Called every time a carbon with a harmful embed moves, rolling a chance for the item to cause pain. The chance is halved if the carbon is crawling or walking. -/datum/component/embedded/proc/jostleCheck() - SIGNAL_HANDLER - - var/mob/living/carbon/victim = parent - var/datum/embed_data/embed_data = weapon.get_embed() - var/chance = embed_data.jostle_chance - if(victim.move_intent == MOVE_INTENT_WALK || victim.body_position == LYING_DOWN) - chance *= 0.5 - - if(harmful && prob(chance)) - var/damage = weapon.w_class * embed_data.jostle_pain_mult - victim.apply_damage( - damage = (1 - embed_data.pain_stam_pct) * damage, - damagetype = BRUTE, - def_zone = limb, - wound_bonus = CANT_WOUND, - sharpness = weapon.get_sharpness(), - attacking_item = weapon, - ) - victim.apply_damage( - damage = embed_data.pain_stam_pct * damage, - damagetype = STAMINA, - ) - to_chat(victim, span_userdanger("[weapon] embedded in your [limb.plaintext_zone] jostles and stings!")) - embed_data.jostle_callback?.Invoke(victim, weapon, embed_data) - - -/// Called when then item randomly falls out of a carbon. This handles the damage and descriptors, then calls safe_remove() -/datum/component/embedded/proc/fallOut() - var/mob/living/carbon/victim = parent - var/datum/embed_data/embed_data = weapon.get_embed() - - if(harmful) - var/damage = weapon.w_class * embed_data.remove_pain_mult - victim.apply_damage( - damage = (1 - embed_data.pain_stam_pct) * damage, - damagetype = BRUTE, - def_zone = limb, - wound_bonus = CANT_WOUND, - sharpness = weapon.get_sharpness(), - attacking_item = weapon, - ) - victim.apply_damage( - damage = embed_data.pain_stam_pct * damage, - damagetype = STAMINA, - ) - victim.visible_message(span_danger("[weapon] falls [harmful ? "out" : "off"] of [victim.name]'s [limb.plaintext_zone]!"), span_userdanger("[weapon] falls [harmful ? "out" : "off"] of your [limb.plaintext_zone]!")) - safeRemove() - - -/// Called when a carbon with an object embedded/stuck to them inspects themselves and clicks the appropriate link to begin ripping the item out. This handles the ripping attempt, descriptors, and dealing damage, then calls safe_remove() -/datum/component/embedded/proc/ripOut(datum/source, obj/item/I, obj/item/bodypart/limb) - SIGNAL_HANDLER - - 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 - 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) - victim.visible_message(span_warning("[victim] attempts to remove [weapon] from [victim.p_their()] [limb.plaintext_zone]."),span_notice("You attempt to remove [weapon] from your [limb.plaintext_zone]... (It will take [DisplayTimeText(time_taken)])")) - if(!do_after(victim, time_taken, target = victim)) - return - if(!weapon || !limb || weapon.loc != victim || !(weapon in limb.embedded_objects)) - qdel(src) - return - if(harmful) - damaging_removal(victim, I, limb) - - victim.visible_message(span_notice("[victim] successfully rips [weapon] [harmful ? "out" : "off"] of [victim.p_their()] [limb.plaintext_zone]!"), span_notice("You successfully remove [weapon] from your [limb.plaintext_zone].")) - safeRemove(victim) - -/// Proc that actually does the damage associated with ripping something out of yourself. Call this before safeRemove. -/datum/component/embedded/proc/damaging_removal(mob/living/carbon/victim, obj/item/removed, obj/item/bodypart/limb, ouch_multiplier = 1) - var/datum/embed_data/embed_data = weapon.get_embed() - var/damage = weapon.w_class * embed_data.remove_pain_mult * ouch_multiplier - victim.apply_damage( - damage = (1 - embed_data.pain_stam_pct) * damage, - damagetype = BRUTE, - def_zone = limb, - wound_bonus = max(0, weapon.wound_bonus), // It hurts to rip it out, get surgery you dingus. unlike the others, this CAN wound + increase slash bloodflow - sharpness = weapon.get_sharpness() || SHARP_EDGED, // always sharp, even if the object isn't - attacking_item = weapon, - ) - victim.apply_damage( - damage = embed_data.pain_stam_pct * damage, - damagetype = STAMINA, - ) - victim.emote("scream") - -/// This proc handles the final step and actual removal of an embedded/stuck item from a carbon, whether or not it was actually removed safely. -/// If you want the thing to go into someone's hands rather than the floor, pass them in to_hands -/datum/component/embedded/proc/safeRemove(mob/to_hands) - SIGNAL_HANDLER - - var/mob/living/carbon/victim = parent - limb._unembed_object(weapon) - UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING)) // have to do it here otherwise we trigger weaponDeleted() - - SEND_SIGNAL(weapon, COMSIG_ITEM_UNEMBEDDED, victim) - if(!weapon.unembedded()) // if it hasn't deleted itself due to drop del - UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING)) - if(to_hands) - INVOKE_ASYNC(to_hands, TYPE_PROC_REF(/mob, put_in_hands), weapon) - else - weapon.forceMove(get_turf(victim)) - - qdel(src) - -/// Something deleted or moved our weapon while it was embedded, how rude! -/datum/component/embedded/proc/weaponDeleted() - SIGNAL_HANDLER - - var/mob/living/carbon/victim = parent - limb._unembed_object(weapon) - - if(victim) - to_chat(victim, span_userdanger("\The [weapon] that was embedded in your [limb.plaintext_zone] disappears!")) - - qdel(src) - -/// The signal for listening to see if someone is using a hemostat on us to pluck out this object -/datum/component/embedded/proc/checkTweeze(mob/living/carbon/victim, obj/item/possible_tweezers, mob/user) - SIGNAL_HANDLER - - 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 - - INVOKE_ASYNC(src, PROC_REF(tweezePluck), possible_tweezers, 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) - - if(self_pluck) - user.visible_message(span_danger("[user] begins plucking [weapon] from [user.p_their()] [limb.plaintext_zone] with [possible_tweezers]..."), span_notice("You start plucking [weapon] from your [limb.plaintext_zone] with [possible_tweezers]... (It will take [DisplayTimeText(pluck_time)])"),\ - vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=victim) - else - user.visible_message(span_danger("[user] begins plucking [weapon] from [victim]'s [limb.plaintext_zone] with [possible_tweezers]..."),span_notice("You start plucking [weapon] from [victim]'s [limb.plaintext_zone] with [possible_tweezers]... (It will take [DisplayTimeText(pluck_time)])"), \ - vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=victim) - to_chat(victim, span_userdanger("[user] begins plucking [weapon] from your [limb.plaintext_zone] with [possible_tweezers]... (It will take [DisplayTimeText(pluck_time)])")) - - if(!do_after(user, pluck_time, victim)) - if(self_pluck) - to_chat(user, span_danger("You fail to pluck [weapon] from your [limb.plaintext_zone].")) - else - to_chat(user, span_danger("You fail to pluck [weapon] from [victim]'s [limb.plaintext_zone].")) - to_chat(victim, span_danger("[user] fails to pluck [weapon] from your [limb.plaintext_zone].")) - return - - to_chat(user, span_notice("You successfully pluck [weapon] from [victim]'s [limb.plaintext_zone][tweezer_safe ? "." : ", but hurt [victim.p_them()] in the process."]")) - to_chat(victim, span_notice("[user] plucks [weapon] from your [limb.plaintext_zone][tweezer_safe ? "." : ", but it's not perfect."]")) - if(!tweezer_safe) - // sure it still hurts but it sucks less - damaging_removal(victim, weapon, limb, (0.4 * possible_tweezers.w_class)) - safeRemove(user) - -/// 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) - SIGNAL_HANDLER - - if(marked_item != weapon) - return - - var/mob/living/carbon/victim = parent - - if(!harmful) - victim.visible_message(span_danger("[marked_item] vanishes from [victim.name]'s [limb.plaintext_zone]!"), span_userdanger("[weapon] vanishes from [limb.plaintext_zone]!")) - return - - var/datum/embed_data/embed_data = weapon.get_embed() - var/damage = weapon.w_class * embed_data.remove_pain_mult - victim.apply_damage( - damage = (1 - embed_data.pain_stam_pct) * damage * 1.5, - damagetype = BRUTE, - def_zone = limb, - wound_bonus = max(0, weapon.wound_bonus), // Performs exit wounds and flings the user to the caster if nearby - sharpness = weapon.get_sharpness() || SHARP_EDGED, - attacking_item = weapon, - ) - victim.apply_damage( - damage = embed_data.pain_stam_pct * damage, - damagetype = STAMINA, - ) - victim.cause_wound_of_type_and_severity(WOUND_PIERCE, limb, WOUND_SEVERITY_MODERATE) - playsound(victim, 'sound/effects/wounds/blood2.ogg', 50, TRUE) - - var/dist = get_dist(caster, victim) //Check if the caster is close enough to yank them in - if(dist < 7) - victim.throw_at(caster, get_dist(victim, caster) - 1, 1, caster) - victim.Paralyze(1 SECONDS) - victim.visible_message(span_alert("[victim] is sent flying towards [caster] as the [marked_item] tears out of them!"), span_alert("You are launched at [caster] as the [marked_item] tears from your body and towards their hand!")) - victim.visible_message(span_danger("[marked_item] is violently torn from [victim.name]'s [limb.plaintext_zone]!"), span_userdanger("[weapon] is violently torn from your [limb.plaintext_zone]!")) diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm index 5f5ac41a57e..a55bfc38c1c 100644 --- a/code/datums/components/tackle.dm +++ b/code/datums/components/tackle.dm @@ -601,7 +601,7 @@ if(windscreen_casualty.type in list(/obj/structure/window, /obj/structure/window/fulltile, /obj/structure/window/unanchored, /obj/structure/window/fulltile/unanchored)) // boring unreinforced windows for(var/i in 1 to speed) var/obj/item/shard/shard = new /obj/item/shard(get_turf(user)) - shard.set_embed(/datum/embed_data/glass_candy) + shard.set_embed(/datum/embedding/glass_candy) user.hitby(shard, skipcatch = TRUE, hitpush = FALSE) shard.set_embed(initial(shard.embed_type)) windscreen_casualty.atom_destruction() diff --git a/code/datums/elements/caseless.dm b/code/datums/elements/caseless.dm index 9b1c0601207..a07c994fe5c 100644 --- a/code/datums/elements/caseless.dm +++ b/code/datums/elements/caseless.dm @@ -19,12 +19,11 @@ /datum/element/caseless/proc/on_ready_projectile(obj/item/ammo_casing/shell, atom/target, mob/living/user, quiet, zone_override, atom/fired_from) SIGNAL_HANDLER var/obj/projectile/proj = shell.loaded_projectile - if(isnull(proj)) + if(isnull(proj) || !reusable) return - if(reusable) - if(!ispath(proj.shrapnel_type)) - proj.shrapnel_type = shell.type - proj.AddElement(/datum/element/projectile_drop, shell.type) + if(!ispath(proj.shrapnel_type)) + proj.shrapnel_type = shell.type + proj.AddElement(/datum/element/projectile_drop, shell.type) /datum/element/caseless/proc/on_fired_casing(obj/item/ammo_casing/shell, atom/target, mob/living/user, fired_from, randomspread, spread, zone_override, params, distro, obj/projectile/proj) SIGNAL_HANDLER diff --git a/code/datums/elements/embed.dm b/code/datums/elements/embed.dm deleted file mode 100644 index 90787f85817..00000000000 --- a/code/datums/elements/embed.dm +++ /dev/null @@ -1,183 +0,0 @@ -/* - The presence of this element allows an item (or a projectile carrying an item) to embed itself in a carbon when it is thrown into a target (whether by hand, gun, or explosive wave) with either - at least 4 throwspeed (EMBED_THROWSPEED_THRESHOLD) or ignore_throwspeed_threshold set to TRUE. Items meant to be used as shrapnel for projectiles should have ignore_throwspeed_threshold set to true. - - Whether we're dealing with a direct /obj/item (throwing a knife at someone) or an /obj/projectile with a shrapnel_type, how we handle things plays out the same, with one extra step separating them. - Items simply make their COMSIG_MOVABLE_IMPACT_ZONE check, while projectiles check on COMSIG_PROJECTILE_SELF_ON_HIT. - Upon a projectile hitting a valid target, it spawns whatever type of payload it has defined, then has that try to embed itself in the target on its own. - - Otherwise non-embeddable or stickable items can be made embeddable/stickable through wizard events/sticky tape/admin memes. -*/ - -/datum/element/embed - -/datum/element/embed/Attach(datum/target) - . = ..() - - if(!isitem(target) && !isprojectile(target)) - return ELEMENT_INCOMPATIBLE - - RegisterSignal(target, COMSIG_ELEMENT_ATTACH, PROC_REF(sever_element)) - if(isprojectile(target)) - RegisterSignal(target, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(check_embed_projectile)) - return - - RegisterSignal(target, COMSIG_MOVABLE_IMPACT_ZONE, PROC_REF(check_embed)) - RegisterSignal(target, COMSIG_ATOM_EXAMINE_TAGS, PROC_REF(examined_tags)) - RegisterSignal(target, COMSIG_EMBED_TRY_FORCE, PROC_REF(try_force_embed)) - RegisterSignal(target, COMSIG_ITEM_DISABLE_EMBED, PROC_REF(detach_from_weapon)) - -/datum/element/embed/Detach(obj/target) - . = ..() - if(isprojectile(target)) - UnregisterSignal(target, list(COMSIG_PROJECTILE_SELF_ON_HIT, COMSIG_ELEMENT_ATTACH)) - return - - UnregisterSignal(target, list(COMSIG_MOVABLE_IMPACT_ZONE, COMSIG_ELEMENT_ATTACH, COMSIG_MOVABLE_IMPACT, COMSIG_ATOM_EXAMINE, COMSIG_EMBED_TRY_FORCE, COMSIG_ITEM_DISABLE_EMBED)) - -/// Checking to see if we're gonna embed into a human -/datum/element/embed/proc/check_embed(obj/item/weapon, mob/living/carbon/victim, hit_zone, blocked, datum/thrownthing/throwingdatum, forced=FALSE) - SIGNAL_HANDLER - - if(forced) - embed_object(weapon, victim, hit_zone, throwingdatum) - return TRUE - - if(blocked || !istype(victim) || HAS_TRAIT(victim, TRAIT_PIERCEIMMUNE)) - return FALSE - - if(HAS_TRAIT(victim, TRAIT_GODMODE)) - return FALSE - - var/flying_speed = throwingdatum?.speed || weapon.throw_speed - - if(flying_speed < EMBED_THROWSPEED_THRESHOLD && !weapon.get_embed().ignore_throwspeed_threshold) - return FALSE - - if(!roll_embed_chance(weapon, victim, hit_zone, throwingdatum)) - return FALSE - - embed_object(weapon, victim, hit_zone, throwingdatum) - return TRUE - -/// Actually sticks the object to a victim -/datum/element/embed/proc/embed_object(obj/item/weapon, mob/living/carbon/victim, hit_zone, datum/thrownthing/throwingdatum) - var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || pick(victim.bodyparts) - victim.AddComponent(/datum/component/embedded,\ - weapon,\ - throwingdatum,\ - part = limb) - -///A different embed element has been attached, so we'll detach and let them handle things -/datum/element/embed/proc/sever_element(obj/weapon, datum/element/E) - SIGNAL_HANDLER - - if(istype(E, /datum/element/embed)) - Detach(weapon) - -///If we don't want to be embeddable anymore (deactivating an e-dagger for instance) -/datum/element/embed/proc/detach_from_weapon(obj/weapon) - SIGNAL_HANDLER - - Detach(weapon) - -///Someone inspected our embeddable item -/datum/element/embed/proc/examined_tags(obj/item/I, mob/user, list/examine_list) - SIGNAL_HANDLER - - if(I.is_embed_harmless()) - examine_list["sticky"] = "[I] feels sticky, and could probably get stuck to someone if thrown properly!" - else - examine_list["embeddable"] = "[I] has a fine point, and could probably embed in someone if thrown properly!" - -/** - * check_embed_projectile() is what we get when a projectile with a defined shrapnel_type impacts a target. - * - * If we hit a valid target, we create the shrapnel_type object and then forcefully try to embed it on its - * behalf. DO NOT EVER add an embed element to the payload and let it do the rest. - * That's awful, and it'll limit us to drop-deletable shrapnels in the worry of stuff like - * arrows and harpoons being embeddable even when not let loose by their weapons. - */ -/datum/element/embed/proc/check_embed_projectile(obj/projectile/source, atom/movable/firer, atom/hit, angle, hit_zone, blocked, pierce_hit) - SIGNAL_HANDLER - - if (pierce_hit) - return - - if(!source.can_embed_into(hit) || blocked) - Detach(source) - return // we don't care - - var/payload_type = source.shrapnel_type - var/obj/item/payload = new payload_type(get_turf(hit)) - payload.set_embed(source.get_embed()) - if(istype(payload, /obj/item/shrapnel/bullet)) - payload.name = source.name - SEND_SIGNAL(source, COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED, payload) - var/mob/living/carbon/C = hit - var/obj/item/bodypart/limb = C.get_bodypart(hit_zone) - if(!limb) - limb = C.get_bodypart() - - if(!try_force_embed(payload, limb)) - payload.failedEmbed() - else - SEND_SIGNAL(source, COMSIG_PROJECTILE_ON_EMBEDDED, payload, hit) - Detach(source) - -/** - * try_force_embed() is called here when we fire COMSIG_EMBED_TRY_FORCE from [/obj/item/proc/tryEmbed]. Mostly, this means we're a piece of shrapnel from a projectile that just impacted something, and we're trying to embed in it. - * - * The reason for this extra mucking about is avoiding having to do an extra hitby(), and annoying the target by impacting them once with the projectile, then again with the shrapnel, and possibly - * AGAIN if we actually embed. This way, we save on at least one message. - * - * Arguments: - * * embedding_item- the item we're trying to insert into the target - * * target- what we're trying to shish-kabob, either a bodypart or a carbon - * * hit_zone- if our target is a carbon, try to hit them in this zone, if we don't have one, pick a random one. If our target is a bodypart, we already know where we're hitting. - * * forced- if we want this to succeed 100% - */ -/datum/element/embed/proc/try_force_embed(obj/item/embedding_item, atom/target, hit_zone, forced=FALSE) - SIGNAL_HANDLER - - var/obj/item/bodypart/limb - var/mob/living/carbon/victim - - if(iscarbon(target)) - victim = target - if(!hit_zone) - limb = pick(victim.bodyparts) - hit_zone = limb.body_zone - else if(isbodypart(target)) - limb = target - hit_zone = limb.body_zone - victim = limb.owner - - if(!forced && !roll_embed_chance(embedding_item, victim, hit_zone)) - return - - return check_embed(embedding_item, victim, hit_zone, forced=TRUE) // Don't repeat the embed roll, we already did it - -/// Calculates the actual chance to embed based on armour penetration and throwing speed, then returns true if we pass that probability check -/datum/element/embed/proc/roll_embed_chance(obj/item/embedding_item, mob/living/victim, hit_zone, datum/thrownthing/throwingdatum) - var/actual_chance = embedding_item.get_embed().embed_chance - - if(throwingdatum?.speed > embedding_item.throw_speed) - actual_chance += (throwingdatum.speed - embedding_item.throw_speed) * EMBED_CHANCE_SPEED_BONUS - - if(embedding_item.is_embed_harmless()) // all the armor in the world won't save you from a kick me sign - return prob(actual_chance) - - var/armor = max(victim.run_armor_check(hit_zone, BULLET, silent=TRUE), victim.run_armor_check(hit_zone, BOMB, silent=TRUE)) * 0.5 // we'll be nice and take the better of bullet and bomb armor, halved - if(!armor) // we only care about armor penetration if there's actually armor to penetrate - return prob(actual_chance) - - //Keep this above 1, as it is a multiplier for the pen_mod for determining actual embed chance. - var/penetrative_behaviour = embedding_item.weak_against_armour ? ARMOR_WEAKENED_MULTIPLIER : 1 - var/pen_mod = -(armor * penetrative_behaviour) // if our shrapnel is weak into armor, then we restore our armor to the full value. - actual_chance += pen_mod // doing the armor pen as a separate calc just in case this ever gets expanded on - if(actual_chance <= 0) - victim.visible_message(span_danger("[embedding_item] bounces off [victim]'s armor, unable to embed!"), span_notice("[embedding_item] bounces off your armor, unable to embed!"), vision_distance = COMBAT_MESSAGE_RANGE) - return FALSE - - return prob(actual_chance) diff --git a/code/datums/embed_data.dm b/code/datums/embed_data.dm deleted file mode 100644 index 865b285d09b..00000000000 --- a/code/datums/embed_data.dm +++ /dev/null @@ -1,58 +0,0 @@ -/// Assosciative list of type -> embed data. -GLOBAL_LIST_INIT(embed_by_type, generate_embed_type_cache()) - -/proc/generate_embed_type_cache() - var/list/embed_cache = list() - for(var/datum/embed_data/embed_type as anything in subtypesof(/datum/embed_data)) - var/datum/embed_data/embed = new embed_type - embed_cache[embed_type] = embed - return embed_cache - -/proc/get_embed_by_type(embed_type) - var/datum/embed_data/embed = GLOB.embed_by_type[embed_type] - if(embed) - return embed - CRASH("Attempted to get an embed type that did not exist! '[embed_type]'") - -/datum/embed_data - /// Chance for an object to embed into somebody when thrown - var/embed_chance = 45 - /// Chance for embedded object to fall out (causing pain but removing the object) - var/fall_chance = 5 - /// Chance for embedded objects to cause pain (damage user) - var/pain_chance = 15 - /// Coefficient of multiplication for the damage the item does while embedded (this*item.w_class) - var/pain_mult = 2 - /// Coefficient of multiplication for the damage the item does when it first embeds (this*item.w_class) - 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) - var/jostle_chance = 5 - /// Coefficient of multiplication for the damage the item does while - var/jostle_pain_mult = 1 - /// Call this proc on jostling, if it exists! - var/datum/callback/jostle_callback - /// This percentage of all pain will be dealt as stam damage rather than brute (0-1) - var/pain_stam_pct = 0 - -/datum/embed_data/proc/generate_with_values(embed_chance, fall_chance, pain_chance, pain_mult, impact_pain_mult, remove_pain_mult, rip_time, ignore_throwspeed_threshold, jostle_chance, jostle_pain_mult, pain_stam_pct, force_new = FALSE) - var/datum/embed_data/data = isnull(GLOB.embed_by_type[type]) && !force_new ? src : new() - - data.embed_chance = !isnull(embed_chance) ? embed_chance : src.embed_chance - data.fall_chance = !isnull(fall_chance) ? fall_chance : src.fall_chance - data.pain_chance = !isnull(pain_chance) ? pain_chance : src.pain_chance - data.pain_mult = !isnull(pain_mult) ? pain_mult : src.pain_mult - data.impact_pain_mult = !isnull(impact_pain_mult) ? impact_pain_mult : src.impact_pain_mult - data.remove_pain_mult = !isnull(remove_pain_mult) ? remove_pain_mult : src.remove_pain_mult - data.rip_time = !isnull(rip_time) ? rip_time : src.rip_time - data.ignore_throwspeed_threshold = !isnull(ignore_throwspeed_threshold) ? ignore_throwspeed_threshold : src.ignore_throwspeed_threshold - data.jostle_chance = !isnull(jostle_chance) ? jostle_chance : src.jostle_chance - data.jostle_pain_mult = !isnull(jostle_pain_mult) ? jostle_pain_mult : src.jostle_pain_mult - data.jostle_callback = !isnull(jostle_callback) ? jostle_callback : src.jostle_callback - data.pain_stam_pct = !isnull(pain_stam_pct) ? pain_stam_pct : src.pain_stam_pct - return data diff --git a/code/datums/embedding.dm b/code/datums/embedding.dm new file mode 100644 index 00000000000..a61d7aa6903 --- /dev/null +++ b/code/datums/embedding.dm @@ -0,0 +1,599 @@ +/// How quicker is it for someone else to rip out an item? +#define RIPPING_OUT_HELP_TIME_MULTIPLIER 0.75 +/// How much safer is it for someone else to rip out an item? +#define RIPPING_OUT_HELP_DAMAGE_MULTIPLIER 0.75 + +/* + * The magical embedding datum which is a container for all embedding interactions an item (or a projectile) can have. + * Whenever an item with an embedding datum is thrown into a carbon with either EMBED_THROWSPEED_THRESHOLD throwspeed or ignore_throwspeed_threshold set to TRUE, it will + * embed into them, with latter option reserved for sticky items and shrapnel. + * Whenever a projectile embeds, the datum is copied onto the shrapnel + */ + +/datum/embedding + /// Chance for an object to embed into somebody when thrown + var/embed_chance = 45 + /// Chance for embedded object to fall out (causing pain but removing the object) + var/fall_chance = 5 + /// Chance for embedded objects to cause pain (damage user) + var/pain_chance = 15 + /// Coefficient of multiplication for the damage the item does while embedded (this*item.w_class) + var/pain_mult = 2 + /// Coefficient of multiplication for the damage the item does when it first embeds (this*item.w_class) + 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 = 3 SECONDS + /// 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) + var/jostle_chance = 5 + /// Coefficient of multiplication for the damage the item does while + var/jostle_pain_mult = 1 + /// This percentage of all pain will be dealt as stam damage rather than brute (0-1) + var/pain_stam_pct = 0 + /// Traits which make target immune to us embedding into them, any trait from the list works + var/list/immune_traits = list(TRAIT_PIERCEIMMUNE) + + /// Thing that we're attached to + VAR_FINAL/obj/item/parent + /// Mob we've embedded into, if any + VAR_FINAL/mob/living/carbon/owner + /// Limb we've embedded into in whose contents we reside + VAR_FINAL/obj/item/bodypart/owner_limb + +/datum/embedding/New(obj/item/creator) + . = ..() + if (creator) + register_on(creator) + +/// Registers ourselves with an item +/datum/embedding/proc/register_on(obj/item/new_parent) + if(!isitem(new_parent)) + CRASH("Embedding datum attempted to register on a non-item object [new_parent] ([new_parent?.type])") + + parent = new_parent + RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(on_qdel)) + + RegisterSignal(parent, COMSIG_MOVABLE_IMPACT_ZONE, PROC_REF(try_embed)) + RegisterSignal(parent, COMSIG_ATOM_EXAMINE_TAGS, PROC_REF(examined_tags)) + +/datum/embedding/Destroy(force) + if (!parent) + return ..() + parent.set_embed(null) + UnregisterSignal(parent, list(COMSIG_QDELETING, COMSIG_MOVABLE_IMPACT_ZONE, COMSIG_ATOM_EXAMINE)) + owner = null + owner_limb = null + parent = null + return ..() + +/// Creates a copy and sets all of its *relevant* variables +/// Children should override this with new variables if they add any "generic" ones +/datum/embedding/proc/create_copy(atom/movable/new_owner) + var/datum/embedding/brother = new type(new_owner) + brother.embed_chance = embed_chance + brother.fall_chance = fall_chance + brother.pain_chance = pain_chance + brother.pain_mult = pain_mult + brother.impact_pain_mult = impact_pain_mult + brother.remove_pain_mult = remove_pain_mult + brother.rip_time = rip_time + brother.ignore_throwspeed_threshold = ignore_throwspeed_threshold + brother.jostle_chance = jostle_chance + brother.jostle_pain_mult = jostle_pain_mult + brother.pain_stam_pct = pain_stam_pct + brother.immune_traits = immune_traits.Copy() + return brother + +///Someone inspected our embeddable item +/datum/embedding/proc/examined_tags(obj/item/source, mob/user, list/examine_list) + SIGNAL_HANDLER + + if(is_harmless()) + examine_list["sticky"] = "[parent] looks sticky, and could probably get stuck to someone if thrown properly!" + else + examine_list["embeddable"] = "[parent] has a fine point, and could probably embed in someone if thrown properly!" + +/// Is passed victim a valid target for us to embed into? +/datum/embedding/proc/can_embed(atom/movable/source, mob/living/carbon/victim, hit_zone, datum/thrownthing/throwingdatum) + if (!istype(victim)) + return FALSE + + if (HAS_TRAIT(victim, TRAIT_GODMODE)) + return + + if (immune_traits) + for (var/immunity_trait in immune_traits) + if (HAS_TRAIT(victim, immunity_trait)) + return FALSE + + if (isitem(source)) + var/flying_speed = throwingdatum?.speed || source.throw_speed + if(flying_speed < EMBED_THROWSPEED_THRESHOLD && !ignore_throwspeed_threshold) + return FALSE + + return TRUE + +/// Attempts to embed an object +/datum/embedding/proc/try_embed(obj/item/weapon, mob/living/carbon/victim, hit_zone, blocked, datum/thrownthing/throwingdatum) + SIGNAL_HANDLER + + if (blocked || !can_embed(parent, victim, hit_zone, throwingdatum)) + failed_embed(victim, hit_zone) + return + + if (!roll_embed_chance(victim, hit_zone, throwingdatum)) + failed_embed(victim, hit_zone, random = TRUE) + return + + var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || victim.bodyparts[1] + embed_into(victim, limb) + return MOVABLE_IMPACT_ZONE_OVERRIDE + +/// Attempts to embed shrapnel from a projectile +/datum/embedding/proc/try_embed_projectile(obj/projectile/source, atom/hit, hit_zone, blocked, pierce_hit) + if (pierce_hit) + return + + if (blocked || !can_embed(source, hit)) + failed_embed(hit, hit_zone) + return + + var/mob/living/carbon/victim = hit + var/shrapnel_type = source.shrapnel_type + var/obj/item/payload = new shrapnel_type(get_turf(victim)) + setup_shrapnel(payload, source, victim) + + if (!roll_embed_chance(victim, hit_zone)) + failed_embed(victim, hit_zone, random = TRUE) + return + + var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || victim.bodyparts[1] + embed_into(victim, limb) + SEND_SIGNAL(source, COMSIG_PROJECTILE_ON_EMBEDDED, payload, hit) + +/// Used for custom logic while setting up shrapnel payload +/datum/embedding/proc/setup_shrapnel(obj/item/payload, obj/projectile/source, mob/living/carbon/victim) + // Detach from parent, we don't want em to delete us + source.set_embed(null, dont_delete = TRUE) + // Hook signals up first, as payload sends a comsig upon embed update + register_on(payload) + payload.set_embed(src) + if(istype(payload, /obj/item/shrapnel/bullet)) + payload.name = source.name + SEND_SIGNAL(source, COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED, payload, victim) + +/// Calculates the actual chance to embed based on armour penetration and throwing speed, then returns true if we pass that probability check +/datum/embedding/proc/roll_embed_chance(mob/living/carbon/victim, hit_zone, datum/thrownthing/throwingdatum) + var/chance = embed_chance + + // Something threw us really, really fast + if (throwingdatum?.speed > parent.throw_speed) + chance += (throwingdatum.speed - parent.throw_speed) * EMBED_CHANCE_SPEED_BONUS + + if (is_harmless()) + return prob(embed_chance) + + // We'll be nice and take the better of bullet and bomb armor, halved + var/armor = max(victim.run_armor_check(hit_zone, BULLET, armour_penetration = parent.armour_penetration, silent = TRUE), victim.run_armor_check(hit_zone, BOMB, armour_penetration = parent.armour_penetration, silent = TRUE)) * 0.5 + // We only care about armor penetration if there's actually armor to penetrate + if(!armor) + return prob(chance) + + if (parent.weak_against_armour) + armor *= ARMOR_WEAKENED_MULTIPLIER + + chance -= armor + if (chance < 0) + victim.visible_message(span_danger("[parent] bounces off [victim]'s armor, unable to embed!"), + span_notice("[parent] bounces off your armor, unable to embed!"), vision_distance = COMBAT_MESSAGE_RANGE) + return FALSE + + return prob(chance) + +/// We've tried to embed into something and failed +/// Random being TRUE means we've lost the roulette, FALSE means we've either been blocked or the target is invalid +/datum/embedding/proc/failed_embed(mob/living/carbon/victim, hit_zone, random = FALSE) + if (!istype(parent)) + return + SEND_SIGNAL(parent, COMSIG_ITEM_FAILED_EMBED, victim, hit_zone) + if((parent.item_flags & DROPDEL) && !QDELETED(parent)) + qdel(parent) + +/// Does this item deal any damage when embedding or jostling inside of someone? +/datum/embedding/proc/is_harmless() + return pain_mult == 0 && jostle_pain_mult == 0 + +//Handles actual embedding logic. +/datum/embedding/proc/embed_into(mob/living/carbon/victim, obj/item/bodypart/target_limb) + SHOULD_NOT_OVERRIDE(TRUE) + + set_owner(victim, target_limb) + + START_PROCESSING(SSprocessing, src) + owner_limb._embed_object(parent) + parent.forceMove(owner) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(weapon_disappeared)) + RegisterSignal(parent, COMSIG_MAGIC_RECALL, PROC_REF(magic_pull)) + owner.visible_message(span_danger("[parent] [is_harmless() ? "sticks itself to" : "embeds itself in"] [owner]'s [owner_limb.plaintext_zone]!"), + span_userdanger("[parent] [is_harmless() ? "sticks itself to" : "embeds itself in"] your [owner_limb.plaintext_zone]!")) + + var/damage = parent.throwforce + if (!is_harmless()) + owner.throw_alert(ALERT_EMBEDDED_OBJECT, /atom/movable/screen/alert/embeddedobject) + playsound(owner,'sound/items/weapons/bladeslice.ogg', 40) + if (owner_limb.can_bleed()) + parent.add_mob_blood(owner) // it embedded itself in you, of course it's bloody! + damage += parent.w_class * impact_pain_mult + owner.add_mood_event("embedded", /datum/mood_event/embedded) + + SEND_SIGNAL(parent, COMSIG_ITEM_EMBEDDED, victim, target_limb) + on_successful_embed(victim, target_limb) + + if (damage <= 0) + return TRUE + + var/armor = owner.run_armor_check(owner_limb.body_zone, MELEE, "Your armor has protected your [owner_limb.plaintext_zone].", + "Your armor has softened a hit to your [owner_limb.plaintext_zone].", parent.armour_penetration, + weak_against_armour = parent.weak_against_armour, + ) + + owner.apply_damage( + damage = (1 - pain_stam_pct) * damage, + damagetype = BRUTE, + def_zone = owner_limb.body_zone, + blocked = armor, + wound_bonus = parent.wound_bonus, + bare_wound_bonus = parent.bare_wound_bonus, + sharpness = parent.get_sharpness(), + attacking_item = parent, + ) + + owner.apply_damage( + damage = pain_stam_pct * damage, + damagetype = STAMINA, + ) + return TRUE + +/// Proc which is called upon successfully embedding into someone/something, for children to override +/datum/embedding/proc/on_successful_embed(mob/living/carbon/victim, obj/item/bodypart/target_limb) + return + +/// Registers signals that our owner should have +/// Handles jostling, tweezing embedded items out and grenade chain reactions +/datum/embedding/proc/set_owner(mob/living/carbon/victim, obj/item/bodypart/target_limb) + owner = victim + owner_limb = target_limb + RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(owner_moved)) + RegisterSignal(owner, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby)) + RegisterSignal(owner, COMSIG_ATOM_EX_ACT, PROC_REF(on_ex_act)) + RegisterSignal(owner_limb, COMSIG_BODYPART_REMOVED, PROC_REF(on_removed)) + +/// Avoid calling this directly as this doesn't move the object from its owner's contents +/// Returns TRUE if the item got deleted due to DROPDEL flag +/datum/embedding/proc/stop_embedding() + if (owner_limb) + UnregisterSignal(owner_limb, COMSIG_BODYPART_REMOVED) + owner_limb._unembed_object(parent) + if (owner) + UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_ATOM_ATTACKBY, COMSIG_ATOM_EX_ACT)) + if (!owner.has_embedded_objects()) + owner.clear_alert(ALERT_EMBEDDED_OBJECT) + owner.clear_mood_event("embedded") + UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_MAGIC_RECALL)) + SEND_SIGNAL(parent, COMSIG_ITEM_UNEMBEDDED, owner, owner_limb) + owner = null + owner_limb = null + if((parent.item_flags & DROPDEL) && !QDELETED(parent)) + qdel(parent) + return TRUE + return FALSE + +/datum/embedding/proc/on_qdel(atom/movable/source) + SIGNAL_HANDLER + if (owner_limb) + weapon_disappeared() + qdel(src) + +/// Move self to owner's turf when our limb gets removed +/datum/embedding/proc/on_removed(datum/source, mob/living/carbon/old_owner) + SIGNAL_HANDLER + stop_embedding() + parent.forceMove(old_owner.drop_location()) + +/// Someone attempted to pull us out! Either the owner by inspecting themselves, or someone else by examining the owner and clicking the link. +/datum/embedding/proc/rip_out(mob/living/jack_the_ripper) + if (!jack_the_ripper.CanReach(owner)) + return + + if (!jack_the_ripper.can_perform_action(owner, FORBID_TELEKINESIS_REACH | NEED_HANDS | ALLOW_RESTING)) + return + + var/time_taken = rip_time * parent.w_class + var/damage_mult = 1 + if (jack_the_ripper != owner) + time_taken *= RIPPING_OUT_HELP_TIME_MULTIPLIER + damage_mult *= RIPPING_OUT_HELP_DAMAGE_MULTIPLIER + owner.visible_message(span_warning("[jack_the_ripper] attempts to remove [parent] from [owner]'s [owner_limb.plaintext_zone]!"), + span_userdanger("[jack_the_ripper] attempt to remove [parent] from your [owner_limb.plaintext_zone]!"), ignored_mobs = jack_the_ripper) + to_chat(jack_the_ripper, span_notice("You attempt to remove [parent] from [owner]'s [owner_limb.plaintext_zone]...")) + else + owner.visible_message(span_warning("[owner] attempts to remove [parent] from [owner.p_their()] [owner_limb.plaintext_zone]."), + span_notice("You attempt to remove [parent] from your [owner_limb.plaintext_zone]...")) + + if (!do_after(jack_the_ripper, time_taken, owner, extra_checks = CALLBACK(src, PROC_REF(still_in)))) + return + + if (parent.loc != owner || !(parent in owner_limb?.embedded_objects)) + return + + if (jack_the_ripper == owner) + owner.visible_message(span_notice("[owner] successfully rips [parent] [is_harmless() ? "off" : "out"] of [owner.p_their()] [owner_limb.plaintext_zone]!"), + span_notice("You successfully remove [parent] from your [owner_limb.plaintext_zone].")) + else + owner.visible_message(span_notice("[jack_the_ripper] successfully rips [parent] [is_harmless() ? "off" : "out"] of [owner]'s [owner_limb.plaintext_zone]!"), + span_userdanger("[jack_the_ripper] removes [parent] from your [owner_limb.plaintext_zone]!"), ignored_mobs = jack_the_ripper) + to_chat(jack_the_ripper, span_notice("You successfully remove [parent] from [owner]'s [owner_limb.plaintext_zone].")) + + if (!is_harmless()) + damaging_removal_effect(damage_mult) + remove_embedding(jack_the_ripper) + +/// Handles damage effects upon forceful removal +/datum/embedding/proc/damaging_removal_effect(ouchies_multiplier) + var/damage = parent.w_class * remove_pain_mult * ouchies_multiplier + owner.apply_damage( + damage = (1 - pain_stam_pct) * damage, + damagetype = BRUTE, + def_zone = owner_limb, + wound_bonus = max(0, parent.wound_bonus), // It hurts to rip it out, get surgery you dingus. unlike the others, this CAN wound + increase slash bloodflow + sharpness = parent.get_sharpness() || SHARP_EDGED, // always sharp, even if the object isn't + attacking_item = parent, + ) + + owner.apply_damage( + damage = pain_stam_pct * damage, + damagetype = STAMINA, + ) + + owner.emote("scream") + +/// The proper proc to call when you want to remove something. If a mob is passed, the item will be put in its hands - otherwise its just dumped onto the ground +/datum/embedding/proc/remove_embedding(mob/living/to_hands) + var/mob/living/carbon/stored_owner = owner + if (stop_embedding()) // Dropdel? + return + parent.forceMove(stored_owner.drop_location()) + if (!isnull(to_hands)) + to_hands.put_in_hands(parent) + +/// When owner moves around, attempt to jostle the item +/datum/embedding/proc/owner_moved(mob/living/carbon/source, atom/old_loc, dir, forced, list/old_locs) + SIGNAL_HANDLER + + var/chance = jostle_chance + if(!forced && (owner.move_intent == MOVE_INTENT_WALK || owner.body_position == LYING_DOWN) && !CHECK_MOVE_LOOP_FLAGS(source, MOVEMENT_LOOP_OUTSIDE_CONTROL)) + chance *= 0.5 + + if(is_harmless() || !prob(chance)) + return + + var/damage = parent.w_class * jostle_pain_mult + owner.apply_damage( + damage = (1 - pain_stam_pct) * damage, + damagetype = BRUTE, + def_zone = owner_limb, + wound_bonus = CANT_WOUND, + sharpness = parent.get_sharpness(), + attacking_item = parent, + ) + + owner.apply_damage( + damage = pain_stam_pct * damage, + damagetype = STAMINA, + ) + + to_chat(owner, span_userdanger("[parent] embedded in your [owner_limb.plaintext_zone] jostles and stings!")) + jostle_effects() + +/// Effects which should occur when the owner moves, sometimes +/datum/embedding/proc/jostle_effects() + return + +/// When someone attempts to pluck us with tweezers or wirecutters +/datum/embedding/proc/on_attackby(mob/living/carbon/victim, obj/item/tool, mob/user) + SIGNAL_HANDLER + + if (user.zone_selected != owner_limb.body_zone || (tool.tool_behaviour != TOOL_HEMOSTAT && tool.tool_behaviour != TOOL_WIRECUTTER)) + return + + if (parent != owner_limb.embedded_objects[1]) // Don't pluck everything at the same time + return + + // Ensure that we can actually + if (!owner.try_inject(user, owner_limb.body_zone, INJECT_CHECK_IGNORE_SPECIES | INJECT_TRY_SHOW_ERROR_MESSAGE)) + return COMPONENT_NO_AFTERATTACK + + INVOKE_ASYNC(src, PROC_REF(try_pluck), tool, user) + return COMPONENT_NO_AFTERATTACK + +/datum/embedding/process(seconds_per_tick) + if (!owner || !owner_limb || owner_limb.owner != owner) + stack_trace("Attempted to process embedding on [parent] ([parent.type]) without an owner, owner_limb or owner-less limb!") + parent.forceMove(get_turf(parent)) + return + + if (owner.stat == DEAD) + return + + var/fall_chance_current = SPT_PROB_RATE(fall_chance / 100, seconds_per_tick) * 100 + if(owner.body_position == LYING_DOWN) + fall_chance_current *= 0.2 + + if(prob(fall_chance_current)) + fall_out() + return + + var/damage = parent.w_class * pain_mult + var/pain_chance_current = SPT_PROB_RATE(pain_chance / 100, seconds_per_tick) * 100 + if(pain_stam_pct && HAS_TRAIT_FROM(owner, TRAIT_INCAPACITATED, STAMINA)) //if it's a less-lethal embed, give them a break if they're already stamcritted + pain_chance_current *= 0.2 + damage *= 0.5 + else if(owner.body_position == LYING_DOWN) + pain_chance_current *= 0.2 + + if (is_harmless() || !prob(pain_chance_current)) + return + + owner.apply_damage( + damage = (1 - pain_stam_pct) * damage, + damagetype = BRUTE, + def_zone = owner_limb, + wound_bonus = CANT_WOUND, + sharpness = parent.get_sharpness(), + attacking_item = parent, + ) + + owner.apply_damage( + damage = pain_stam_pct * damage, + damagetype = STAMINA, + ) + + to_chat(owner, span_userdanger("[parent] embedded in your [owner_limb.plaintext_zone] hurts!")) + +/// Attempt to pluck out the embedded item using tweezers of some kind +/datum/embedding/proc/try_pluck(obj/item/tool, mob/user) + var/pluck_time = rip_time * (parent.w_class * 0.3) * tool.toolspeed + var/self_pluck = (user == owner) + var/safe_pluck = tool.tool_behaviour != TOOL_HEMOSTAT + // Don't harm ourselves if we're just stuck + if (is_harmless()) + safe_pluck = TRUE + if (self_pluck) + pluck_time *= 1.5 + // Wirecutters are harder to use for this + if (safe_pluck) + pluck_time *= 1.5 + + if (self_pluck) + owner.visible_message(span_danger("[owner] begins plucking [parent] from [owner.p_their()] [owner_limb.plaintext_zone] with [tool]..."), + span_notice("You start plucking [parent] from your [owner_limb.plaintext_zone] with [tool]..."), visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE) + else + user.visible_message(span_danger("[user] begins plucking [parent] from [owner]'s [owner_limb.plaintext_zone] with [tool]..."), + span_notice("You start plucking [parent] from [owner]'s [owner_limb.plaintext_zone] with [tool]..."), ignored_mobs = owner) + to_chat(owner, span_userdanger("[user] begins plucking [parent] from your [owner_limb.plaintext_zone] with [tool]... ")) + + if (!do_after(user, pluck_time, owner, extra_checks = CALLBACK(src, PROC_REF(still_in)))) + if (self_pluck) + to_chat(user, span_danger("You fail to pluck [parent] from your [owner_limb.plaintext_zone].")) + else + to_chat(user, span_danger("You fail to pluck [parent] from [owner]'s [owner_limb.plaintext_zone].")) + to_chat(owner, span_danger("[user] fails to pluck [parent] from your [owner_limb.plaintext_zone].")) + return + + if (self_pluck) + to_chat(span_notice("You pluck [parent] from your [owner_limb.plaintext_zone][safe_pluck ? "." : span_danger(", but it hurts like hell")]")) + + if(!safe_pluck) + damaging_removal_effect(min(self_pluck ? 1 : RIPPING_OUT_HELP_DAMAGE_MULTIPLIER, 0.4 * tool.w_class)) + + remove_embedding(user) + +/// Called when then item randomly falls out of a carbon. This handles the damage and descriptors, then calls remove_embedding() +/datum/embedding/proc/fall_out() + if(is_harmless()) + owner.visible_message(span_danger("[parent] falls off of [owner.name]'s [owner_limb.plaintext_zone]!"), + span_userdanger("[parent] falls off of your [owner_limb.plaintext_zone]!")) + remove_embedding() + return + + var/damage = parent.w_class * remove_pain_mult + owner.apply_damage( + damage = (1 - pain_stam_pct) * damage, + damagetype = BRUTE, + def_zone = owner_limb, + wound_bonus = CANT_WOUND, + sharpness = parent.get_sharpness(), + attacking_item = parent, + ) + + owner.apply_damage( + damage = pain_stam_pct * damage, + damagetype = STAMINA, + ) + + owner.visible_message(span_danger("[parent] falls out of [owner.name]'s [owner_limb.plaintext_zone]!"), + span_userdanger("[parent] falls out of your [owner_limb.plaintext_zone]!")) + remove_embedding() + +/// Whenever the parent item is forcefully moved by some weird means +/datum/embedding/proc/weapon_disappeared(atom/old_loc, dir, forced) + SIGNAL_HANDLER + // If something moved it to their limb, its not really *disappearing*, is it? + if (owner && parent.loc != owner_limb) + to_chat(owner, span_userdanger("[parent] that was embedded in your [owner_limb.plaintext_zone] disappears!")) + stop_embedding() + +/// So the sticky grenades chain-detonate, because mobs are very careful with which of their contents they blow up +/datum/embedding/proc/on_ex_act(atom/source, severity) + SIGNAL_HANDLER + // In the process of owner's ex_act + if (QDELETED(parent)) + return + switch(severity) + if(EXPLODE_DEVASTATE) + SSexplosions.high_mov_atom += parent + if(EXPLODE_HEAVY) + SSexplosions.med_mov_atom += parent + if(EXPLODE_LIGHT) + SSexplosions.low_mov_atom += parent + +/// Called when an object is ripped out of someone's body by magic or other abnormal means +/datum/embedding/proc/magic_pull(obj/item/weapon, mob/living/caster) + SIGNAL_HANDLER + + if(is_harmless()) + owner.visible_message(span_danger("[parent] vanishes from [owner]'s [owner_limb.plaintext_zone]!"), span_userdanger("[parent] vanishes from [owner_limb.plaintext_zone]!")) + return + + var/damage = parent.w_class * remove_pain_mult + + owner.apply_damage( + damage = (1 - pain_stam_pct) * damage * 1.5, + damagetype = BRUTE, + def_zone = owner_limb, + wound_bonus = max(0, parent.wound_bonus), // Performs exit wounds and flings the user to the caster if nearby + sharpness = parent.get_sharpness() || SHARP_EDGED, + attacking_item = parent, + ) + + owner.apply_damage( + damage = pain_stam_pct * damage, + damagetype = STAMINA, + ) + + owner.cause_wound_of_type_and_severity(WOUND_PIERCE, owner_limb, WOUND_SEVERITY_MODERATE) + playsound(owner, 'sound/effects/wounds/blood2.ogg', 50, TRUE) + + var/dist = get_dist(caster, owner) //Check if the caster is close enough to yank them in + if(dist >= 7) + owner.visible_message(span_danger("[parent] is violently torn from [owner]'s [owner_limb.plaintext_zone]!"), span_userdanger("[parent] is violently torn from your [owner_limb.plaintext_zone]!")) + return + + owner.throw_at(caster, get_dist(owner, caster) - 1, 1, caster) + owner.Paralyze(1 SECONDS) + owner.visible_message(span_alert("[owner] is sent flying towards [caster] as the [parent] tears out of them!"), span_alert("You are launched at [caster] as the [parent] tears from your body and towards their hand!")) + +/datum/embedding/proc/still_in() + if (parent.loc != owner) + return FALSE + if (!(parent in owner_limb?.embedded_objects)) + return FALSE + if (owner_limb?.owner != owner) + return FALSE + return TRUE + +#undef RIPPING_OUT_HELP_TIME_MULTIPLIER +#undef RIPPING_OUT_HELP_DAMAGE_MULTIPLIER diff --git a/code/datums/mutations/tongue_spike.dm b/code/datums/mutations/tongue_spike.dm index 663dcd2541a..828fd32bbe9 100644 --- a/code/datums/mutations/tongue_spike.dm +++ b/code/datums/mutations/tongue_spike.dm @@ -49,7 +49,7 @@ force = 2 throwforce = 25 throw_speed = 4 - embed_type = /datum/embed_data/tongue_spike + embed_type = /datum/embedding/tongue_spike w_class = WEIGHT_CLASS_SMALL sharpness = SHARP_POINTY custom_materials = list(/datum/material/biomass = SMALL_MATERIAL_AMOUNT * 5) @@ -58,33 +58,34 @@ /// if we missed our target var/missed = TRUE -/datum/embed_data/tongue_spike +/obj/item/hardened_spike/Initialize(mapload, mob/living/carbon/source) + . = ..() + src.fired_by_ref = WEAKREF(source) + addtimer(CALLBACK(src, PROC_REF(check_morph)), 5 SECONDS) + +/obj/item/hardened_spike/proc/check_morph() + // Failed to embed, morph back + if (!embed_data?.owner) + morph_back() + +/obj/item/hardened_spike/proc/morph_back() + visible_message(span_warning("[src] cracks and twists, changing shape!")) + for(var/obj/tongue as anything in contents) + tongue.forceMove(get_turf(src)) + qdel(src) + +/datum/embedding/tongue_spike impact_pain_mult = 0 pain_mult = 15 embed_chance = 100 fall_chance = 0 ignore_throwspeed_threshold = TRUE -/obj/item/hardened_spike/Initialize(mapload, mob/living/carbon/source) +/datum/embedding/tongue_spike/stop_embedding() . = ..() - src.fired_by_ref = WEAKREF(source) - addtimer(CALLBACK(src, PROC_REF(check_embedded)), 5 SECONDS) - -/obj/item/hardened_spike/proc/check_embedded() - if(missed) - unembedded() - -/obj/item/hardened_spike/embedded(atom/target) - . = ..() - if(isbodypart(target)) - missed = FALSE - -/obj/item/hardened_spike/unembedded() - visible_message(span_warning("[src] cracks and twists, changing shape!")) - for(var/obj/tongue as anything in contents) - tongue.forceMove(get_turf(src)) - - qdel(src) + var/obj/item/hardened_spike/tongue_spike = parent + if (!QDELETED(tongue_spike)) // This can cause a qdel loop + tongue_spike.morph_back() /datum/mutation/human/tongue_spike/chem name = "Chem Spike" @@ -112,39 +113,35 @@ desc = "Hardened biomass, shaped into... something." icon_state = "tonguespikechem" throwforce = 2 - embed_type = /datum/embed_data/tongue_spike/chem - /// Whether the tongue's already embedded in a target once before - var/embedded_once_alread = FALSE + embed_type = /datum/embedding/tongue_spike/chem -/datum/embed_data/tongue_spike/chem +/datum/embedding/tongue_spike/chem pain_mult = 0 pain_chance = 0 -/obj/item/hardened_spike/chem/embedded(mob/living/carbon/human/embedded_mob) - . = ..() - if(embedded_once_alread) - return - embedded_once_alread = TRUE - - var/mob/living/carbon/fired_by = fired_by_ref?.resolve() - if(!fired_by) +/datum/embedding/tongue_spike/chem/on_successful_embed(mob/living/carbon/victim, obj/item/bodypart/target_limb) + var/obj/item/hardened_spike/chem/tongue_spike = parent + var/mob/living/carbon/fired_by = tongue_spike.fired_by_ref?.resolve() + if(!istype(fired_by)) return - var/datum/action/send_chems/chem_action = new(src) - chem_action.transferred_ref = WEAKREF(embedded_mob) + var/datum/action/send_chems/chem_action = new(tongue_spike) + chem_action.transferred_ref = WEAKREF(victim) chem_action.Grant(fired_by) to_chat(fired_by, span_notice("Link established! Use the \"Transfer Chemicals\" ability \ to send your chemicals to the linked target!")) -/obj/item/hardened_spike/chem/unembedded() - var/mob/living/carbon/fired_by = fired_by_ref?.resolve() - if(fired_by) - to_chat(fired_by, span_warning("Link lost!")) - var/datum/action/send_chems/chem_action = locate() in fired_by.actions - QDEL_NULL(chem_action) +/datum/embedding/tongue_spike/chem/stop_embedding() + . = ..() + var/obj/item/hardened_spike/chem/tongue_spike = parent + var/mob/living/carbon/fired_by = tongue_spike.fired_by_ref?.resolve() + if(!istype(fired_by)) + return - return ..() + to_chat(fired_by, span_warning("Link lost!")) + var/datum/action/send_chems/chem_action = locate() in fired_by.actions + qdel(chem_action) /datum/action/send_chems name = "Transfer Chemicals" @@ -177,9 +174,11 @@ transferer.reagents.trans_to(transferred, transferer.reagents.total_volume, transferred_by = transferer) var/obj/item/hardened_spike/chem/chem_spike = target - var/obj/item/bodypart/spike_location = chem_spike.check_embedded() - //this is where it would deal damage, if it transfers chems it removes itself so no damage - chem_spike.forceMove(get_turf(spike_location)) - chem_spike.visible_message(span_notice("[chem_spike] falls out of [spike_location]!")) + // This is where it would deal damage, if it transfers chems it removes itself so no damage + var/mob/living/carbon/spike_owner = chem_spike.get_embed()?.owner + // Message first because it'll shift back into a tongue right after moving + if (istype(spike_owner)) + spike_owner.visible_message(span_notice("[chem_spike] falls out of [spike_owner]!")) + chem_spike.forceMove(get_turf(chem_spike)) return TRUE diff --git a/code/game/objects/effects/posters/poster.dm b/code/game/objects/effects/posters/poster.dm index 135887aafc8..ca4242daa76 100644 --- a/code/game/objects/effects/posters/poster.dm +++ b/code/game/objects/effects/posters/poster.dm @@ -195,17 +195,16 @@ return FALSE return TRUE +// HO-HO-HOHOHO HU HU-HU HU-HU /obj/structure/sign/poster/proc/spring_trap(mob/user) var/obj/item/shard/payload = trap?.resolve() if (!payload) return to_chat(user, span_warning("There's something sharp behind this! What the hell?")) - if(!can_embed_trap(user) || !payload.tryEmbed(user.get_active_hand(), forced = TRUE)) + if(!can_embed_trap(user) || !payload.force_embed(user, user.get_active_hand())) visible_message(span_notice("A [payload.name] falls from behind the poster.") ) payload.forceMove(user.drop_location()) - else - SEND_SIGNAL(src, COMSIG_POSTER_TRAP_SUCCEED, user) /obj/structure/sign/poster/proc/can_embed_trap(mob/living/carbon/human/user) if (!istype(user) || HAS_TRAIT(user, TRAIT_PIERCEIMMUNE)) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index d88993ae959..10a7b831bc7 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -176,7 +176,7 @@ /// Does it embed and if yes, what kind of embed var/embed_type /// Stores embedding data - var/datum/embed_data/embed_data + VAR_PROTECTED/datum/embedding/embed_data ///for flags such as [GLASSESCOVERSEYES] var/flags_cover = 0 @@ -277,8 +277,6 @@ add_weapon_description() SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NEW_ITEM, src) - if(get_embed()) - AddElement(/datum/element/embed) setup_reskinning() @@ -855,15 +853,18 @@ /obj/item/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) . = ..() + if(!isliving(hit_atom)) //Living mobs handle hit sounds differently. if(throw_drop_sound) playsound(src, throw_drop_sound, YEET_SOUND_VOLUME, ignore_walls = FALSE, vary = sound_vary) return playsound(src, drop_sound, YEET_SOUND_VOLUME, ignore_walls = FALSE, vary = sound_vary) return - var/volume = get_volume_by_throwforce_and_or_w_class() + if(.) //it's been caught. return + + var/volume = get_volume_by_throwforce_and_or_w_class() if (throwforce > 0 || HAS_TRAIT(src, TRAIT_CUSTOM_TAP_SOUND)) if (mob_throw_hit_sound) playsound(hit_atom, mob_throw_hit_sound, volume, TRUE, -1) @@ -1308,15 +1309,6 @@ dropped(M, FALSE) return ..() -/obj/item/proc/embedded(atom/embedded_target, obj/item/bodypart/part) - SHOULD_CALL_PARENT(TRUE) - SEND_SIGNAL(src, COMSIG_ITEM_EMBEDDED, embedded_target, part) - -/obj/item/proc/unembedded() - if(item_flags & DROPDEL && !QDELETED(src)) - qdel(src) - return TRUE - /obj/item/proc/canStrip(mob/stripper, mob/owner) SHOULD_BE_PURE(TRUE) return !HAS_TRAIT(src, TRAIT_NODROP) && !(item_flags & ABSTRACT) @@ -1324,20 +1316,6 @@ /obj/item/proc/doStrip(mob/stripper, mob/owner) return owner.dropItemToGround(src) -///Does the current embedding var meet the criteria for being harmless? Namely, does it have a pain multiplier and jostle pain mult of 0? If so, return true. -/obj/item/proc/is_embed_harmless() - if (!get_embed()) - return FALSE - - return !isnull(embed_data.pain_mult) && !isnull(embed_data.jostle_pain_mult) && embed_data.pain_mult == 0 && embed_data.jostle_pain_mult == 0 - -///In case we want to do something special (like self delete) upon failing to embed in something. -/obj/item/proc/failedEmbed() - SHOULD_CALL_PARENT(TRUE) - SEND_SIGNAL(src, COMSIG_ITEM_FAILED_EMBED) - if(item_flags & DROPDEL && !QDELETED(src)) - qdel(src) - ///Called by the carbon throw_item() proc. Returns null if the item negates the throw, or a reference to the thing to suffer the throw else. /obj/item/proc/on_thrown(mob/living/carbon/user, atom/target) if((item_flags & ABSTRACT) || HAS_TRAIT(src, TRAIT_NODROP)) @@ -1348,34 +1326,6 @@ return return src -/** - * tryEmbed() is for when you want to try embedding something without dealing with the damage + hit messages of calling hitby() on the item while targeting the target. - * - * Really, this is used mostly with projectiles with shrapnel payloads, from [/datum/element/embed/proc/checkEmbedProjectile], and called on said shrapnel. Mostly acts as an intermediate between different embed elements. - * - * Returns TRUE if it embedded successfully, nothing otherwise - * - * Arguments: - * * target- Either a body part or a carbon. What are we hitting? - * * forced- Do we want this to go through 100%? - */ -/obj/item/proc/tryEmbed(atom/target, forced=FALSE) - if(!isbodypart(target) && !iscarbon(target)) - return NONE - - if(!forced && !get_embed()) - return NONE - - if(SEND_SIGNAL(src, COMSIG_EMBED_TRY_FORCE, target = target, forced = forced)) - return COMPONENT_EMBED_SUCCESS - - failedEmbed() - -///For when you want to disable an item's embedding capabilities (like transforming weapons and such), this proc will detach any active embed elements from it. -/obj/item/proc/disableEmbedding() - SEND_SIGNAL(src, COMSIG_ITEM_DISABLE_EMBED) - return - /// How many different types of mats will be counted in a bite? #define MAX_MATS_PER_BITE 2 @@ -1404,15 +1354,16 @@ victim.apply_damage(max(15, force), BRUTE, BODY_ZONE_HEAD, wound_bonus = 10, sharpness = TRUE) victim.losebreath += 2 - if(tryEmbed(victim.get_bodypart(BODY_ZONE_CHEST), forced = TRUE)) //and if it embeds successfully in their chest, cause a lot of pain + if(force_embed(victim, BODY_ZONE_CHEST)) //and if it embeds successfully in their chest, cause a lot of pain victim.apply_damage(max(25, force*1.5), BRUTE, BODY_ZONE_CHEST, wound_bonus = 7, sharpness = TRUE) victim.losebreath += 6 discover_after = FALSE if(QDELETED(src)) // in case trying to embed it caused its deletion (say, if it's DROPDEL) return source_item?.reagents?.add_reagent(/datum/reagent/blood, 2) + return discover_after - else if(custom_materials?.len) //if we've got materials, let's see what's in it + if(custom_materials?.len) //if we've got materials, let's see what's in it // How many mats have we found? You can only be affected by two material datums by default var/found_mats = 0 // How much of each material is in it? Used to determine if the glass should break @@ -1445,25 +1396,25 @@ victim.adjust_disgust(33) victim.visible_message(span_warning("[victim] looks like [victim.p_theyve()] just bitten into something hard."), \ span_warning("Eugh! Did I just bite into something?")) + return discover_after - else if(w_class == WEIGHT_CLASS_TINY) //small items like soap or toys that don't have mat datums - // victim's chest (for cavity implanting the item) - var/obj/item/bodypart/chest/victim_cavity = victim.get_bodypart(BODY_ZONE_CHEST) - if(victim_cavity.cavity_item) - victim.vomit(vomit_flags = (MOB_VOMIT_MESSAGE | MOB_VOMIT_HARM), lost_nutrition = 5, distance = 0) - forceMove(drop_location()) - to_chat(victim, span_warning("You vomit up a [name]! [source_item? "Was that in \the [source_item]?" : ""]")) - else - victim.transferItemToLoc(src, victim, TRUE) - victim.losebreath += 2 - victim_cavity.cavity_item = src - to_chat(victim, span_warning("You swallow hard. [source_item? "Something small was in \the [source_item]..." : ""]")) - discover_after = FALSE - - else + if(w_class > WEIGHT_CLASS_TINY) //small items like soap or toys that don't have mat datums to_chat(victim, span_warning("[source_item? "Something strange was in the \the [source_item]..." : "I just bit something strange..."] ")) + return discover_after - return discover_after + // victim's chest (for cavity implanting the item) + var/obj/item/bodypart/chest/victim_cavity = victim.get_bodypart(BODY_ZONE_CHEST) + if(victim_cavity.cavity_item) + victim.vomit(vomit_flags = (MOB_VOMIT_MESSAGE | MOB_VOMIT_HARM), lost_nutrition = 5, distance = 0) + forceMove(drop_location()) + to_chat(victim, span_warning("You vomit up a [name]! [source_item? "Was that in \the [source_item]?" : ""]")) + return FALSE + + victim.transferItemToLoc(src, victim, TRUE) + victim.losebreath += 2 + victim_cavity.cavity_item = src + to_chat(victim, span_warning("You swallow hard. [source_item? "Something small was in \the [source_item]..." : ""]")) + return FALSE #undef MAX_MATS_PER_BITE @@ -1925,21 +1876,6 @@ return TRUE return FALSE -/// Fetches embedding data -/obj/item/proc/get_embed() - RETURN_TYPE(/datum/embed_data) - return embed_type ? (embed_data ||= get_embed_by_type(embed_type)) : embed_data - -/obj/item/proc/set_embed(datum/embed_data/embed) - if(embed_data == embed) - return - if(isnull(get_embed())) // Add embed on objects that did not have it added - AddElement(/datum/element/embed) - if(!GLOB.embed_by_type[embed_data?.type]) - qdel(embed_data) - embed_data = ispath(embed) ? get_embed_by_type(embed) : embed - SEND_SIGNAL(src, COMSIG_ITEM_EMBEDDING_UPDATE) - /obj/item/apply_main_material_effects(datum/material/main_material, amount, multipier) . = ..() if(material_flags & MATERIAL_GREYSCALE) @@ -2048,3 +1984,40 @@ BARE WOUND: [bare_wound_bonus] "} + +/// Fetches, or lazyloads, our embedding datum +/obj/item/proc/get_embed() + RETURN_TYPE(/datum/embedding) + // Something may call this during qdeleting, which would cause a harddel + if (QDELETED(src)) + return null + if (embed_data) + return embed_data + if (embed_type) + embed_data = new embed_type(src) + return embed_data + +/// Sets our embedding datum to a different one. Can also take types +/obj/item/proc/set_embed(datum/embedding/new_embed) + if (new_embed == embed_data) + return + + // Needs to be QDELETED as embed data uses this to clean itself up from its parent (us) + if (!QDELETED(embed_data)) + qdel(embed_data) + + if (ispath(new_embed)) + new_embed = new new_embed(src) + + embed_data = new_embed + SEND_SIGNAL(src, COMSIG_ITEM_EMBEDDING_UPDATE) + +/// Embed ourselves into an object if we possess embedding data +/obj/item/proc/force_embed(mob/living/carbon/victim, obj/item/bodypart/target_limb) + if (!istype(victim)) + return FALSE + + if (!istype(target_limb)) + target_limb = victim.get_bodypart(target_limb) || victim.bodyparts[1] + + return get_embed()?.embed_into(victim, target_limb) diff --git a/code/game/objects/items/grenades/plastic.dm b/code/game/objects/items/grenades/plastic.dm index c9090912cc7..f50bcf43511 100644 --- a/code/game/objects/items/grenades/plastic.dm +++ b/code/game/objects/items/grenades/plastic.dm @@ -143,8 +143,7 @@ var/obj/item/thrown_weapon = bomb_target thrown_weapon.throw_speed = max(1, (thrown_weapon.throw_speed - 3)) thrown_weapon.throw_range = max(1, (thrown_weapon.throw_range - 3)) - if(thrown_weapon.get_embed()) - thrown_weapon.set_embed(thrown_weapon.get_embed().generate_with_values(embed_chance = 0)) + thrown_weapon.get_embed()?.embed_chance = 0 else if(isliving(bomb_target)) plastic_overlay.layer = FLOAT_LAYER diff --git a/code/game/objects/items/knives.dm b/code/game/objects/items/knives.dm index fc7836bbc04..438a157e9a6 100644 --- a/code/game/objects/items/knives.dm +++ b/code/game/objects/items/knives.dm @@ -145,14 +145,14 @@ icon_state = "buckknife" worn_icon_state = "buckknife" icon_angle = -45 - embed_type = /datum/embed_data/combat_knife + embed_type = /datum/embedding/combat_knife force = 20 throwforce = 20 attack_verb_continuous = list("slashes", "stabs", "slices", "tears", "lacerates", "rips", "cuts") attack_verb_simple = list("slash", "stab", "slice", "tear", "lacerate", "rip", "cut") slot_flags = ITEM_SLOT_MASK -/datum/embed_data/combat_knife +/datum/embedding/combat_knife pain_mult = 4 embed_chance = 65 fall_chance = 10 @@ -185,7 +185,7 @@ desc = "A hunting grade survival knife." icon_state = "survivalknife" worn_icon_state = "survivalknife" - embed_type = /datum/embed_data/combat_knife/weak + embed_type = /datum/embedding/combat_knife/weak force = 15 throwforce = 15 @@ -197,7 +197,7 @@ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' inhand_icon_state = "rootshiv" - embed_type = /datum/embed_data/combat_knife/weak + embed_type = /datum/embedding/combat_knife/weak force = 15 throwforce = 15 @@ -209,13 +209,13 @@ worn_icon_state = "bone_dagger" lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - embed_type = /datum/embed_data/combat_knife/weak + embed_type = /datum/embedding/combat_knife/weak obj_flags = parent_type::obj_flags & ~CONDUCTS_ELECTRICITY force = 15 throwforce = 15 custom_materials = null -/datum/embed_data/combat_knife/weak +/datum/embedding/combat_knife/weak embed_chance = 35 /obj/item/knife/combat/cyborg diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm index a386375b827..361fc05ec64 100644 --- a/code/game/objects/items/melee/energy.dm +++ b/code/game/objects/items/melee/energy.dm @@ -170,7 +170,7 @@ return (BRUTELOSS|FIRELOSS) /// Energy swords. -/datum/embed_data/esword +/datum/embedding/esword embed_chance = 75 impact_pain_mult = 10 @@ -190,7 +190,7 @@ armour_penetration = 35 block_chance = 50 block_sound = 'sound/items/weapons/block_blade.ogg' - embed_type = /datum/embed_data/esword + embed_type = /datum/embedding/esword var/list/alt_continuous = list("stabs", "pierces", "impales") var/list/alt_simple = list("stab", "pierce", "impale") diff --git a/code/game/objects/items/robot/items/food.dm b/code/game/objects/items/robot/items/food.dm index 6eba8e8fa76..90caf967053 100644 --- a/code/game/objects/items/robot/items/food.dm +++ b/code/game/objects/items/robot/items/food.dm @@ -237,12 +237,12 @@ var/head_color /obj/projectile/bullet/lollipop/harmful - embed_type = /datum/embed_data/lollipop + embed_type = /datum/embedding/lollipop damage = 10 shrapnel_type = /obj/item/food/lollipop/cyborg embed_falloff_tile = 0 -/datum/embed_data/lollipop +/datum/embedding/lollipop embed_chance = 35 fall_chance = 2 jostle_chance = 0 diff --git a/code/game/objects/items/shrapnel.dm b/code/game/objects/items/shrapnel.dm index a4adc353db3..cb0cea91ed6 100644 --- a/code/game/objects/items/shrapnel.dm +++ b/code/game/objects/items/shrapnel.dm @@ -34,9 +34,9 @@ ignore_range_hit_prone_targets = TRUE sharpness = SHARP_EDGED wound_bonus = 30 - embed_type = /datum/embed_data/shrapnel + embed_type = /datum/embedding/shrapnel -/datum/embed_data/shrapnel +/datum/embedding/shrapnel embed_chance = 70 ignore_throwspeed_threshold = TRUE fall_chance = 1 @@ -75,9 +75,9 @@ ricochet_incidence_leeway = 0 embed_falloff_tile = -2 shrapnel_type = /obj/item/shrapnel/stingball - embed_type = /datum/embed_data/stingball + embed_type = /datum/embedding/stingball -/datum/embed_data/stingball +/datum/embedding/stingball embed_chance = 55 fall_chance = 2 jostle_chance = 7 @@ -107,11 +107,11 @@ ricochets_max = 2 ricochet_chance = 140 shrapnel_type = /obj/item/shrapnel/capmine - embed_type = /datum/embed_data/capmine + embed_type = /datum/embedding/capmine wound_falloff_tile = 0 embed_falloff_tile = 0 -/datum/embed_data/capmine +/datum/embedding/capmine embed_chance = 90 fall_chance = 3 jostle_chance = 7 diff --git a/code/game/objects/items/spear.dm b/code/game/objects/items/spear.dm index 2ac99231d73..5f995f9c847 100644 --- a/code/game/objects/items/spear.dm +++ b/code/game/objects/items/spear.dm @@ -13,7 +13,7 @@ throwforce = 20 throw_speed = 4 demolition_mod = 0.75 - embed_type = /datum/embed_data/spear + embed_type = /datum/embedding/spear armour_penetration = 10 custom_materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/glass= HALF_SHEET_MATERIAL_AMOUNT * 2) hitsound = 'sound/items/weapons/bladeslice.ogg' @@ -33,7 +33,7 @@ /// How much damage to do wielded var/force_wielded = 18 -/datum/embed_data/spear +/datum/embedding/spear impact_pain_mult = 2 remove_pain_mult = 4 jostle_chance = 2.5 diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm index 9e91ba2aaac..9ee9db57188 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -33,7 +33,7 @@ GLOBAL_LIST_INIT(rod_recipes, list ( \ attack_verb_continuous = list("hits", "bludgeons", "whacks") attack_verb_simple = list("hit", "bludgeon", "whack") hitsound = 'sound/items/weapons/gun/general/grenade_launch.ogg' - embed_type = /datum/embed_data/rods + embed_type = /datum/embedding/rods novariants = TRUE matter_amount = 2 cost = HALF_SHEET_MATERIAL_AMOUNT @@ -43,7 +43,7 @@ GLOBAL_LIST_INIT(rod_recipes, list ( \ drop_sound = 'sound/items/handling/materials/metal_drop.ogg' sound_vary = TRUE -/datum/embed_data/rods +/datum/embedding/rods embed_chance = 50 /obj/item/stack/rods/suicide_act(mob/living/carbon/user) diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm index a3340fbd43a..7cb720daee3 100644 --- a/code/game/objects/items/stacks/sheets/glass.dm +++ b/code/game/objects/items/stacks/sheets/glass.dm @@ -306,12 +306,12 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( var/shiv_type = /obj/item/knife/shiv var/craft_time = 3.5 SECONDS var/obj/item/stack/sheet/weld_material = /obj/item/stack/sheet/glass - embed_type = /datum/embed_data/shard + embed_type = /datum/embedding/shard -/datum/embed_data/shard +/datum/embedding/shard embed_chance = 65 -/datum/embed_data/glass_candy +/datum/embedding/glass_candy embed_chance = 100 ignore_throwspeed_threshold = TRUE impact_pain_mult = 1 diff --git a/code/game/objects/items/stacks/tape.dm b/code/game/objects/items/stacks/tape.dm index a1394bbad4f..24805aec645 100644 --- a/code/game/objects/items/stacks/tape.dm +++ b/code/game/objects/items/stacks/tape.dm @@ -14,16 +14,17 @@ grind_results = list(/datum/reagent/cellulose = 5) splint_factor = 0.65 merge_type = /obj/item/stack/sticky_tape - var/conferred_embed = /datum/embed_data/sticky_tape + var/conferred_embed = /datum/embedding/sticky_tape ///The tape type you get when ripping off a piece of tape. var/obj/tape_gag = /obj/item/clothing/mask/muzzle/tape greyscale_config = /datum/greyscale_config/tape greyscale_colors = "#B2B2B2#BD6A62" -/datum/embed_data/sticky_tape +/datum/embedding/sticky_tape pain_mult = 0 jostle_pain_mult = 0 ignore_throwspeed_threshold = 0 + immune_traits = null /obj/item/stack/sticky_tape/attack_hand(mob/user, list/modifiers) if(user.get_inactive_held_item() == src) @@ -55,27 +56,29 @@ user.visible_message(span_notice("[user] begins wrapping [target] with [src]."), span_notice("You begin wrapping [target] with [src].")) playsound(user, 'sound/items/duct_tape/duct_tape_rip.ogg', 50, TRUE) - if(do_after(user, 3 SECONDS, target=target)) - playsound(user, 'sound/items/duct_tape/duct_tape_snap.ogg', 50, TRUE) - use(1) - if(istype(target, /obj/item/clothing/gloves/fingerless)) - var/obj/item/clothing/gloves/tackler/offbrand/O = new /obj/item/clothing/gloves/tackler/offbrand - to_chat(user, span_notice("You turn [target] into [O] with [src].")) - QDEL_NULL(target) - user.put_in_hands(O) - return ITEM_INTERACT_SUCCESS + if(!do_after(user, 3 SECONDS, target=target)) + return ITEM_INTERACT_BLOCKING - if(target.get_embed() && target.get_embed().type == conferred_embed) - to_chat(user, span_warning("[target] is already coated in [src]!")) - return ITEM_INTERACT_BLOCKING + playsound(user, 'sound/items/duct_tape/duct_tape_snap.ogg', 50, TRUE) + use(1) + if(istype(target, /obj/item/clothing/gloves/fingerless)) + var/obj/item/clothing/gloves/tackler/offbrand/O = new /obj/item/clothing/gloves/tackler/offbrand + to_chat(user, span_notice("You turn [target] into [O] with [src].")) + QDEL_NULL(target) + user.put_in_hands(O) + return ITEM_INTERACT_SUCCESS - target.set_embed(conferred_embed) - to_chat(user, span_notice("You finish wrapping [target] with [src].")) - target.name = "[prefix] [target.name]" + if(target.get_embed()?.type == conferred_embed) + to_chat(user, span_warning("[target] is already coated in [src]!")) + return ITEM_INTERACT_BLOCKING - if(isgrenade(target)) - var/obj/item/grenade/sticky_bomb = target - sticky_bomb.sticky = TRUE + target.set_embed(conferred_embed) + to_chat(user, span_notice("You finish wrapping [target] with [src].")) + target.name = "[prefix] [target.name]" + + if(isgrenade(target)) + var/obj/item/grenade/sticky_bomb = target + sticky_bomb.sticky = TRUE return ITEM_INTERACT_SUCCESS @@ -84,13 +87,13 @@ singular_name = "super sticky tape" desc = "Quite possibly the most mischevious substance in the galaxy. Use with extreme lack of caution." prefix = "super sticky" - conferred_embed = /datum/embed_data/sticky_tape/super + conferred_embed = /datum/embedding/sticky_tape/super splint_factor = 0.4 merge_type = /obj/item/stack/sticky_tape/super greyscale_colors = "#4D4D4D#75433F" tape_gag = /obj/item/clothing/mask/muzzle/tape/super -/datum/embed_data/sticky_tape/super +/datum/embedding/sticky_tape/super embed_chance = 100 fall_chance = 0.1 @@ -100,13 +103,13 @@ desc = "Used for sticking to things for sticking said things inside people." icon_state = "tape_spikes" prefix = "pointy" - conferred_embed = /datum/embed_data/pointy_tape + conferred_embed = /datum/embedding/pointy_tape merge_type = /obj/item/stack/sticky_tape/pointy greyscale_config = /datum/greyscale_config/tape/spikes greyscale_colors = "#E64539#808080#AD2F45" tape_gag = /obj/item/clothing/mask/muzzle/tape/pointy -/datum/embed_data/pointy_tape +/datum/embedding/pointy_tape ignore_throwspeed_threshold = TRUE /obj/item/stack/sticky_tape/pointy/super @@ -114,12 +117,12 @@ singular_name = "super pointy tape" desc = "You didn't know tape could look so sinister. Welcome to Space Station 13." prefix = "super pointy" - conferred_embed = /datum/embed_data/pointy_tape/super + conferred_embed = /datum/embedding/pointy_tape/super merge_type = /obj/item/stack/sticky_tape/pointy/super greyscale_colors = "#8C0A00#4F4F4F#300008" tape_gag = /obj/item/clothing/mask/muzzle/tape/pointy/super -/datum/embed_data/pointy_tape/super +/datum/embedding/pointy_tape/super embed_chance = 100 /obj/item/stack/sticky_tape/surgical @@ -127,14 +130,14 @@ singular_name = "surgical tape" desc = "Made for patching broken bones back together alongside bone gel, not for playing pranks." prefix = "surgical" - conferred_embed = /datum/embed_data/sticky_tape/surgical + conferred_embed = /datum/embedding/sticky_tape/surgical splint_factor = 0.5 custom_price = PAYCHECK_CREW merge_type = /obj/item/stack/sticky_tape/surgical greyscale_colors = "#70BAE7#BD6A62" tape_gag = /obj/item/clothing/mask/muzzle/tape/surgical -/datum/embed_data/sticky_tape/surgical +/datum/embedding/sticky_tape/surgical embed_chance = 30 /obj/item/stack/sticky_tape/surgical/get_surgery_tool_overlay(tray_extended) diff --git a/code/game/objects/items/tail_pin.dm b/code/game/objects/items/tail_pin.dm index dc2ffaefea0..08e7d9c29ef 100644 --- a/code/game/objects/items/tail_pin.dm +++ b/code/game/objects/items/tail_pin.dm @@ -14,9 +14,9 @@ sharpness = SHARP_POINTY max_integrity = 200 layer = CORGI_ASS_PIN_LAYER - embed_type = /datum/embed_data/corgi_pin + embed_type = /datum/embedding/corgi_pin -/datum/embed_data/corgi_pin +/datum/embedding/corgi_pin pain_chance = 0 jostle_pain_mult = 0 ignore_throwspeed_threshold = TRUE diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index 3e03cd77097..0e85af8e532 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -417,7 +417,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 force = 2 throwforce = 10 //10 + 2 (WEIGHT_CLASS_SMALL) * 4 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = 18 damage on hit due to guaranteed embedding throw_speed = 4 - embed_type = /datum/embed_data/throwing_star + embed_type = /datum/embedding/throwing_star armour_penetration = 40 w_class = WEIGHT_CLASS_SMALL @@ -425,7 +425,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 custom_materials = list(/datum/material/iron= SMALL_MATERIAL_AMOUNT * 5, /datum/material/glass= SMALL_MATERIAL_AMOUNT * 5) resistance_flags = FIRE_PROOF -/datum/embed_data/throwing_star +/datum/embedding/throwing_star pain_mult = 4 embed_chance = 100 fall_chance = 0 @@ -434,9 +434,9 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 name = "shock throwing star" desc = "An aerodynamic disc designed to cause excruciating pain when stuck inside fleeing targets, hopefully without causing fatal harm." throwforce = 5 - embed_type = /datum/embed_data/throwing_star/stamina + embed_type = /datum/embedding/throwing_star/stamina -/datum/embed_data/throwing_star/stamina +/datum/embedding/throwing_star/stamina pain_mult = 5 jostle_chance = 10 pain_stam_pct = 0.8 @@ -448,9 +448,9 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 sharpness = NONE force = 0 throwforce = 0 - embed_type = /datum/embed_data/throwing_star/toy + embed_type = /datum/embedding/throwing_star/toy -/datum/embed_data/throwing_star/toy +/datum/embedding/throwing_star/toy pain_mult = 0 jostle_pain_mult = 0 @@ -1234,7 +1234,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 throwforce = 25 throw_speed = 4 attack_speed = CLICK_CD_HYPER_RAPID - embed_type = /datum/embed_data/hfr_blade + embed_type = /datum/embedding/hfr_blade block_chance = 25 block_sound = 'sound/items/weapons/parry.ogg' sharpness = SHARP_EDGED @@ -1249,7 +1249,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /// The previous target we attacked var/datum/weakref/previous_target -/datum/embed_data/hfr_blade +/datum/embedding/hfr_blade embed_chance = 100 /obj/item/highfrequencyblade/Initialize(mapload) diff --git a/code/modules/antagonists/heretic/structures/carving_knife.dm b/code/modules/antagonists/heretic/structures/carving_knife.dm index eb8a3a4769b..267937104f1 100644 --- a/code/modules/antagonists/heretic/structures/carving_knife.dm +++ b/code/modules/antagonists/heretic/structures/carving_knife.dm @@ -16,7 +16,7 @@ attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "rends") attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "rend") actions_types = list(/datum/action/item_action/rune_shatter) - embed_type = /datum/embed_data/rune_carver + embed_type = /datum/embedding/rune_carver /// Whether we're currently drawing a rune var/drawing = FALSE @@ -35,7 +35,7 @@ alt_simple = string_list(alt_simple) AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple) -/datum/embed_data/rune_carver +/datum/embedding/rune_carver ignore_throwspeed_threshold = TRUE embed_chance = 75 jostle_chance = 2 diff --git a/code/modules/events/wizard/embeddies.dm b/code/modules/events/wizard/embeddies.dm index 8b456894215..49f4fbc5afe 100644 --- a/code/modules/events/wizard/embeddies.dm +++ b/code/modules/events/wizard/embeddies.dm @@ -43,10 +43,10 @@ GLOBAL_DATUM(global_funny_embedding, /datum/global_funny_embedding) * Makes every item in the world embed when thrown, but also hooks into global signals for new items created to also bless them with embed-ability(??). */ /datum/global_funny_embedding - var/embed_type = /datum/embed_data/global_funny + var/embed_type = /datum/embedding/global_funny var/prefix = "error" -/datum/embed_data/global_funny +/datum/embedding/global_funny ignore_throwspeed_threshold = TRUE /datum/global_funny_embedding/New() @@ -91,9 +91,9 @@ GLOBAL_DATUM(global_funny_embedding, /datum/global_funny_embedding) ///everything will be... sticky? sure, why not /datum/global_funny_embedding/sticky - embed_type = /datum/embed_data/global_funny/sticky + embed_type = /datum/embedding/global_funny/sticky prefix = "sticky" -/datum/embed_data/global_funny/sticky +/datum/embedding/global_funny/sticky pain_mult = 0 jostle_pain_mult = 0 diff --git a/code/modules/fishing/fish/types/rift.dm b/code/modules/fishing/fish/types/rift.dm index ce7046c1f0c..c06de438b4f 100644 --- a/code/modules/fishing/fish/types/rift.dm +++ b/code/modules/fishing/fish/types/rift.dm @@ -14,7 +14,7 @@ throwforce = 11 throw_range = 8 throw_speed = 4 - embed_type = /datum/embed_data/chrystarfish + embed_type = /datum/embedding/chrystarfish attack_verb_continuous = list("stabs", "jabs") attack_verb_simple = list("stab", "jab") hitsound = SFX_SHATTER @@ -44,7 +44,7 @@ electrogenesis_power = 9 MEGA JOULES // Basically a ninja star that's highly likely to embed and teleports you around if you don't stop to remove it. However it doesn't deal that much damage! -/datum/embed_data/chrystarfish +/datum/embedding/chrystarfish pain_mult = 1 embed_chance = 85 fall_chance = 3 @@ -55,16 +55,10 @@ ignore_throwspeed_threshold = TRUE // basically shaped like a shuriken jostle_chance = 15 jostle_pain_mult = 1 - // about to be set! - jostle_callback = null -/datum/embed_data/chrystarfish/New() - ..() - jostle_callback = CALLBACK(src, PROC_REF(teleport)) - -/datum/embed_data/chrystarfish/proc/teleport(mob/victim, atom/embed_parent, datum/embed_data/real_data) - do_teleport(victim, get_turf(victim), 3, asoundin = 'sound/effects/phasein.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) - victim.visible_message(span_danger("[victim] teleports as [embed_parent] jostles inside [victim.p_them()]!")) +/datum/embedding/chrystarfish/jostle_effects() + do_teleport(owner, get_turf(owner), 3, asoundin = 'sound/effects/phasein.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) + owner.visible_message(span_danger("[owner] teleports as [parent] jostles inside of [owner.p_them()]!")) /obj/item/fish/starfish/chrystarfish/set_status(new_status, silent) . = ..() diff --git a/code/modules/hydroponics/hydroitemdefines.dm b/code/modules/hydroponics/hydroitemdefines.dm index 216c913d792..ca44cd83feb 100644 --- a/code/modules/hydroponics/hydroitemdefines.dm +++ b/code/modules/hydroponics/hydroitemdefines.dm @@ -492,14 +492,14 @@ throwforce = 15 throw_speed = 4 throw_range = 7 - embed_type = /datum/embed_data/hatchet + embed_type = /datum/embedding/hatchet custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*7.5) attack_verb_continuous = list("chops", "tears", "lacerates", "cuts") attack_verb_simple = list("chop", "tear", "lacerate", "cut") hitsound = 'sound/items/weapons/bladeslice.ogg' sharpness = SHARP_EDGED -/datum/embed_data/hatchet +/datum/embedding/hatchet pain_mult = 4 embed_chance = 35 fall_chance = 10 diff --git a/code/modules/hydroponics/plant_genes.dm b/code/modules/hydroponics/plant_genes.dm index 79dd725b6e3..85a560eb034 100644 --- a/code/modules/hydroponics/plant_genes.dm +++ b/code/modules/hydroponics/plant_genes.dm @@ -864,14 +864,28 @@ var/obj/item/seeds/our_seed = our_plant.get_plant_seed() our_plant.throwforce = (our_seed.potency/20) - if (!our_plant.get_embed()) + var/datum/embedding/plant_embed = our_plant.get_embed() + if (!plant_embed) + if(our_seed.get_gene(/datum/plant_gene/trait/stinging)) + our_plant.set_embed(/datum/embedding/spiky_plant) + else + our_plant.set_embed(/datum/embedding/sticky_plant) return + plant_embed.ignore_throwspeed_threshold = TRUE if(our_seed.get_gene(/datum/plant_gene/trait/stinging)) - our_plant.set_embed(our_plant.get_embed().generate_with_values(ignore_throwspeed_threshold = TRUE)) return - our_plant.set_embed(our_plant.get_embed().generate_with_values(ignore_throwspeed_threshold = TRUE, pain_mult = 0, jostle_pain_mult = 0)) + plant_embed.pain_mult = 0 + plant_embed.jostle_pain_mult = 0 + +/datum/embedding/sticky_plant + pain_mult = 0 + jostle_pain_mult = 0 + ignore_throwspeed_threshold = TRUE + +/datum/embedding/spiky_plant + ignore_throwspeed_threshold = TRUE /** * This trait automatically heats up the plant's chemical contents when harvested. diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index adcbc8495f5..1a1dd350772 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -228,13 +228,13 @@ /mob/living/carbon/Topic(href, href_list) ..() if(href_list["embedded_object"]) - var/obj/item/bodypart/L = locate(href_list["embedded_limb"]) in bodyparts - if(!L) + var/obj/item/bodypart/limb = locate(href_list["embedded_limb"]) in bodyparts + if(!limb) 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 + var/obj/item/weapon = locate(href_list["embedded_object"]) in limb.embedded_objects + if(!weapon || weapon.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) + weapon.get_embed().rip_out(usr) return if(href_list["show_paper_note"]) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 5113816166d..0be935c76f8 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -416,22 +416,22 @@ return var/embeds = FALSE - for(var/X in bodyparts) - var/obj/item/bodypart/LB = X - for(var/obj/item/I in LB.embedded_objects) + for(var/obj/item/bodypart/limb as anything in bodyparts) + for(var/obj/item/weapon as anything in limb.embedded_objects) if(!embeds) embeds = TRUE // this way, we only visibly try to examine ourselves if we have something embedded, otherwise we'll still hug ourselves :) visible_message(span_notice("[src] examines [p_them()]self."), \ - span_notice("You check yourself for shrapnel.")) - if(I.is_embed_harmless()) - to_chat(src, "\t There is \a [I] stuck to your [LB.name]!") + span_notice("You check yourself for shrapnel."), visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE) + var/harmless = weapon.get_embed().is_harmless() + var/stuck_wordage = harmless ? "stuck to" : "embedded in" + var/embed_text = "\t There is [icon2html(weapon, src)] \a [weapon] [stuck_wordage] your [limb.plaintext_zone]!" + if (harmless) + to_chat(src, span_italics(span_notice(embed_text))) else - to_chat(src, "\t There is \a [I] embedded in your [LB.name]!") - + to_chat(src, span_boldwarning(embed_text)) return embeds - /mob/living/carbon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash, length = 25) var/obj/item/organ/eyes/eyes = get_organ_slot(ORGAN_SLOT_EYES) if(!eyes) //can't flash what can't see! diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index 7918820ef60..d494a5a1fba 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -1,5 +1,6 @@ /// Adds a newline to the examine list if the above entry is not empty and it is not the first element in the list #define ADD_NEWLINE_IF_NECESSARY(list) if(length(list) > 0 && list[length(list)]) { list += "" } +#define CARBON_EXAMINE_EMBEDDING_MAX_DIST 4 /mob/living/carbon/human/get_examine_name(mob/user) if(!HAS_TRAIT(user, TRAIT_PROSOPAGNOSIA)) @@ -61,8 +62,16 @@ disabled += body_part missing -= body_part.body_zone for(var/obj/item/embedded as anything in body_part.embedded_objects) - var/stuck_wordage = embedded.is_embed_harmless() ? "stuck to" : "embedded in" - . += span_boldwarning("[t_He] [t_has] [icon2html(embedded, user)] \a [embedded] [stuck_wordage] [t_his] [body_part.plaintext_zone]!") + var/harmless = embedded.get_embed().is_harmless() + var/stuck_wordage = harmless ? "stuck to" : "embedded in" + var/embed_line = "\a [embedded]" + if (get_dist(src, user) <= CARBON_EXAMINE_EMBEDDING_MAX_DIST) + embed_line = "\a [embedded]" + var/embed_text = "[t_He] [t_has] [icon2html(embedded, user)] [embed_line] [stuck_wordage] [t_his] [body_part.plaintext_zone]!" + if (harmless) + . += span_italics(span_notice(embed_text)) + else + . += span_boldwarning(embed_text) for(var/datum/wound/iter_wound as anything in body_part.wounds) . += span_danger(iter_wound.get_examine_description(user)) @@ -583,4 +592,9 @@ if(undershirt.has_sensor == BROKEN_SENSORS) . += list(span_notice("The [undershirt]'s medical sensors are sparking.")) + for(var/datum/scar/iter_scar as anything in all_scars) + if(iter_scar.is_visible(user)) + . += iter_scar.get_examine_description(user) + #undef ADD_NEWLINE_IF_NECESSARY +#undef CARBON_EXAMINE_EMBEDDING_MAX_DIST diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index 693d00dc4cb..30ee759bf46 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -167,8 +167,7 @@ for(var/i in missing_bodyparts) var/datum/scar/scaries = new scars += "[scaries.format_amputated(i)]" - for(var/i in all_scars) - var/datum/scar/iter_scar = i + for(var/datum/scar/iter_scar as anything in all_scars) if(!iter_scar.fake) scars += "[iter_scar.format()];" return scars diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 6c6793ff999..ee9f95570bc 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -228,7 +228,7 @@ blocked = TRUE var/zone = get_random_valid_zone(BODY_ZONE_CHEST, 65)//Hits a random part of the body, geared towards the chest - var/nosell_hit = SEND_SIGNAL(thrown_item, COMSIG_MOVABLE_IMPACT_ZONE, src, zone, blocked, throwingdatum) // TODO: find a better way to handle hitpush and skipcatch for humans + var/nosell_hit = (SEND_SIGNAL(thrown_item, COMSIG_MOVABLE_IMPACT_ZONE, src, zone, blocked, throwingdatum) & MOVABLE_IMPACT_ZONE_OVERRIDE) // TODO: find a better way to handle hitpush and skipcatch for humans if(nosell_hit) skipcatch = TRUE hitpush = FALSE diff --git a/code/modules/mob/living/simple_animal/hostile/ooze.dm b/code/modules/mob/living/simple_animal/hostile/ooze.dm index a47d9fe2676..77ccf3ea483 100644 --- a/code/modules/mob/living/simple_animal/hostile/ooze.dm +++ b/code/modules/mob/living/simple_animal/hostile/ooze.dm @@ -389,50 +389,32 @@ name = "mending globule" icon_state = "glob_projectile" shrapnel_type = /obj/item/mending_globule - embed_type = /datum/embed_data/mending_globule + embed_type = /datum/embedding/mending_globule damage = 0 -///This item is what is embedded into the mob, and actually handles healing of mending globules +///This item is what is embedded into the mob /obj/item/mending_globule name = "mending globule" desc = "It somehow heals those who touch it." icon = 'icons/obj/science/vatgrowing.dmi' icon_state = "globule" - embed_type = /datum/embed_data/mending_globule - var/obj/item/bodypart/bodypart var/heals_left = 35 -/datum/embed_data/mending_globule +/datum/embedding/mending_globule embed_chance = 100 ignore_throwspeed_threshold = TRUE pain_mult = 0 jostle_pain_mult = 0 fall_chance = 0.5 -/obj/item/mending_globule/Destroy() +// This already processes, zero logic to add additional tracking to the item +/datum/embedding/mending_globule/process(seconds_per_tick) . = ..() - bodypart = null - -/obj/item/mending_globule/embedded(mob/living/carbon/human/embedded_mob, obj/item/bodypart/part) - . = ..() - if(!istype(part)) - return - bodypart = part - START_PROCESSING(SSobj, src) - -/obj/item/mending_globule/unembedded() - . = ..() - bodypart = null - STOP_PROCESSING(SSobj, src) - -///Handles the healing of the mending globule -/obj/item/mending_globule/process() - if(!bodypart) //this is fucked - return FALSE - bodypart.heal_damage(1,1) - heals_left-- - if(heals_left <= 0) - qdel(src) + var/obj/item/mending_globule/globule = parent + owner_limb.heal_damage(0.5 * seconds_per_tick, 0.5 * seconds_per_tick) + globule.heals_left-- + if(globule.heals_left <= 0) + qdel(globule) ///This action lets you put a mob inside of a cacoon that will inject it with some chemicals. /datum/action/cooldown/gel_cocoon diff --git a/code/modules/mod/modules/modules_engineering.dm b/code/modules/mod/modules/modules_engineering.dm index ea12a61e5b4..7ffda55fbb0 100644 --- a/code/modules/mod/modules/modules_engineering.dm +++ b/code/modules/mod/modules/modules_engineering.dm @@ -128,7 +128,7 @@ hitsound_wall = 'sound/items/weapons/batonextend.ogg' suppressed = SUPPRESSED_VERY hit_threshhold = ABOVE_NORMAL_TURF_LAYER - embed_type = /datum/embed_data/tether_projectile + embed_type = /datum/embedding/tether_projectile shrapnel_type = /obj/item/tether_anchor /// Reference to the beam following the projectile. var/line @@ -262,7 +262,7 @@ to_chat(target, span_userdanger("[user] attaches a tether to you!")) target.AddComponent(/datum/component/tether, src, 7, "tether", tether_trait_source = REF(src), no_target_trait = TRUE) -/datum/embed_data/tether_projectile +/datum/embedding/tether_projectile embed_chance = 65 //spiky fall_chance = 2 ignore_throwspeed_threshold = TRUE diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm index ff113e2b2cd..a64b5789553 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -30,7 +30,7 @@ var/degrees = 0 var/font = PEN_FONT var/requires_gravity = TRUE // can you use this to write in zero-g - embed_type = /datum/embed_data/pen + embed_type = /datum/embedding/pen sharpness = SHARP_POINTY var/dart_insert_icon = 'icons/obj/weapons/guns/toy.dmi' var/dart_insert_casing_icon_state = "overlay_pen" @@ -38,7 +38,7 @@ /// If this pen can be clicked in order to retract it var/can_click = TRUE -/datum/embed_data/pen +/datum/embedding/pen embed_chance = 50 /obj/item/pen/Initialize(mapload) @@ -86,11 +86,11 @@ /obj/item/pen/proc/on_inserted_into_dart(datum/source, obj/projectile/dart, mob/user, embedded = FALSE) SIGNAL_HANDLER -/obj/item/pen/proc/get_dart_var_modifiers() +/obj/item/pen/proc/get_dart_var_modifiers(obj/projectile/projectile) return list( "damage" = max(5, throwforce), "speed" = max(0, throw_speed - 3), - "embedding" = get_embed(), + "embedding" = get_embed().create_copy(projectile), "armour_penetration" = armour_penetration, "wound_bonus" = wound_bonus, "bare_wound_bonus" = bare_wound_bonus, @@ -195,7 +195,7 @@ "Black and Silver" = "pen-fountain-b", "Command Blue" = "pen-fountain-cb" ) - embed_type = /datum/embed_data/pen/captain + embed_type = /datum/embedding/pen/captain dart_insert_casing_icon_state = "overlay_fountainpen_gold" dart_insert_projectile_icon_state = "overlay_fountainpen_gold_proj" var/list/overlay_reskin = list( @@ -206,7 +206,7 @@ "Command Blue" = "overlay_fountainpen_gold" ) -/datum/embed_data/pen/captain +/datum/embedding/pen/captain embed_chance = 50 /obj/item/pen/fountain/captain/Initialize(mapload) @@ -369,8 +369,8 @@ var/datum/component/transforming/transform_comp = GetComponent(/datum/component/transforming) .["damage"] = max(5, transform_comp.throwforce_on) .["speed"] = max(0, transform_comp.throw_speed_on - 3) - var/datum/embed_data/data = .["embedding"] - .["embedding"] = data.generate_with_values(embed_chance = 100) + var/datum/embedding/data = .["embedding"] + data.embed_chance = 100 /obj/item/pen/edagger/proc/on_containing_dart_fired(obj/projectile/source) SIGNAL_HANDLER @@ -426,7 +426,7 @@ inhand_icon_state = hidden_icon lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - set_embed(/datum/embed_data/edagger_active) + set_embed(/datum/embedding/edagger_active) else name = initial(name) desc = initial(desc) @@ -442,7 +442,7 @@ set_light_on(active) return COMPONENT_NO_DEFAULT_MESSAGE -/datum/embed_data/edagger_active +/datum/embedding/edagger_active embed_chance = 100 /obj/item/pen/edagger/proc/on_scan(datum/source, mob/user, list/extra_data) diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm index 41152b170ad..4fc28dd7891 100644 --- a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm +++ b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm @@ -33,9 +33,9 @@ speed = 1 range = 25 shrapnel_type = null - embed_type = /datum/embed_data/arrow + embed_type = /datum/embedding/arrow -/datum/embed_data/arrow +/datum/embedding/arrow embed_chance = 90 fall_chance = 2 jostle_chance = 2 @@ -62,9 +62,9 @@ damage = 30 speed = 1.3 range = 20 - embed_type = /datum/embed_data/arrow/sticky + embed_type = /datum/embedding/arrow/sticky -/datum/embed_data/arrow/sticky +/datum/embedding/arrow/sticky embed_chance = 99 fall_chance = 0 jostle_chance = 1 @@ -89,7 +89,7 @@ desc = "Better to not get hit with this!" icon_state = "poison_arrow_projectile" damage = 40 - embed_type = /datum/embed_data/arrow + embed_type = /datum/embedding/arrow /obj/projectile/bullet/arrow/poison/on_hit(atom/target, blocked, pierce_hit) . = ..() diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index f907682305e..51f50157242 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -248,7 +248,7 @@ /// If we have a shrapnel_type defined, these embedding stats will be passed to the spawned shrapnel type, which will roll for embedding on the target var/embed_type /// Saves embedding data - var/datum/embed_data/embed_data + VAR_PROTECTED/datum/embedding/embed_data /// If TRUE, hit mobs, even if they are lying on the floor and are not our target within MAX_RANGE_HIT_PRONE_TARGETS tiles var/hit_prone_targets = FALSE /// If TRUE, ignores the range of MAX_RANGE_HIT_PRONE_TARGETS tiles of hit_prone_targets @@ -273,8 +273,8 @@ /obj/projectile/Initialize(mapload) . = ..() maximum_range = range - if (get_embed()) - AddElement(/datum/element/embed) + if (embed_type) + set_embed(embed_type) add_traits(list(TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT), INNATE_TRAIT) /obj/projectile/Destroy() @@ -283,6 +283,7 @@ STOP_PROCESSING(SSprojectiles, src) firer = null original = null + QDEL_NULL(embed_data) if (movement_vector) QDEL_NULL(movement_vector) if (beam_points) @@ -299,7 +300,7 @@ wound_bonus += wound_falloff_tile bare_wound_bonus = max(0, bare_wound_bonus + wound_falloff_tile) if(embed_falloff_tile && get_embed()) - set_embed(embed_data.generate_with_values(embed_data.embed_chance + embed_falloff_tile)) + embed_data.embed_chance += embed_falloff_tile if(damage_falloff_tile && damage >= 0) damage += damage_falloff_tile if(stamina_falloff_tile && stamina >= 0) @@ -387,6 +388,7 @@ new impact_effect_type(target_turf, impact_x, impact_y) var/mob/living/living_target = target + get_embed()?.try_embed_projectile(src, target, hit_limb_zone, blocked, pierce_hit) var/reagent_note if(reagents?.reagent_list) reagent_note = "REAGENTS: [pretty_string_from_reagent_list(reagents.reagent_list)]" @@ -1332,7 +1334,7 @@ ///Checks if the projectile can embed into someone /obj/projectile/proc/can_embed_into(atom/hit) - return get_embed() && shrapnel_type && iscarbon(hit) && !HAS_TRAIT(hit, TRAIT_PIERCEIMMUNE) + return shrapnel_type && get_embed()?.can_embed(src, hit) /// Reflects the projectile off of something /obj/projectile/proc/reflect(atom/hit_atom) @@ -1372,19 +1374,27 @@ bullet.fire() return bullet -/// Fetches embedding data -/obj/projectile/proc/get_embed() - RETURN_TYPE(/datum/embed_data) - return embed_type ? (embed_data ||= get_embed_by_type(embed_type)) : embed_data - -/obj/projectile/proc/set_embed(datum/embed_data/embed) - if(embed_data == embed) - return - // GLOB.embed_by_type stores shared "default" embedding values of datums - // Dynamically generated embeds use the base class and thus are not present in there, and should be qdeleted upon being discarded - if(!isnull(embed_data) && !GLOB.embed_by_type[embed_data.type]) - qdel(embed_data) - embed_data = ispath(embed) ? get_embed_by_type(armor) : embed - #undef MOVES_HITSCAN #undef MUZZLE_EFFECT_PIXEL_INCREMENT + +/// Fetches, or lazyloads, our embedding datum +/obj/projectile/proc/get_embed() + RETURN_TYPE(/datum/embedding) + if (embed_data) + return embed_data + if (embed_type) + embed_data = new embed_type(src) + return embed_data + +/// Sets our embedding datum to a different one. Can also take types +/obj/projectile/proc/set_embed(datum/embedding/new_embed, dont_delete = FALSE) + if (new_embed == embed_data) + return + + if (!isnull(embed_data) && !dont_delete) + qdel(embed_data) + + if (ispath(new_embed)) + new_embed = new new_embed() + + embed_data = new_embed diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm index 1d1313d9e55..2db698c670b 100644 --- a/code/modules/projectiles/projectile/bullets.dm +++ b/code/modules/projectiles/projectile/bullets.dm @@ -8,7 +8,7 @@ sharpness = SHARP_POINTY impact_effect_type = /obj/effect/temp_visual/impact_effect shrapnel_type = /obj/item/shrapnel/bullet - embed_type = /datum/embed_data/bullet + embed_type = /datum/embedding/bullet wound_bonus = 0 wound_falloff_tile = -5 embed_falloff_tile = -3 @@ -17,7 +17,7 @@ name = "divine retribution" damage = 10 -/datum/embed_data/bullet +/datum/embedding/bullet embed_chance=20 fall_chance=2 jostle_chance=0 diff --git a/code/modules/projectiles/projectile/bullets/junk.dm b/code/modules/projectiles/projectile/bullets/junk.dm index 1c6ea89962e..12712cd9dc8 100644 --- a/code/modules/projectiles/projectile/bullets/junk.dm +++ b/code/modules/projectiles/projectile/bullets/junk.dm @@ -4,7 +4,7 @@ name = "junk bullet" icon_state = "trashball" damage = 30 - embed_type = /datum/embed_data/bullet_junk + embed_type = /datum/embedding/bullet_junk /// What biotype does our junk projectile especially harm? var/extra_damage_mob_biotypes = MOB_ROBOTIC /// How much do we multiply our total base damage? @@ -28,15 +28,15 @@ if(finalized_damage) living_target.apply_damage(finalized_damage, damagetype = extra_damage_type, def_zone = BODY_ZONE_CHEST, wound_bonus = wound_bonus) -/datum/embed_data/bullet_junk - embed_chance=15 - fall_chance=3 - jostle_chance=4 - ignore_throwspeed_threshold=TRUE - pain_stam_pct=0.4 - pain_mult=5 - jostle_pain_mult=6 - rip_time=10 +/datum/embedding/bullet_junk + embed_chance = 15 + fall_chance = 3 + jostle_chance = 4 + ignore_throwspeed_threshold = TRUE + pain_stam_pct = 0.4 + pain_mult = 5 + jostle_pain_mult = 6 + rip_time = 10 /obj/projectile/bullet/incendiary/fire/junk name = "burning oil" @@ -75,19 +75,19 @@ name = "junk ripper bullet" icon_state = "redtrac" damage = 10 - embed_type = /datum/embed_data/bullet_junk_ripper + embed_type = /datum/embedding/bullet_junk_ripper wound_bonus = 10 bare_wound_bonus = 30 -/datum/embed_data/bullet_junk_ripper - embed_chance=100 - fall_chance=3 - jostle_chance=4 - ignore_throwspeed_threshold=TRUE - pain_stam_pct=0.4 - pain_mult=5 - jostle_pain_mult=6 - rip_time=10 +/datum/embedding/bullet_junk_ripper + embed_chance = 100 + fall_chance = 3 + jostle_chance = 4 + ignore_throwspeed_threshold = TRUE + pain_stam_pct = 0.4 + pain_mult = 5 + jostle_pain_mult = 6 + rip_time = 10 /obj/projectile/bullet/junk/reaper name = "junk reaper bullet" diff --git a/code/modules/projectiles/projectile/bullets/pistol.dm b/code/modules/projectiles/projectile/bullets/pistol.dm index bc64363a2d3..3d832da9a7d 100644 --- a/code/modules/projectiles/projectile/bullets/pistol.dm +++ b/code/modules/projectiles/projectile/bullets/pistol.dm @@ -3,17 +3,17 @@ /obj/projectile/bullet/c9mm name = "9mm bullet" damage = 30 - embed_type = /datum/embed_data/bullet_c9mm + embed_type = /datum/embedding/bullet_c9mm -/datum/embed_data/bullet_c9mm - embed_chance=15 - fall_chance=3 - jostle_chance=4 - ignore_throwspeed_threshold=TRUE - pain_stam_pct=0.4 - pain_mult=5 - jostle_pain_mult=6 - rip_time=10 +/datum/embedding/bullet_c9mm + embed_chance = 15 + fall_chance = 3 + jostle_chance = 4 + ignore_throwspeed_threshold = TRUE + pain_stam_pct = 0.4 + pain_mult = 5 + jostle_pain_mult = 6 + rip_time = 10 /obj/projectile/bullet/c9mm/ap name = "9mm armor-piercing bullet" @@ -78,13 +78,13 @@ name = ".160 smart bullet" icon_state = "smartgun" damage = 10 - embed_type = /datum/embed_data/bullet_c160smart + embed_type = /datum/embedding/bullet_c160smart speed = 0.5 homing_turn_speed = 5 homing_inaccuracy_min = 4 homing_inaccuracy_max = 10 -/datum/embed_data/bullet_c160smart +/datum/embedding/bullet_c160smart embed_chance = 10 fall_chance = 5 jostle_chance = 3 diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm index 273a0109c56..df798142a12 100644 --- a/code/modules/projectiles/projectile/bullets/revolver.dm +++ b/code/modules/projectiles/projectile/bullets/revolver.dm @@ -21,18 +21,18 @@ ricochet_auto_aim_range = 3 wound_bonus = -20 bare_wound_bonus = 10 - embed_type = /datum/embed_data/bullet_c38 + embed_type = /datum/embedding/bullet_c38 embed_falloff_tile = -4 -/datum/embed_data/bullet_c38 - embed_chance=25 - fall_chance=2 - jostle_chance=2 - ignore_throwspeed_threshold=TRUE - pain_stam_pct=0.4 - pain_mult=3 - jostle_pain_mult=5 - rip_time=1 SECONDS +/datum/embedding/bullet_c38 + embed_chance = 25 + fall_chance = 2 + jostle_chance = 2 + ignore_throwspeed_threshold = TRUE + pain_stam_pct = 0.4 + pain_mult = 3 + jostle_pain_mult = 5 + rip_time = 1 SECONDS /obj/projectile/bullet/c38/match name = ".38 Match bullet" @@ -75,19 +75,19 @@ sharpness = SHARP_EDGED wound_bonus = 20 bare_wound_bonus = 20 - embed_type = /datum/embed_data/bullet_c38_dumdum + embed_type = /datum/embedding/bullet_c38_dumdum wound_falloff_tile = -5 embed_falloff_tile = -15 -/datum/embed_data/bullet_c38_dumdum - embed_chance=75 - fall_chance=3 - jostle_chance=4 - ignore_throwspeed_threshold=TRUE - pain_stam_pct=0.4 - pain_mult=5 - jostle_pain_mult=6 - rip_time=1 SECONDS +/datum/embedding/bullet_c38_dumdum + embed_chance = 75 + fall_chance = 3 + jostle_chance = 4 + ignore_throwspeed_threshold = TRUE + pain_stam_pct = 0.4 + pain_mult = 5 + jostle_pain_mult = 6 + rip_time = 1 SECONDS /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 1302aea9315..67c06e021fe 100644 --- a/code/modules/projectiles/projectile/bullets/rifle.dm +++ b/code/modules/projectiles/projectile/bullets/rifle.dm @@ -48,19 +48,19 @@ armour_penetration = 50 wound_bonus = -20 bare_wound_bonus = 80 - embed_type = /datum/embed_data/harpoon + embed_type = /datum/embedding/harpoon wound_falloff_tile = -5 shrapnel_type = null -/datum/embed_data/harpoon - embed_chance=100 - fall_chance=3 - jostle_chance=4 - ignore_throwspeed_threshold=TRUE - pain_stam_pct=0.4 - pain_mult=5 - jostle_pain_mult=6 - rip_time=10 +/datum/embedding/harpoon + embed_chance = 100 + fall_chance = 3 + jostle_chance = 4 + ignore_throwspeed_threshold = TRUE + pain_stam_pct = 0.4 + pain_mult = 5 + jostle_pain_mult = 6 + rip_time = 10 // Rebar (Rebar Crossbow) /obj/projectile/bullet/rebar @@ -72,12 +72,12 @@ armour_penetration = 10 wound_bonus = -20 bare_wound_bonus = 20 - embed_type = /datum/embed_data/rebar + embed_type = /datum/embedding/rebar embed_falloff_tile = -5 wound_falloff_tile = -2 shrapnel_type = /obj/item/ammo_casing/rebar -/datum/embed_data/rebar +/datum/embedding/rebar embed_chance = 60 fall_chance = 2 jostle_chance = 2 @@ -98,10 +98,10 @@ wound_bonus = 10 bare_wound_bonus = 20 embed_falloff_tile = -3 - embed_type = /datum/embed_data/rebar_syndie + embed_type = /datum/embedding/rebar_syndie shrapnel_type = /obj/item/ammo_casing/rebar/syndie -/datum/embed_data/rebar_syndie +/datum/embedding/rebar_syndie embed_chance = 80 fall_chance = 1 jostle_chance = 3 @@ -122,11 +122,11 @@ armour_penetration = 20 // not nearly as good, as its not as sharp. wound_bonus = 10 bare_wound_bonus = 40 - embed_type = /datum/embed_data/rebar_zaukerite + embed_type = /datum/embedding/rebar_zaukerite embed_falloff_tile = 0 // very spiky. shrapnel_type = /obj/item/ammo_casing/rebar/zaukerite -/datum/embed_data/rebar_zaukerite +/datum/embedding/rebar_zaukerite embed_chance = 100 fall_chance = 0 jostle_chance = 5 @@ -151,7 +151,7 @@ wound_bonus = -100 bare_wound_bonus = 0 shrapnel_type = /obj/item/ammo_casing/rebar/hydrogen - embed_type = /datum/embed_data/rebar_hydrogen + embed_type = /datum/embedding/rebar_hydrogen embed_falloff_tile = -3 accurate_range = 205 //15 tiles before falloff starts to kick in @@ -159,7 +159,7 @@ . = ..() def_zone = ran_zone(def_zone, clamp(205-(7*get_dist(get_turf(A), starting)), 5, 100)) -/datum/embed_data/rebar_hydrogen +/datum/embedding/rebar_hydrogen embed_chance = 0 /obj/projectile/bullet/rebar/hydrogen/on_hit(atom/target, blocked, pierce_hit) diff --git a/code/modules/spells/spell_types/self/summonitem.dm b/code/modules/spells/spell_types/self/summonitem.dm index 4165781c366..acea28fc19b 100644 --- a/code/modules/spells/spell_types/self/summonitem.dm +++ b/code/modules/spells/spell_types/self/summonitem.dm @@ -128,17 +128,17 @@ // If its on someone, properly drop it if(ismob(item_to_retrieve.loc)) - var/mob/holding_mark = item_to_retrieve.loc - - // Items in silicons warp the whole silicon - if(issilicon(holding_mark)) - holding_mark.loc.visible_message(span_warning("[holding_mark] suddenly disappears!")) - holding_mark.forceMove(caster.loc) - holding_mark.loc.visible_message(span_warning("[holding_mark] suddenly appears!")) - item_to_retrieve = null + if(!issilicon(item_to_retrieve.loc)) break - holding_mark.dropItemToGround(item_to_retrieve) + // Items in silicons warp the whole silicon + var/mob/holding_mark = item_to_retrieve.loc + holding_mark.loc.visible_message(span_warning("[holding_mark] suddenly disappears!")) + holding_mark.forceMove(caster.loc) + holding_mark.loc.visible_message(span_warning("[holding_mark] suddenly appears!")) + SEND_SIGNAL(holding_mark, COMSIG_MAGIC_RECALL, caster, item_to_retrieve) + playsound(holding_mark, 'sound/effects/magic/summonitems_generic.ogg', 50, TRUE) + return else if(isobj(item_to_retrieve.loc)) var/obj/retrieved_item = item_to_retrieve.loc @@ -159,6 +159,13 @@ if(!item_to_retrieve) return + SEND_SIGNAL(item_to_retrieve, COMSIG_MAGIC_RECALL, caster, item_to_retrieve) + + if (ismob(item_to_retrieve.loc)) + var/mob/holder = item_to_retrieve.loc + if (!holder.dropItemToGround(item_to_retrieve, force = TRUE)) + return + item_to_retrieve.loc?.visible_message(span_warning("[item_to_retrieve] suddenly disappears!")) if(isitem(item_to_retrieve) && caster.put_in_hands(item_to_retrieve)) @@ -167,7 +174,6 @@ item_to_retrieve.forceMove(caster.drop_location()) item_to_retrieve.loc.visible_message(span_warning("[item_to_retrieve] suddenly appears!")) - SEND_SIGNAL(item_to_retrieve, COMSIG_MAGIC_RECALL, caster, item_to_retrieve) playsound(get_turf(item_to_retrieve), 'sound/effects/magic/summonitems_generic.ogg', 50, TRUE) /datum/action/cooldown/spell/summonitem/abductor diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 2588882d2ad..96defc7f12b 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -360,9 +360,14 @@ if(WOUND_SEVERITY_CRITICAL) check_list += "\t [span_boldwarning("Your [name] is suffering [wound.a_or_from] [LOWER_TEXT(wound.name)]!!!")]" - for(var/obj/item/embedded_thing in embedded_objects) - var/stuck_word = embedded_thing.is_embed_harmless() ? "stuck" : "embedded" - check_list += "\t There is \a [embedded_thing] [stuck_word] in your [name]!" + for(var/obj/item/embedded_thing as anything in embedded_objects) + var/harmless = embedded_thing.get_embed().is_harmless() + var/stuck_wordage = harmless ? "stuck to" : "embedded in" + var/embed_text = "\t There is [icon2html(embedded_thing, examiner)] \a [embedded_thing] [stuck_wordage] your [plaintext_zone]!" + if (harmless) + check_list += span_italics(span_notice(embed_text)) + else + check_list += span_boldwarning(embed_text) /obj/item/bodypart/blob_act() receive_damage(max_damage, wound_bonus = CANT_WOUND) @@ -1214,8 +1219,8 @@ if(generic_bleedstacks > 0) cached_bleed_rate += 0.5 - for(var/obj/item/embeddies in embedded_objects) - if(!embeddies.is_embed_harmless()) + for(var/obj/item/embeddies as anything in embedded_objects) + if(!embeddies.get_embed().is_harmless()) cached_bleed_rate += 0.25 for(var/datum/wound/iter_wound as anything in wounds) diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index f4724db3b69..52bc11e3cab 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -106,8 +106,6 @@ qdel(surgery) break - for(var/obj/item/embedded in embedded_objects) - embedded.forceMove(src) // It'll self remove via signal reaction, just need to move it if(!phantom_owner.has_embedded_objects()) phantom_owner.clear_alert(ALERT_EMBEDDED_OBJECT) phantom_owner.clear_mood_event("embedded") diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm index 2f9a42e0d1f..8d4d65de80f 100644 --- a/code/modules/surgery/bodyparts/helpers.dm +++ b/code/modules/surgery/bodyparts/helpers.dm @@ -129,18 +129,20 @@ ///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) + if (embedded.get_embed()?.owner != src) + return + embedded.get_embed().remove_embedding() ///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) + for(var/obj/item/embedded as anything in bodypart.embedded_objects) remove_embedded_object(embedded) -/mob/living/carbon/proc/has_embedded_objects(include_harmless=FALSE) +/mob/living/carbon/proc/has_embedded_objects(include_harmless = FALSE) for(var/obj/item/bodypart/bodypart as anything in bodyparts) - for(var/obj/item/embedded in bodypart.embedded_objects) - if(!include_harmless && embedded.is_embed_harmless()) + for(var/obj/item/embedded as anything in bodypart.embedded_objects) + if(!include_harmless && embedded.get_embed().is_harmless()) continue return TRUE diff --git a/code/modules/unit_tests/embedding.dm b/code/modules/unit_tests/embedding.dm index 5e6a8a90647..f5d730ec277 100644 --- a/code/modules/unit_tests/embedding.dm +++ b/code/modules/unit_tests/embedding.dm @@ -4,11 +4,10 @@ var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human/consistent) var/mob/living/carbon/human/firer = allocate(/mob/living/carbon/human/consistent) var/obj/projectile/bullet/c38/bullet = new(get_turf(firer)) - bullet.set_embed(bullet.get_embed().generate_with_values(embed_chance = 100)) + bullet.get_embed().embed_chance = 100 TEST_ASSERT_EQUAL(bullet.get_embed().embed_chance, 100, "embed_chance failed to modify") bullet.aim_projectile(victim, firer) bullet.fire(get_angle(firer, victim), victim) - var/list/components = victim.GetComponents(/datum/component/embedded) - TEST_ASSERT_EQUAL(components.len, 1, "Projectile with 100% embed chance didn't embed, or embedded multiple times") - var/datum/component/embedded/comp = components[1] - TEST_ASSERT_EQUAL(comp.weapon.get_embed().embed_chance, 100, "embed_chance modification did not transfer to shrapnel") + var/obj/item/shrapnel/shrapnel = locate() in victim + TEST_ASSERT(!isnull(shrapnel), "Projectile with 100% embed chance didn't embed") + TEST_ASSERT_EQUAL(shrapnel.get_embed().embed_chance, 100, "embed_chance modification did not transfer to shrapnel") diff --git a/code/modules/vehicles/vehicle_key.dm b/code/modules/vehicles/vehicle_key.dm index 60b578d0962..97541c37ae6 100644 --- a/code/modules/vehicles/vehicle_key.dm +++ b/code/modules/vehicles/vehicle_key.dm @@ -34,11 +34,11 @@ attack_verb_continuous = list("stubs", "pokes") attack_verb_simple = list("stub", "poke") sharpness = SHARP_EDGED - embed_type = /datum/embed_data/janicart_key + embed_type = /datum/embedding/janicart_key wound_bonus = -1 bare_wound_bonus = 2 -/datum/embed_data/janicart_key +/datum/embedding/janicart_key pain_mult = 1 embed_chance = 30 fall_chance = 70 diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index 79e6b86f545..bc1bec7a734 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -1082,7 +1082,7 @@ GLOBAL_LIST_EMPTY(vending_machines_to_restock) var/mob/living/carbon/carbon_target = atom_target for(var/i in 1 to num_shards) var/obj/item/shard/shard = new /obj/item/shard(get_turf(carbon_target)) - shard.set_embed(/datum/embed_data/glass_candy) + shard.set_embed(/datum/embedding/glass_candy) carbon_target.hitby(shard, skipcatch = TRUE, hitpush = FALSE) shard.set_embed(initial(shard.embed_type)) return TRUE diff --git a/tgstation.dme b/tgstation.dme index 2b009d39434..993c4b27d8c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -799,7 +799,7 @@ #include "code\datums\drift_handler.dm" #include "code\datums\ductnet.dm" #include "code\datums\eigenstate.dm" -#include "code\datums\embed_data.dm" +#include "code\datums\embedding.dm" #include "code\datums\emotes.dm" #include "code\datums\ert.dm" #include "code\datums\hailer_phrase.dm" @@ -1120,7 +1120,6 @@ #include "code\datums\components\effect_remover.dm" #include "code\datums\components\egg_layer.dm" #include "code\datums\components\electrified_buckle.dm" -#include "code\datums\components\embedded.dm" #include "code\datums\components\energized.dm" #include "code\datums\components\engraved.dm" #include "code\datums\components\evolutionary_leap.dm" @@ -1493,7 +1492,6 @@ #include "code\datums\elements\easily_fragmented.dm" #include "code\datums\elements\effect_trail.dm" #include "code\datums\elements\elevation.dm" -#include "code\datums\elements\embed.dm" #include "code\datums\elements\empprotection.dm" #include "code\datums\elements\envenomable_casing.dm" #include "code\datums\elements\eyestab.dm"