diff --git a/code/__DEFINES/achievements.dm b/code/__DEFINES/achievements.dm index 65bced18233..98f64531059 100644 --- a/code/__DEFINES/achievements.dm +++ b/code/__DEFINES/achievements.dm @@ -22,6 +22,7 @@ #define MEDAL_RULE8 "Rule 8" #define MEDAL_LONGSHIFT "longshift" #define MEDAL_SNAIL "KKKiiilll mmmeee" +#define MEDAL_LOOKOUTSIR "Look Out, Sir!" //Skill medal hub IDs #define MEDAL_LEGENDARY_MINER "Legendary Miner" diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index fcd819098e1..19288eccdf7 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -138,6 +138,7 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( #define EMBEDDED_JOSTLE_CHANCE 5 //Chance for embedded objects to cause pain every time they move (jostle) #define EMBEDDED_JOSTLE_PAIN_MULTIPLIER 1 //Coefficient of multiplication for the damage the item does while #define EMBEDDED_PAIN_STAM_PCT 0.0 //This percentage of all pain will be dealt as stam damage rather than brute (0-1) +#define EMBED_CHANCE_TURF_MOD -15 //You are this many percentage points less likely to embed into a turf (good for things glass shards and spears vs walls) #define EMBED_HARMLESS list("pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE) #define EMBED_HARMLESS_SUPERIOR list("pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE, "embed_chance" = 100, "fall_chance" = 0.1) @@ -179,6 +180,10 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( ///ammo box will have a different state for full and empty; -max_ammo and -0 #define AMMO_BOX_FULL_EMPTY 2 +#define SUPPRESSED_NONE 0 +#define SUPPRESSED_QUIET 1 ///standard suppressed +#define SUPPRESSED_VERY 2 /// no message + //Projectile Reflect #define REFLECT_NORMAL (1<<0) #define REFLECT_FAKEPROJECTILE (1<<1) @@ -235,3 +240,5 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( #define BULLET_ACT_BLOCK "BLOCK" //It's a blocked hit, whatever that means in the context of the thing it's hitting. #define BULLET_ACT_FORCE_PIERCE "PIERCE" //It pierces through the object regardless of the bullet being piercing by default. #define BULLET_ACT_TURF "TURF" //It hit us but it should hit something on the same turf too. Usually used for turfs. + +#define NICE_SHOT_RICOCHET_BONUS 10 //if the shooter has the NICE_SHOT trait and they fire a ricocheting projectile, add this to the ricochet chance and auto aim angle diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 5efe3e6fedf..dd9aba5377b 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -244,6 +244,8 @@ #define COMSIG_CARBON_LOSE_ORGAN "carbon_lose_organ" //from /item/organ/proc/Remove() (/obj/item/organ/) #define COMSIG_CARBON_EQUIP_HAT "carbon_equip_hat" //from /mob/living/carbon/doUnEquip(obj/item/I, force, newloc, no_move, invdrop, silent) #define COMSIG_CARBON_UNEQUIP_HAT "carbon_unequip_hat" //from /mob/living/carbon/doUnEquip(obj/item/I, force, newloc, no_move, invdrop, silent) +#define COMSIG_CARBON_EMBED_RIP "item_embed_start_rip" // 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_REMOVAL "item_embed_remove_safe" // called when removing a given item from a mob, from mob/living/carbon/remove_embedded_object(mob/living/carbon/target, /obj/item) // /mob/living/simple_animal/hostile signals #define COMSIG_HOSTILE_ATTACKINGTARGET "hostile_attackingtarget" @@ -287,6 +289,8 @@ #define COMPONENT_BLOCK_SHARPEN_MAXED 8 #define COMSIG_TOOL_IN_USE "tool_in_use" ///from base of [/obj/item/proc/tool_check_callback]: (mob/living/user) #define COMSIG_TOOL_START_USE "tool_start_use" ///from base of [/obj/item/proc/tool_start_check]: (mob/living/user) +#define COMSIG_ITEM_DISABLE_EMBED "item_disable_embed" ///from [/obj/item/proc/disableEmbedding]: +#define COMSIG_MINE_TRIGGERED "minegoboom" ///from [/obj/effect/mine/proc/triggermine]: // /obj/item signals for economy #define COMSIG_ITEM_SOLD "item_sold" //called when an item is sold by the exports subsystem @@ -329,11 +333,20 @@ // /obj/item/gun signals #define COMSIG_MOB_FIRED_GUN "mob_fired_gun" //called in /obj/item/gun/process_fire (user, target, params, zone_override) +// /obj/item/grenade signals +#define COMSIG_GRENADE_PRIME "grenade_prime" //called in /obj/item/gun/process_fire (user, target, params, zone_override) +#define COMSIG_GRENADE_ARMED "grenade_armed" //called in /obj/item/gun/process_fire (user, target, params, zone_override) + // /obj/projectile signals (sent to the firer) +#define COMSIG_PROJECTILE_SELF_ON_HIT "projectile_self_on_hit" // from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, Angle) #define COMSIG_PROJECTILE_ON_HIT "projectile_on_hit" // from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, Angle) #define COMSIG_PROJECTILE_BEFORE_FIRE "projectile_before_fire" // from base of /obj/projectile/proc/fire(): (obj/projectile, atom/original_target) #define COMSIG_PROJECTILE_FIRE "projectile_fire" // from the base of /obj/projectile/proc/fire(): () #define COMSIG_PROJECTILE_PREHIT "com_proj_prehit" // sent to targets during the process_hit proc of projectiles +#define COMSIG_PROJECTILE_RANGE_OUT "projectile_range_out" // sent to targets during the process_hit proc of projectiles +#define COMSIG_EMBED_TRY_FORCE "item_try_embed" // sent when trying to force an embed (mainly for projectiles, only used in the embed element) + +#define COMSIG_PELLET_CLOUD_INIT "pellet_cloud_init" // sent to targets during the process_hit proc of projectiles // /obj/mecha signals #define COMSIG_MECHA_ACTION_ACTIVATE "mecha_action_activate" //sent from mecha action buttons to the mecha they're linked to @@ -344,8 +357,6 @@ #define COMSIG_HUMAN_MELEE_UNARMED_ATTACKBY "human_melee_unarmed_attackby" //from mob/living/carbon/human/UnarmedAttack(): (mob/living/carbon/human/attacker) #define COMSIG_HUMAN_DISARM_HIT "human_disarm_hit" //Hit by successful disarm attack (mob/living/carbon/human/attacker,zone_targeted) #define COMSIG_JOB_RECEIVED "job_received" //Whenever EquipRanked is called, called after job is set -#define COMSIG_HUMAN_EMBED_RIP "item_embed_removing" // called on human when said human tries to rip out this embedded item (mob/living/carbon/human/target, /obj/item, /obj/item/bodypart/L) -#define COMSIG_HUMAN_EMBED_REMOVAL "item_embed_remove_surgery" // called on human from /datum/surgery_step/remove_object/success (mob/living/carbon/human/target, /obj/item, /obj/item/bodypart/L) // /datum/species signals #define COMSIG_SPECIES_GAIN "species_gain" //from datum/species/on_species_gain(): (datum/species/new_species, datum/species/old_species) diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index 1980ab71eae..46931cc1b43 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -15,8 +15,6 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 /// This flag is what recursive_hear_check() uses to determine wether to add an item to the hearer list or not. #define HEAR_1 (1<<3) -/// Projectiels will check ricochet on things impacted that have this. -#define CHECK_RICOCHET_1 (1<<4) /// conducts electricity (metal etc.) #define CONDUCT_1 (1<<5) /// For machines and structures that should not break into parts, eg, holodeck stuff @@ -38,6 +36,11 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define PREVENT_CONTENTS_EXPLOSION_1 (1<<16) +/// If the thing can reflect light (lasers/energy) +#define RICOCHET_SHINY (1<<0) +/// If the thing can reflect matter (bullets/bomb shrapnel) +#define RICOCHET_HARD (1<<1) + //turf-only flags #define NOJAUNT_1 (1<<0) #define UNUSED_RESERVATION_TURF_1 (1<<1) diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index dfb6a68c6a5..c417b821103 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -154,6 +154,12 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list( #define isitem(A) (istype(A, /obj/item)) +#define isgrenade(A) (istype(A, /obj/item/grenade)) + +#define islandmine(A) (istype(A, /obj/effect/mine)) + +#define isammocasing(A) (istype(A, /obj/item/ammo_casing)) + #define isidcard(I) (istype(I, /obj/item/card/id)) #define isstructure(A) (istype(A, /obj/structure)) diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index f23d8e15357..354c0912842 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -183,6 +183,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_GIANT "giant" #define TRAIT_DWARF "dwarf" #define TRAIT_SILENT_FOOTSTEPS "silent_footsteps" //makes your footsteps completely silent +#define TRAIT_NICE_SHOT "nice_shot" //hnnnnnnnggggg..... you're pretty good.... //non-mob traits #define TRAIT_PARALYSIS "paralysis" //Used for limb-based paralysis, where replacing the limb will fix it diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 60c05c9897e..97fbb9b7d98 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -729,22 +729,6 @@ Turf and target are separate in case you want to teleport some distance from a t return null -//For objects that should embed, but make no sense being is_sharp or is_pointed() -//e.g: rods -GLOBAL_LIST_INIT(can_embed_types, typecacheof(list( - /obj/item/stack/rods, - /obj/item/pipe))) - -/proc/can_embed(obj/item/W) - if(W.get_sharpness()) - return 1 - if(is_pointed(W)) - return 1 - - if(is_type_in_typecache(W, GLOB.can_embed_types)) - return 1 - - /* Checks if that loc and dir has an item on the wall */ diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 8cd0e1827a9..4cfe8345a52 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -122,7 +122,6 @@ GLOBAL_LIST_INIT(bitfields, list( "CAN_BE_DIRTY_1" = CAN_BE_DIRTY_1, "CULT_PERMITTED_1" = CULT_PERMITTED_1, "HEAR_1" = HEAR_1, - "CHECK_RICOCHET_1" = CHECK_RICOCHET_1, "CONDUCT_1" = CONDUCT_1, "NO_LAVA_GEN_1" = NO_LAVA_GEN_1, "NODECONSTRUCT_1" = NODECONSTRUCT_1, @@ -135,6 +134,10 @@ GLOBAL_LIST_INIT(bitfields, list( "INITIALIZED_1" = INITIALIZED_1, "ADMIN_SPAWNED_1" = ADMIN_SPAWNED_1 ), + "flags_ricochet" = list( + "RICOCHET_SHINY" = RICOCHET_SHINY, + "RICOCHET_HARD" = RICOCHET_HARD + ), "clothing_flags" = list( "LAVAPROTECT" = LAVAPROTECT, "STOPSPRESSUREDAMAGE" = STOPSPRESSUREDAMAGE, diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index 587ecaf5608..4bdc3c8827a 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -106,7 +106,8 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_GAMERGOD" = TRAIT_GAMERGOD, "TRAIT_DWARF" = TRAIT_DWARF, "TRAIT_GIANT" = TRAIT_GIANT, - "TRAIT_SILENT_FOOTSTEPS" = TRAIT_SILENT_FOOTSTEPS + "TRAIT_SILENT_FOOTSTEPS" = TRAIT_SILENT_FOOTSTEPS, + "TRAIT_NICE_SHOT" = TRAIT_NICE_SHOT ), /obj/item/bodypart = list( "TRAIT_PARALYSIS" = TRAIT_PARALYSIS diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index c77880555e5..357d3c45318 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -244,7 +244,7 @@ If you're feeling frisky, examine yourself and click the underlined item to pull /obj/screen/alert/embeddedobject/Click() if(isliving(usr) && usr == owner) - var/mob/living/carbon/human/M = usr + var/mob/living/carbon/M = usr return M.help_shake_act(M) /obj/screen/alert/weightless diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index be33e46d327..3236a1a0ce8 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -639,9 +639,9 @@ screen_loc = ui_healthdoll /obj/screen/healthdoll/Click() - if (ishuman(usr)) - var/mob/living/carbon/human/H = usr - H.check_self_for_injuries() + if (iscarbon(usr)) + var/mob/living/carbon/C = usr + C.check_self_for_injuries() /obj/screen/healthdoll/living icon_state = "fullhealth0" diff --git a/code/datums/achievements/misc_achievements.dm b/code/datums/achievements/misc_achievements.dm index c81d2c51ddb..d67bee2f008 100644 --- a/code/datums/achievements/misc_achievements.dm +++ b/code/datums/achievements/misc_achievements.dm @@ -101,3 +101,8 @@ desc = "You were a little too ambitious, but hey, I guess you're still alive?" database_id = MEDAL_SNAIL icon = "snail" + +/datum/award/achievement/misc/lookoutsir + name = "Look Out, Sir!" + desc = "Either awarded for making the ultimate sacrifice for your comrades, or a really dumb attempt at grenade jumping." + database_id = MEDAL_LOOKOUTSIR diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm index 984a298bef5..a5fdae4d91f 100644 --- a/code/datums/components/_component.dm +++ b/code/datums/components/_component.dm @@ -50,8 +50,9 @@ parent = raw_args[1] var/list/arguments = raw_args.Copy(2) if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE) + stack_trace("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]") qdel(src, TRUE, TRUE) - CRASH("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]") + return _JoinParent(parent) diff --git a/code/datums/components/embedded.dm b/code/datums/components/embedded.dm index 0ac5b4e35e8..d0159cd4a47 100644 --- a/code/datums/components/embedded.dm +++ b/code/datums/components/embedded.dm @@ -3,12 +3,12 @@ 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. - There are 2 different things that can be embedded presently: humans, and closed turfs (see: walls) + There are 2 different things that can be embedded presently: carbons, and closed turfs (see: walls) - - Human embedding has all the classical embedding behavior, and tracks more events and signals. The main behaviors and hooks to look for are: + - 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 human topic() and the embed removal surgery in order to handle removals. + -- Various signals hooking into carbon topic() and the embed removal surgery in order to handle removals. - Turf embedding is much simpler. All we do here is draw an overlay of the item's inhand on the turf, hide the item, and create an HTML link in the turf's inspect that allows you to rip the item out. There's nothing dynamic about this, so far less checks. @@ -24,7 +24,7 @@ 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 for humans + -- All damage checks and bloodloss are skipped for carbons -- Pointy objects create sparks when embedding into a turf */ @@ -32,7 +32,7 @@ /datum/component/embedded dupe_mode = COMPONENT_DUPE_ALLOWED - var/obj/item/bodypart/L + var/obj/item/bodypart/limb var/obj/item/weapon // all of this stuff is explained in _DEFINES/combat.dm @@ -47,6 +47,7 @@ var/jostle_chance var/jostle_pain_mult var/pain_stam_pct + var/embed_chance_turf_mod ///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 @@ -54,6 +55,7 @@ /datum/component/embedded/Initialize(obj/item/I, datum/thrownthing/throwingdatum, + obj/item/bodypart/part, embed_chance = EMBED_CHANCE, fall_chance = EMBEDDED_ITEM_FALLOUT, pain_chance = EMBEDDED_PAIN_CHANCE, @@ -64,11 +66,14 @@ ignore_throwspeed_threshold = FALSE, jostle_chance = EMBEDDED_JOSTLE_CHANCE, jostle_pain_mult = EMBEDDED_JOSTLE_PAIN_MULTIPLIER, - pain_stam_pct = EMBEDDED_PAIN_STAM_PCT) + pain_stam_pct = EMBEDDED_PAIN_STAM_PCT, + embed_chance_turf_mod = EMBED_CHANCE_TURF_MOD) - if((!ishuman(parent) && !isclosedturf(parent)) || !isitem(I)) + if((!iscarbon(parent) && !isclosedturf(parent)) || !isitem(I)) return COMPONENT_INCOMPATIBLE + if(part) + limb = part src.embed_chance = embed_chance src.fall_chance = fall_chance src.pain_chance = pain_chance @@ -80,36 +85,37 @@ src.jostle_chance = jostle_chance src.jostle_pain_mult = jostle_pain_mult src.pain_stam_pct = pain_stam_pct + src.embed_chance_turf_mod = embed_chance_turf_mod src.weapon = I - if(src.pain_mult || src.jostle_pain_mult) + if(!weapon.isEmbedHarmless()) harmful = TRUE - if(ishuman(parent)) - initHuman() + if(iscarbon(parent)) + initCarbon() else if(isclosedturf(parent)) initTurf(throwingdatum) /datum/component/embedded/RegisterWithParent() - if(ishuman(parent)) + if(iscarbon(parent)) RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/jostleCheck) - RegisterSignal(parent, COMSIG_HUMAN_EMBED_RIP, .proc/ripOutHuman) - RegisterSignal(parent, COMSIG_HUMAN_EMBED_REMOVAL, .proc/safeRemoveHuman) + RegisterSignal(parent, COMSIG_CARBON_EMBED_RIP, .proc/ripOutCarbon) + RegisterSignal(parent, COMSIG_CARBON_EMBED_REMOVAL, .proc/safeRemoveCarbon) else if(isclosedturf(parent)) RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examineTurf) + RegisterSignal(parent, COMSIG_PARENT_QDELETING, .proc/itemMoved) /datum/component/embedded/UnregisterFromParent() - if(ishuman(parent)) - UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_HUMAN_EMBED_RIP, COMSIG_HUMAN_EMBED_REMOVAL)) - else if(isclosedturf(parent)) - UnregisterSignal(parent, COMSIG_PARENT_EXAMINE) + UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_CARBON_EMBED_RIP, COMSIG_CARBON_EMBED_REMOVAL, COMSIG_PARENT_EXAMINE)) /datum/component/embedded/process() - if(ishuman(parent)) - processHuman() + if(iscarbon(parent)) + processCarbon() /datum/component/embedded/Destroy() + if(weapon) + UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING)) if(overlay) var/atom/A = parent A.cut_overlay(overlay, TRUE) @@ -121,29 +127,33 @@ /////////////HUMAN PROCS//////////////// //////////////////////////////////////// -/// Set up an instance of embedding for a human. This is basically an extension of Initialize() so not much to say -/datum/component/embedded/proc/initHuman() +/// Set up an instance of embedding for a carbon. This is basically an extension of Initialize() so not much to say +/datum/component/embedded/proc/initCarbon() START_PROCESSING(SSdcs, src) - var/mob/living/carbon/human/victim = parent - L = pick(victim.bodyparts) - L.embedded_objects |= weapon // on the inside... on the inside... + var/mob/living/carbon/victim = parent + if(!istype(limb)) + limb = pick(victim.bodyparts) + + limb.embedded_objects |= weapon // on the inside... on the inside... weapon.forceMove(victim) + RegisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING), .proc/byeItemCarbon) if(harmful) - victim.visible_message("[weapon] embeds itself in [victim]'s [L.name]!","[weapon] embeds itself in your [L.name]!") + victim.visible_message("[weapon] embeds itself in [victim]'s [limb.name]!",ignored_mobs=victim) + to_chat(victim, "[weapon] embeds itself in your [limb.name]!") victim.throw_alert("embeddedobject", /obj/screen/alert/embeddedobject) playsound(victim,'sound/weapons/bladeslice.ogg', 40) weapon.add_mob_blood(victim)//it embedded itself in you, of course it's bloody! var/damage = weapon.w_class * impact_pain_mult - L.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) + limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) SEND_SIGNAL(victim, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded) else - victim.visible_message("[weapon] sticks itself to [victim]'s [L.name]!","[weapon] sticks itself to your [L.name]!") + victim.visible_message("[weapon] sticks itself to [victim]'s [limb.name]!",ignored_mobs=victim) + to_chat(victim, "[weapon] sticks itself to your [limb.name]!") - -/// Called every time a human with a harmful embed moves, rolling a chance for the item to cause pain. The chance is halved if the human is crawling or walking. +/// 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() - var/mob/living/carbon/human/victim = parent + var/mob/living/carbon/victim = parent var/chance = jostle_chance if(victim.m_intent == MOVE_INTENT_WALK || !(victim.mobility_flags & MOBILITY_STAND)) @@ -151,54 +161,73 @@ if(harmful && prob(chance)) var/damage = weapon.w_class * jostle_pain_mult - L.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) - to_chat(victim, "[weapon] embedded in your [L.name] jostles and stings!") + limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) + to_chat(victim, "[weapon] embedded in your [limb.name] jostles and stings!") -/// Called when then item randomly falls out of a human. This handles the damage and descriptors, then calls safe_remove() -/datum/component/embedded/proc/fallOutHuman() - var/mob/living/carbon/human/victim = parent +/// Called when then item randomly falls out of a carbon. This handles the damage and descriptors, then calls safe_remove() +/datum/component/embedded/proc/fallOutCarbon() + var/mob/living/carbon/victim = parent if(harmful) var/damage = weapon.w_class * remove_pain_mult - L.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) - victim.visible_message("[weapon] falls out of [victim.name]'s [L.name]!","[weapon] falls out of your [L.name]!") + limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) + victim.visible_message("[weapon] falls out of [victim.name]'s [limb.name]!", ignored_mobs=victim) + to_chat(victim, "[weapon] falls out of your [limb.name]!") else - victim.visible_message("[weapon] falls off of [victim.name]'s [L.name]!","[weapon] falls off of your [L.name]!") + victim.visible_message("[weapon] falls off of [victim.name]'s [limb.name]!", ignored_mobs=victim) + to_chat(victim, "[weapon] falls off of your [limb.name]!") - safeRemoveHuman() + safeRemoveCarbon() -/// Called when a human 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/ripOutHuman() - var/mob/living/carbon/human/victim = parent +/// 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/ripOutCarbon(datum/source, obj/item/I, obj/item/bodypart/limb) + if(I != weapon || src.limb != limb) + return + + var/mob/living/carbon/victim = parent var/time_taken = rip_time * weapon.w_class - victim.visible_message("[victim] attempts to remove [weapon] from [victim.p_their()] [L.name].","You attempt to remove [weapon] from your [L.name]... (It will take [DisplayTimeText(time_taken)].)") + victim.visible_message("[victim] attempts to remove [weapon] from [victim.p_their()] [limb.name].","You attempt to remove [weapon] from your [limb.name]... (It will take [DisplayTimeText(time_taken)].)") if(do_after(victim, time_taken, target = victim)) - if(!weapon || !L || weapon.loc != victim || !(weapon in L.embedded_objects)) + if(!weapon || !limb || weapon.loc != victim || !(weapon in limb.embedded_objects)) qdel(src) + return if(harmful) var/damage = weapon.w_class * remove_pain_mult - L.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) //It hurts to rip it out, get surgery you dingus. + limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) //It hurts to rip it out, get surgery you dingus. victim.emote("scream") - victim.visible_message("[victim] successfully rips [weapon] out of [victim.p_their()] [L.name]!", "You successfully remove [weapon] from your [L.name].") + victim.visible_message("[victim] successfully rips [weapon] out of [victim.p_their()] [limb.name]!", "You successfully remove [weapon] from your [limb.name].") else - victim.visible_message("[victim] successfully rips [weapon] off of [victim.p_their()] [L.name]!", "You successfully remove [weapon] from your [L.name].") + victim.visible_message("[victim] successfully rips [weapon] off of [victim.p_their()] [limb.name]!", "You successfully remove [weapon] from your [limb.name].") - safeRemoveHuman(TRUE) + safeRemoveCarbon(TRUE) -/// This proc handles the final step and actual removal of an embedded/stuck item from a human, whether or not it was actually removed safely. +/// 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. /// Pass TRUE for to_hands if we want it to go to the victim's hands when they pull it out -/datum/component/embedded/proc/safeRemoveHuman(to_hands) - var/mob/living/carbon/human/victim = parent - L.embedded_objects -= weapon +/datum/component/embedded/proc/safeRemoveCarbon(to_hands) + var/mob/living/carbon/victim = parent + limb.embedded_objects -= weapon - if(!victim) - weapon.forceMove(get_turf(weapon)) + UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING)) // have to unhook these here so they don't also register as having disappeared + + if(!weapon) + if(!victim.has_embedded_objects()) + victim.clear_alert("embeddedobject") + SEND_SIGNAL(victim, COMSIG_CLEAR_MOOD_EVENT, "embedded") qdel(src) + return + + if(weapon.unembedded()) // if it deleted itself + weapon = null + if(!victim.has_embedded_objects()) + victim.clear_alert("embeddedobject") + SEND_SIGNAL(victim, COMSIG_CLEAR_MOOD_EVENT, "embedded") + qdel(src) + return if(to_hands) victim.put_in_hands(weapon) @@ -211,25 +240,46 @@ qdel(src) -/// Items embedded/stuck to humans both check whether they randomly fall out (if applicable), as well as if the target mob and limb still exists. -/// Items harmfully embedded in humans have an additional check for random pain (if applicable) -/datum/component/embedded/proc/processHuman() - var/mob/living/carbon/human/victim = parent +/// Something deleted or moved our weapon while it was embedded, how rude! +/datum/component/embedded/proc/byeItemCarbon() + var/mob/living/carbon/victim = parent + limb.embedded_objects -= weapon + UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING)) - if(!victim || !L) // in case the victim and/or their limbs exploded (say, due to a sticky bomb) + if(victim) + to_chat(victim, "\The [weapon] that was embedded in your [limb.name] disappears!") + if(!victim.has_embedded_objects()) + victim.clear_alert("embeddedobject") + SEND_SIGNAL(victim, COMSIG_CLEAR_MOOD_EVENT, "embedded") + weapon = null + qdel(src) + + +/// Items embedded/stuck to carbons both check whether they randomly fall out (if applicable), as well as if the target mob and limb still exists. +/// Items harmfully embedded in carbons have an additional check for random pain (if applicable) +/datum/component/embedded/proc/processCarbon() + 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) if(victim.stat == DEAD) return - if(harmful && prob(pain_chance)) - var/damage = weapon.w_class * pain_mult - L.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) - to_chat(victim, "[weapon] embedded in your [L.name] hurts!") + var/damage = weapon.w_class * pain_mult + var/chance = pain_chance + if(pain_stam_pct && victim.stam_paralyzed) //if it's a less-lethal embed, give them a break if they're already stamcritted + chance *= 0.3 + damage *= 0.7 + + if(harmful && prob(chance)) + limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) + to_chat(victim, "[weapon] embedded in your [limb.name] hurts!") if(prob(fall_chance)) - fallOutHuman() + fallOutCarbon() + //////////////////////////////////////// @@ -299,6 +349,7 @@ if(do_after(us, 30, target = parent)) us.put_in_hands(weapon) + weapon.unembedded() qdel(src) @@ -306,4 +357,5 @@ /datum/component/embedded/proc/itemMoved() weapon.invisibility = initial(weapon.invisibility) weapon.visible_message("[weapon] falls loose from [parent].") + weapon.unembedded() qdel(src) diff --git a/code/datums/components/fantasy/suffixes.dm b/code/datums/components/fantasy/suffixes.dm index 0dd37edb6f4..4b2cea028a9 100644 --- a/code/datums/components/fantasy/suffixes.dm +++ b/code/datums/components/fantasy/suffixes.dm @@ -143,7 +143,7 @@ var/obj/projectile/picked_projectiletype = pickweight(weighted_projectile_types) var/obj/item/master = comp.parent - comp.appliedComponents += master.AddComponent(/datum/component/shrapnel, picked_projectiletype) + comp.appliedComponents += master.AddComponent(/datum/component/mirv, picked_projectiletype) return "[newName] of [initial(picked_projectiletype.name)] shrapnel" /datum/fantasy_affix/strength diff --git a/code/datums/components/shrapnel.dm b/code/datums/components/mirv.dm similarity index 69% rename from code/datums/components/shrapnel.dm rename to code/datums/components/mirv.dm index f549fbbb342..bf9413a5c04 100644 --- a/code/datums/components/shrapnel.dm +++ b/code/datums/components/mirv.dm @@ -1,33 +1,36 @@ -/datum/component/shrapnel +/datum/component/mirv var/projectile_type var/radius // shoots a projectile for every turf on this radius from the hit target var/override_projectile_range -/datum/component/shrapnel/Initialize(projectile_type, radius=1, override_projectile_range) - if(!isgun(parent) && !ismachinery(parent) && !isstructure(parent)) +/datum/component/mirv/Initialize(projectile_type, radius=1, override_projectile_range) + if(!isgun(parent) && !ismachinery(parent) && !isstructure(parent) && !isgrenade(parent)) return COMPONENT_INCOMPATIBLE src.projectile_type = projectile_type src.radius = radius src.override_projectile_range = override_projectile_range -/datum/component/shrapnel/RegisterWithParent() + if(isgrenade(parent)) + parent.AddComponent(/datum/component/pellet_cloud, projectile_type=projectile_type) + +/datum/component/mirv/RegisterWithParent() if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit) -/datum/component/shrapnel/UnregisterFromParent() +/datum/component/mirv/UnregisterFromParent() UnregisterSignal(parent, list(COMSIG_PROJECTILE_ON_HIT)) -/datum/component/shrapnel/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle) +/datum/component/mirv/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle) do_shrapnel(firer, target) -/datum/component/shrapnel/proc/do_shrapnel(mob/firer, atom/target) +/datum/component/mirv/proc/do_shrapnel(mob/firer, atom/target) if(radius < 1) return var/turf/target_turf = get_turf(target) for(var/turf/shootat_turf in RANGE_TURFS(radius, target) - RANGE_TURFS(radius-1, target)) - var/obj/projectile/P = new projectile_type(target_turf) + var/obj/projectile/P = new projectile_type(target_turf) //Shooting Code: P.range = radius+1 if(override_projectile_range) diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm new file mode 100644 index 00000000000..b0e9a3fcccc --- /dev/null +++ b/code/datums/components/pellet_cloud.dm @@ -0,0 +1,236 @@ +/* + * This component is used when you want to create a bunch of shrapnel or projectiles (say, shrapnel from a fragmentation grenade, or buckshot from a shotgun) from a central point, + * without necessarily printing a separate message for every single impact. This component should be instantiated right when you need it (like the moment of firing), then activated + * by signal. + * + * Pellet cloud currently works on two classes of sources: directed (ammo casings), and circular (grenades, landmines). + * -Directed: This means you're shooting multiple pellets, like buckshot. If an ammo casing is defined as having multiple pellets, it will automatically create a pellet cloud + * and call COMSIG_PELLET_CLOUD_INIT (see [/obj/item/ammo_casing/proc/fire_casing]). Thus, the only projectiles fired will be the ones fired here. + * The magnitude var controls how many pellets are created. + * -Circular: This results in a big spray of shrapnel flying all around the detonation point when the grenade fires COMSIG_GRENADE_PRIME or landmine triggers COMSIG_MINE_TRIGGERED. + * The magnitude var controls how big the detonation radius is (the bigger the magnitude, the more shrapnel is created). Grenades can be covered with bodies to reduce shrapnel output. + * + * Once all of the fired projectiles either hit a target or disappear due to ranging out/whatever else, we resolve the list of all the things we hit and print aggregate messages so we get + * one "You're hit by 6 buckshot pellets" vs 6x "You're hit by the buckshot blah blah" messages. + * + * Note that this is how all guns handle shooting ammo casings with multiple pellets, in case such a thing comes up. +*/ + +/datum/component/pellet_cloud + var/projectile_type /// What's the projectile path of the shrapnel we're shooting? + + var/num_pellets /// How many shrapnel projectiles are we responsible for tracking? May be reduced for grenades if someone dives on top of it. Defined by ammo casing for casings, derived from magnitude otherwise + var/radius = 4 /// For grenades/landmines, how big is the radius of turfs we're targeting? Note this does not effect the projectiles range, only how many we generate + + var/list/pellets = list() /// The list of pellets we're responsible for tracking, once these are all accounted for, we finalize. + var/list/targets_hit = list() /// An associated list with the atom hit as the key and how many pellets they've eaten for the value, for printing aggregate messages + var/list/bodies /// For grenades, any /mob/living's the grenade is moved onto, see [/datum/component/pellet_cloud/proc/handle_martyrs()] + var/list/purple_hearts /// For grenades, tracking people who die covering a grenade for achievement purposes, see [/datum/component/pellet_cloud/proc/handle_martyrs()] + + var/terminated /// how many pellets ranged out without hitting anything + var/hits /// how many pellets impacted something + + var/mob/living/shooter /// for if we're an ammo casing being fired + +/datum/component/pellet_cloud/Initialize(projectile_type=/obj/item/shrapnel, magnitude=5) + if(!isammocasing(parent) && !isgrenade(parent) && !islandmine(parent)) + return COMPONENT_INCOMPATIBLE + + if(magnitude < 1) + stack_trace("Invalid magnitude [magnitude] < 1 on pellet_cloud, parent: [parent]") + magnitude = 1 + + src.projectile_type = projectile_type + + if(isammocasing(parent)) + num_pellets = magnitude + else if(isgrenade(parent) || islandmine(parent)) + radius = magnitude + +/datum/component/pellet_cloud/Destroy(force, silent) + purple_hearts = null + pellets = null + targets_hit = null + bodies = null + return ..() + +/datum/component/pellet_cloud/RegisterWithParent() + if(isammocasing(parent)) + RegisterSignal(parent, COMSIG_PELLET_CLOUD_INIT, .proc/create_casing_pellets) + else if(isgrenade(parent)) + RegisterSignal(parent, COMSIG_GRENADE_ARMED, .proc/grenade_armed) + RegisterSignal(parent, COMSIG_GRENADE_PRIME, .proc/create_blast_pellets) + else if(islandmine(parent)) + RegisterSignal(parent, COMSIG_MINE_TRIGGERED, .proc/create_blast_pellets) + +/datum/component/pellet_cloud/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_PELLET_CLOUD_INIT, COMSIG_GRENADE_PRIME, COMSIG_GRENADE_ARMED, COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_UNCROSSED, COMSIG_MINE_TRIGGERED, COMSIG_ITEM_DROPPED)) + +/** + * create_casing_pellets() is for directed pellet clouds for ammo casings that have multiple pellets (buckshot and scatter lasers for instance) + * + * Honestly this is mostly just a rehash of [/obj/item/ammo_casing/proc/fire_casing()] for pellet counts > 1, except this lets us tamper with the pellets and hook onto them for tracking purposes. + * The arguments really don't matter, this proc is triggered by COMSIG_PELLET_CLOUD_INIT which is only for this really, it's just a big mess of the state vars we need for doing the stuff over here. + */ +/datum/component/pellet_cloud/proc/create_casing_pellets(obj/item/ammo_casing/A, atom/target, mob/living/user, fired_from, randomspread, spread, zone_override, params, distro) + shooter = user + var/targloc = get_turf(target) + if(!zone_override) + zone_override = shooter.zone_selected + + for(var/i in 1 to num_pellets) + A.ready_proj(target, user, SUPPRESSED_VERY, zone_override, fired_from) + if(distro) + if(randomspread) + spread = round((rand() - 0.5) * distro) + else //Smart spread + spread = round((i / num_pellets - 0.5) * distro) + + RegisterSignal(A.BB, COMSIG_PROJECTILE_SELF_ON_HIT, .proc/pellet_hit) + RegisterSignal(A.BB, list(COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PARENT_QDELETING), .proc/pellet_range) + pellets += A.BB + if(!A.throw_proj(target, targloc, shooter, params, spread)) + return + if(i != num_pellets) + A.newshot() + +/** + * create_blast_pellets() is for when we have a central point we want to shred the surroundings of with a ring of shrapnel, namely frag grenades and landmines. + * + * Note that grenades have extra handling for someone throwing themselves/being thrown on top of it, while landmines do not (obviously, it's a landmine!). See [/datum/component/pellet_cloud/proc/handle_martyrs()] + */ +/datum/component/pellet_cloud/proc/create_blast_pellets() + var/atom/A = parent + var/total_pellets_absorbed = 0 + + if(isgrenade(parent)) // handle_martyrs can reduce the radius and thus the number of pellets we produce if someone dives on top of a frag grenade + total_pellets_absorbed = handle_martyrs() // note that we can modify radius in this proc + + if(radius < 1) + return + + var/list/all_the_turfs_were_gonna_lacerate = RANGE_TURFS(radius, A) - RANGE_TURFS(radius-1, A) + num_pellets = all_the_turfs_were_gonna_lacerate.len + total_pellets_absorbed + + for(var/T in all_the_turfs_were_gonna_lacerate) + var/turf/shootat_turf = T + pew(shootat_turf) + +/** + * handle_martyrs() is used for grenades that shoot shrapnel to check if anyone threw themselves/were thrown on top of the grenade, thus absorbing a good chunk of the shrapnel + * + * Between the time the grenade is armed and the actual detonation, we set var/list/bodies to the list of mobs currently on the new tile, as if the grenade landed on top of them, tracking if any of them move off the tile and removing them from the "under" list + * Once the grenade detonates, handle_martyrs() is called and gets all the new mobs on the tile, and add the ones not in var/list/bodies to var/list/martyrs + * We then iterate through the martyrs and reduce the shrapnel magnitude for each mob on top of it, shredding each of them with some of the shrapnel they helped absorb. This can snuff out all of the shrapnel if there's enough bodies + * + * Note we track anyone who's alive and client'd when they get shredded in var/list/purple_hearts, for achievement checking later + */ +/datum/component/pellet_cloud/proc/handle_martyrs() + var/list/martyrs = list() + for(var/mob/living/body in get_turf(parent)) + if(!(body in bodies)) + martyrs += body // promoted from a corpse to a hero + + var/magnitude_absorbed + var/total_pellets_absorbed + + for(var/M in martyrs) + var/mob/living/martyr = M + if(radius > 4) + martyr.visible_message("[martyr] heroically covers \the [parent] with [martyr.p_their()] body, absorbing a load of the shrapnel!", "You heroically cover \the [parent] with your body, absorbing a load of the shrapnel!") + magnitude_absorbed += round(radius * 0.5) + else if(radius >= 2) + martyr.visible_message("[martyr] heroically covers \the [parent] with [martyr.p_their()] body, absorbing some of the shrapnel!", "You heroically cover \the [parent] with your body, absorbing some of the shrapnel!") + magnitude_absorbed += 2 + else + martyr.visible_message("[martyr] heroically covers \the [parent] with [martyr.p_their()] body, snuffing out the shrapnel!", "You heroically cover \the [parent] with your body, snuffing out the shrapnel!") + magnitude_absorbed = radius + + var/pellets_absorbed = (radius ** 2) - ((radius - magnitude_absorbed - 1) ** 2) + radius -= magnitude_absorbed + total_pellets_absorbed += round(pellets_absorbed/2) + + if(martyr.stat != DEAD && martyr.client) + LAZYADD(purple_hearts, martyr) + + for(var/i in 1 to round(pellets_absorbed/2)) + pew(martyr) + + if(radius < 1) + break + + return total_pellets_absorbed + +///One of our pellets hit something, record what it was and check if we're done (terminated == num_pellets) +/datum/component/pellet_cloud/proc/pellet_hit(obj/projectile/P, atom/movable/firer, atom/target, Angle) + pellets -= P + terminated++ + hits++ + targets_hit[target]++ + UnregisterSignal(P, list(COMSIG_PARENT_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT)) + if(terminated == num_pellets) + finalize() + +///One of our pellets disappeared due to hitting their max range (or just somehow got qdel'd), remove it from our list and check if we're done (terminated == num_pellets) +/datum/component/pellet_cloud/proc/pellet_range(obj/projectile/P) + pellets -= P + terminated++ + UnregisterSignal(P, list(COMSIG_PARENT_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT)) + if(terminated == num_pellets) + finalize() + +/// Minor convenience function for creating each shrapnel piece with circle explosions, mostly stolen from the MIRV component +/datum/component/pellet_cloud/proc/pew(atom/target, spread=0) + var/obj/projectile/P = new projectile_type(get_turf(parent)) + + //Shooting Code: + P.spread = spread + P.original = target + P.fired_from = parent + P.firer = parent // don't hit ourself that would be really annoying + P.permutated += parent // don't hit the target we hit already with the flak + P.suppressed = SUPPRESSED_VERY // set the projectiles to make no message so we can do our own aggregate message + P.preparePixelProjectile(target, parent) + RegisterSignal(P, COMSIG_PROJECTILE_SELF_ON_HIT, .proc/pellet_hit) + RegisterSignal(P, list(COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PARENT_QDELETING), .proc/pellet_range) + pellets += P + P.fire() + +///All of our pellets are accounted for, time to go target by target and tell them how many things they got hit by. +/datum/component/pellet_cloud/proc/finalize() + var/obj/projectile/P = projectile_type + var/proj_name = initial(P.name) + + for(var/atom/target in targets_hit) + var/num_hits = targets_hit[target] + if(num_hits > 1) + target.visible_message("[target] is hit by [num_hits] [proj_name]s!", null, null, COMBAT_MESSAGE_RANGE, target) + to_chat(target, "You're hit by [num_hits] [proj_name]s!") + else + target.visible_message("[target] is hit by a [proj_name]!", null, null, COMBAT_MESSAGE_RANGE, target) + to_chat(target, "You're hit by [num_hits] [proj_name]s!") + + for(var/M in purple_hearts) + var/mob/living/martyr = M + if(martyr.stat == DEAD && martyr.client) + martyr.client.give_award(/datum/award/achievement/misc/lookoutsir, martyr) + + qdel(parent) + qdel(src) + +/// Look alive, we're armed! Now we start watching to see if anyone's covering us +/datum/component/pellet_cloud/proc/grenade_armed() + LAZYINITLIST(bodies) + RegisterSignal(parent, list(COMSIG_ITEM_DROPPED, COMSIG_MOVABLE_MOVED), .proc/grenade_moved) + RegisterSignal(parent, COMSIG_MOVABLE_UNCROSSED, .proc/grenade_uncrossed) + +/// Our grenade has moved, reset var/list/bodies so we're "on top" of any mobs currently on the tile +/datum/component/pellet_cloud/proc/grenade_moved() + LAZYCLEARLIST(bodies) + for(var/mob/living/L in get_turf(parent)) + bodies += L + +/// Someone who was originally "under" the grenade has moved off the tile and is now eligible for being a martyr and "covering" it +/datum/component/pellet_cloud/proc/grenade_uncrossed(datum/source, atom/movable/AM) + bodies -= AM + diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm index 9980a2fc96f..bd6d1eb15f4 100644 --- a/code/datums/components/tackle.dm +++ b/code/datums/components/tackle.dm @@ -421,10 +421,10 @@ for(var/i = 0, i < speed, i++) var/obj/item/shard/shard = new /obj/item/shard(get_turf(user)) shard.embedding = list(embed_chance = 100, ignore_throwspeed_threshold = TRUE, impact_pain_mult=3, pain_chance=5) - shard.AddElement(/datum/element/embed, shard.embedding) + shard.updateEmbedding() user.hitby(shard, skipcatch = TRUE, hitpush = FALSE) shard.embedding = list() - shard.AddElement(/datum/element/embed, shard.embedding) + shard.updateEmbedding() W.obj_destruction() user.adjustStaminaLoss(10 * speed) user.Paralyze(30) diff --git a/code/datums/elements/embed.dm b/code/datums/elements/embed.dm index ab2c0d53706..fd74cc0e7c1 100644 --- a/code/datums/elements/embed.dm +++ b/code/datums/elements/embed.dm @@ -1,15 +1,12 @@ /* - The presence of this element allows an item to embed itself in a human or turf 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. + The presence of this element allows an item (or a projectile carrying an item) to embed itself in a human or turf 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. - This element is granted primarily to any /obj/item that has something in its /embedding var, which should be formatted as a list. If you wish to be able to - grant/rescind the ability for an item to embed (say, when activating and deactivating an edagger), you can do so in two ways: - - 1. Drop the throw_speed var below EMBED_THROWSPEED_THRESHOLD (object will still be able to otherwise embed if thrown at high speed by something else like a blast) - 2. Add/Remove the embed element as needed (won't be able to embed at all) + 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 or COMSIG_MOVABLE_IMPACT_ZONE check (against a closed turf or a carbon, respectively), 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. - */ #define STANDARD_WALL_HARDNESS 40 @@ -17,6 +14,7 @@ /datum/element/embed element_flags = ELEMENT_BESPOKE id_arg_index = 2 + var/initialized = FALSE /// whether we can skip assigning all the vars (since these are bespoke elements, we don't have to reset the vars every time we attach to something, we already know what we are!) // all of this stuff is explained in _DEFINES/combat.dm var/embed_chance @@ -30,102 +28,196 @@ var/jostle_chance var/jostle_pain_mult var/pain_stam_pct + var/payload_type + var/embed_chance_turf_mod -/datum/element/embed/Attach(datum/target, list/embedArgs) +/datum/element/embed/Attach(datum/target, embed_chance, fall_chance, pain_chance, pain_mult, remove_pain_mult, impact_pain_mult, rip_time, ignore_throwspeed_threshold, jostle_chance, jostle_pain_mult, pain_stam_pct, embed_chance_turf_mod, projectile_payload=/obj/item/shard) . = ..() - parseArgs(arglist(embedArgs)) - if(!isitem(target)) + if(!isitem(target) && !isprojectile(target)) return ELEMENT_INCOMPATIBLE - RegisterSignal(target, COMSIG_MOVABLE_IMPACT_ZONE, .proc/checkEmbedMob) - RegisterSignal(target, COMSIG_MOVABLE_IMPACT, .proc/checkEmbedOther) - RegisterSignal(target, COMSIG_ELEMENT_ATTACH, .proc/severancePackage) - RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/examined) + if(isitem(target)) + RegisterSignal(target, COMSIG_MOVABLE_IMPACT_ZONE, .proc/checkEmbedMob) + RegisterSignal(target, COMSIG_MOVABLE_IMPACT, .proc/checkEmbedOther) + RegisterSignal(target, COMSIG_ELEMENT_ATTACH, .proc/severancePackage) + RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/examined) + RegisterSignal(target, COMSIG_EMBED_TRY_FORCE, .proc/tryForceEmbed) + RegisterSignal(target, COMSIG_ITEM_DISABLE_EMBED, .proc/detachFromWeapon) + if(!initialized) + src.embed_chance = embed_chance + src.fall_chance = fall_chance + src.pain_chance = pain_chance + src.pain_mult = pain_mult + src.remove_pain_mult = remove_pain_mult + src.rip_time = rip_time + src.impact_pain_mult = impact_pain_mult + src.ignore_throwspeed_threshold = ignore_throwspeed_threshold + src.jostle_chance = jostle_chance + src.jostle_pain_mult = jostle_pain_mult + src.pain_stam_pct = pain_stam_pct + src.embed_chance_turf_mod = embed_chance_turf_mod + initialized = TRUE + else + payload_type = projectile_payload + RegisterSignal(target, COMSIG_PROJECTILE_SELF_ON_HIT, .proc/checkEmbedProjectile) -/datum/element/embed/Detach(obj/item/target) +/datum/element/embed/Detach(obj/target) . = ..() - UnregisterSignal(target, list(COMSIG_MOVABLE_IMPACT_ZONE, COMSIG_ELEMENT_ATTACH, COMSIG_MOVABLE_IMPACT, COMSIG_PARENT_EXAMINE)) + if(isitem(target)) + UnregisterSignal(target, list(COMSIG_MOVABLE_IMPACT_ZONE, COMSIG_ELEMENT_ATTACH, COMSIG_MOVABLE_IMPACT, COMSIG_PARENT_EXAMINE, COMSIG_EMBED_TRY_FORCE, COMSIG_ITEM_DISABLE_EMBED)) + else + UnregisterSignal(target, list(COMSIG_PROJECTILE_SELF_ON_HIT)) /// Checking to see if we're gonna embed into a human -/datum/element/embed/proc/checkEmbedMob(obj/item/weapon, mob/living/carbon/human/victim, hit_zone, datum/thrownthing/throwingdatum) - if(!istype(victim)) +/datum/element/embed/proc/checkEmbedMob(obj/item/weapon, mob/living/carbon/victim, hit_zone, datum/thrownthing/throwingdatum, forced=FALSE) + if(!istype(victim) || HAS_TRAIT(victim, TRAIT_PIERCEIMMUNE)) return - if((((throwingdatum ? throwingdatum.speed : weapon.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || ignore_throwspeed_threshold) && prob(embed_chance) && !HAS_TRAIT(victim, TRAIT_PIERCEIMMUNE)) - victim.AddComponent(/datum/component/embedded,\ - weapon,\ - throwingdatum,\ - embed_chance = embed_chance,\ - fall_chance = fall_chance,\ - pain_chance = pain_chance,\ - pain_mult = pain_mult,\ - remove_pain_mult = remove_pain_mult,\ - rip_time = rip_time,\ - ignore_throwspeed_threshold = ignore_throwspeed_threshold,\ - jostle_chance = jostle_chance,\ - jostle_pain_mult = jostle_pain_mult,\ - pain_stam_pct = pain_stam_pct) + var/actual_chance = embed_chance + if(!weapon.isEmbedHarmless()) // all the armor in the world won't save you from a kick me sign + var/armor = max(victim.run_armor_check(hit_zone, "bullet", silent=TRUE), victim.run_armor_check(hit_zone, "bomb", silent=TRUE)) // we'll be nice and take the better of bullet and bomb armor -/// We need the hit_zone if we're embedding into a human, so this proc only handled if we're embedding into a turf -/datum/element/embed/proc/checkEmbedOther(obj/item/weapon, turf/closed/hit, datum/thrownthing/throwingdatum) + if(armor) // we only care about armor penetration if there's actually armor to penetrate + var/pen_mod = -armor + weapon.armour_penetration // even a little bit of armor can make a big difference for shrapnel with large negative armor pen + 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("[weapon] bounces off [victim]'s armor!", "[weapon] bounces off your armor!", vision_distance = COMBAT_MESSAGE_RANGE) + return + + var/roll_embed = prob(actual_chance) + var/pass = forced || ((((throwingdatum ? throwingdatum.speed : weapon.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || ignore_throwspeed_threshold) && roll_embed) + if(!pass) + return + + var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || pick(victim.bodyparts) + victim.AddComponent(/datum/component/embedded,\ + weapon,\ + throwingdatum,\ + part = limb,\ + embed_chance = embed_chance,\ + fall_chance = fall_chance,\ + pain_chance = pain_chance,\ + pain_mult = pain_mult,\ + remove_pain_mult = remove_pain_mult,\ + rip_time = rip_time,\ + ignore_throwspeed_threshold = ignore_throwspeed_threshold,\ + jostle_chance = jostle_chance,\ + jostle_pain_mult = jostle_pain_mult,\ + pain_stam_pct = pain_stam_pct,\ + embed_chance_turf_mod = embed_chance_turf_mod) + + return TRUE + +/// We need the hit_zone if we're embedding into a human, so this proc only handles if we're embedding into a turf +/datum/element/embed/proc/checkEmbedOther(obj/item/weapon, turf/closed/hit, datum/thrownthing/throwingdatum, forced=FALSE) if(!istype(hit)) return - var/chance = embed_chance + var/chance = embed_chance + embed_chance_turf_mod if(iswallturf(hit)) var/turf/closed/wall/W = hit chance += 2 * (W.hardness - STANDARD_WALL_HARDNESS) - if((((throwingdatum ? throwingdatum.speed : weapon.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || ignore_throwspeed_threshold) && prob(chance)) - hit.AddComponent(/datum/component/embedded,\ - weapon,\ - throwingdatum,\ - embed_chance = embed_chance,\ - fall_chance = fall_chance,\ - pain_chance = pain_chance,\ - pain_mult = pain_mult,\ - remove_pain_mult = remove_pain_mult,\ - rip_time = rip_time,\ - ignore_throwspeed_threshold = ignore_throwspeed_threshold,\ - jostle_chance = jostle_chance,\ - jostle_pain_mult = jostle_pain_mult,\ - pain_stam_pct = pain_stam_pct) + if(!forced && chance <= 0 || embed_chance_turf_mod <= -100) + return -/datum/element/embed/proc/parseArgs(embed_chance = EMBED_CHANCE, - fall_chance = EMBEDDED_ITEM_FALLOUT, - pain_chance = EMBEDDED_PAIN_CHANCE, - pain_mult = EMBEDDED_PAIN_MULTIPLIER, - remove_pain_mult = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER, - rip_time = EMBEDDED_UNSAFE_REMOVAL_TIME, - impact_pain_mult = EMBEDDED_IMPACT_PAIN_MULTIPLIER, - ignore_throwspeed_threshold = FALSE, - jostle_chance = EMBEDDED_JOSTLE_CHANCE, - jostle_pain_mult = EMBEDDED_JOSTLE_PAIN_MULTIPLIER, - pain_stam_pct = EMBEDDED_PAIN_STAM_PCT) + var/pass = ((((throwingdatum ? throwingdatum.speed : weapon.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || ignore_throwspeed_threshold) && prob(chance)) + if(!pass) + return - src.embed_chance = embed_chance - src.fall_chance = fall_chance - src.pain_chance = pain_chance - src.pain_mult = pain_mult - src.remove_pain_mult = remove_pain_mult - src.impact_pain_mult = impact_pain_mult - src.rip_time = rip_time - src.ignore_throwspeed_threshold = ignore_throwspeed_threshold - src.jostle_chance = jostle_chance - src.jostle_pain_mult = jostle_pain_mult - src.pain_stam_pct = pain_stam_pct + hit.AddComponent(/datum/component/embedded,\ + weapon,\ + throwingdatum,\ + embed_chance = embed_chance,\ + fall_chance = fall_chance,\ + pain_chance = pain_chance,\ + pain_mult = pain_mult,\ + remove_pain_mult = remove_pain_mult,\ + rip_time = rip_time,\ + ignore_throwspeed_threshold = ignore_throwspeed_threshold,\ + jostle_chance = jostle_chance,\ + jostle_pain_mult = jostle_pain_mult,\ + pain_stam_pct = pain_stam_pct,\ + embed_chance_turf_mod = embed_chance_turf_mod) + + return TRUE ///A different embed element has been attached, so we'll detach and let them handle things /datum/element/embed/proc/severancePackage(obj/item/weapon, datum/element/E) 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/detachFromWeapon(obj/weapon) + Detach(weapon) + +///Someone inspected our embeddable item /datum/element/embed/proc/examined(obj/item/I, mob/user, list/examine_list) - if(!pain_mult && !jostle_pain_mult) + if(I.isEmbedHarmless()) examine_list += "[I] feels sticky, and could probably get stuck to someone if thrown properly!" else examine_list += "[I] has a fine point, and could probably embed in someone if thrown properly!" + +/** + * checkEmbedProjectile() is what we get when a projectile with a defined shrapnel_type impacts a target. + * + * If we hit a valid target (carbon or closed turf), we create the shrapnel_type object and immediately call tryEmbed() on it, targeting what we impacted. That will lead + * it to call tryForceEmbed() on its own embed element (it's out of our hands here, our projectile is done), where it will run through all the checks it needs to. + */ +/datum/element/embed/proc/checkEmbedProjectile(obj/projectile/P, atom/movable/firer, atom/hit) + if(!iscarbon(hit) && !isclosedturf(hit)) + Detach(P) + return // we don't care + + var/obj/item/payload = new payload_type(get_turf(hit)) + var/did_embed + if(iscarbon(hit)) + var/mob/living/carbon/C = hit + var/obj/item/bodypart/limb = C.get_bodypart(C.check_limb_hit(P.def_zone)) + did_embed = payload.tryEmbed(limb) + else + did_embed = payload.tryEmbed(hit) + + if(!did_embed) + payload.failedEmbed() + Detach(P) + +/** + * tryForceEmbed() 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 (which likely represents said bullet), and possibly + * AGAIN if we actually embed. This way, we save on at least one message. Runs the standard embed checks on the mob/turf. + * + * Arguments: + * * I- what we're trying to embed, obviously + * * target- what we're trying to shish-kabob, either a bodypart, a carbon, or a closed turf + * * 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/tryForceEmbed(obj/item/I, atom/target, hit_zone, forced=FALSE) + var/obj/item/bodypart/limb + var/mob/living/carbon/C + var/turf/closed/T + + if(!forced && !prob(embed_chance)) + return + + if(iscarbon(target)) + C = target + if(!hit_zone) + limb = pick(C.bodyparts) + hit_zone = limb.body_zone + else if(isbodypart(target)) + limb = target + C = limb.owner + else if(isclosedturf(target)) + T = target + + if(C) + return checkEmbedMob(I, C, hit_zone, forced=TRUE) + else if(T) + return checkEmbedOther(I, T, forced=TRUE) diff --git a/code/datums/embedding_behavior.dm b/code/datums/embedding_behavior.dm deleted file mode 100644 index 449fb3ba1cc..00000000000 --- a/code/datums/embedding_behavior.dm +++ /dev/null @@ -1,73 +0,0 @@ -#define EMBEDID "embed-[embed_chance]-[fall_chance]-[pain_chance]-[pain_mult]-[fall_pain_mult]-[impact_pain_mult]-[rip_pain_mult]-[rip_time]-[ignore_throwspeed_threshold]-[jostle_chance]-[jostle_pain_mult]-[pain_stam_pct]" - -/proc/getEmbeddingBehavior(embed_chance = EMBED_CHANCE, - fall_chance = EMBEDDED_ITEM_FALLOUT, - pain_chance = EMBEDDED_PAIN_CHANCE, - pain_mult = EMBEDDED_PAIN_MULTIPLIER, - fall_pain_mult = EMBEDDED_FALL_PAIN_MULTIPLIER, - impact_pain_mult = EMBEDDED_IMPACT_PAIN_MULTIPLIER, - rip_pain_mult = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER, - rip_time = EMBEDDED_UNSAFE_REMOVAL_TIME, - ignore_throwspeed_threshold = FALSE, - jostle_chance = EMBEDDED_JOSTLE_CHANCE, - jostle_pain_mult = EMBEDDED_JOSTLE_PAIN_MULTIPLIER, - pain_stam_pct = EMBEDDED_PAIN_STAM_PCT) - . = locate(EMBEDID) - if (!.) - . = new /datum/embedding_behavior(embed_chance, fall_chance, pain_chance, pain_mult, fall_pain_mult, impact_pain_mult, rip_pain_mult, rip_time, ignore_throwspeed_threshold, jostle_chance, jostle_pain_mult, pain_stam_pct) - -/datum/embedding_behavior - var/embed_chance - var/fall_chance - var/pain_chance - var/pain_mult //The coefficient of multiplication for the damage this item does while embedded (this*w_class) - var/fall_pain_mult //The coefficient of multiplication for the damage this item does when falling out of a limb (this*w_class) - var/impact_pain_mult //The coefficient of multiplication for the damage this item does when first embedded (this*w_class) - var/rip_pain_mult //The coefficient of multiplication for the damage removing this without surgery causes (this*w_class) - var/rip_time //A time in ticks, multiplied by the w_class. - var/ignore_throwspeed_threshold //if we don't give a damn about EMBED_THROWSPEED_THRESHOLD - var/jostle_chance //Chance to cause pain every time the victim moves (1/2 chance if they're walking or crawling) - var/jostle_pain_mult //The coefficient of multiplication for the damage when jostle damage is applied (this*w_class) - var/pain_stam_pct //Percentage of all pain damage dealt as stamina instead of brute (none by default) - -/datum/embedding_behavior/New(embed_chance = EMBED_CHANCE, - fall_chance = EMBEDDED_ITEM_FALLOUT, - pain_chance = EMBEDDED_PAIN_CHANCE, - pain_mult = EMBEDDED_PAIN_MULTIPLIER, - fall_pain_mult = EMBEDDED_FALL_PAIN_MULTIPLIER, - impact_pain_mult = EMBEDDED_IMPACT_PAIN_MULTIPLIER, - rip_pain_mult = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER, - rip_time = EMBEDDED_UNSAFE_REMOVAL_TIME, - ignore_throwspeed_threshold = FALSE, - jostle_chance = EMBEDDED_JOSTLE_CHANCE, - jostle_pain_mult = EMBEDDED_JOSTLE_PAIN_MULTIPLIER, - pain_stam_pct = EMBEDDED_PAIN_STAM_PCT) - src.embed_chance = embed_chance - src.fall_chance = fall_chance - src.pain_chance = pain_chance - src.pain_mult = pain_mult - src.fall_pain_mult = fall_pain_mult - src.impact_pain_mult = impact_pain_mult - src.rip_pain_mult = rip_pain_mult - src.rip_time = rip_time - src.ignore_throwspeed_threshold = ignore_throwspeed_threshold - src.jostle_chance = jostle_chance - src.jostle_pain_mult = jostle_pain_mult - src.pain_stam_pct = pain_stam_pct - tag = EMBEDID - -/datum/embedding_behavior/proc/setRating(embed_chance, fall_chance, pain_chance, pain_mult, fall_pain_mult, impact_pain_mult, rip_pain_mult, rip_time, ignore_throwspeed_threshold) - return getEmbeddingBehavior((isnull(embed_chance) ? src.embed_chance : embed_chance),\ - (isnull(fall_chance) ? src.fall_chance : fall_chance),\ - (isnull(pain_chance) ? src.pain_chance : pain_chance),\ - (isnull(pain_mult) ? src.pain_mult : pain_mult),\ - (isnull(fall_pain_mult) ? src.fall_pain_mult : fall_pain_mult),\ - (isnull(impact_pain_mult) ? src.impact_pain_mult : impact_pain_mult),\ - (isnull(rip_pain_mult) ? src.rip_pain_mult : rip_pain_mult),\ - (isnull(rip_time) ? src.rip_time : rip_time),\ - (isnull(ignore_throwspeed_threshold) ? src.ignore_throwspeed_threshold : ignore_throwspeed_threshold),\ - (isnull(jostle_chance) ? src.jostle_chance : jostle_chance),\ - (isnull(jostle_pain_mult) ? src.jostle_pain_mult : jostle_pain_mult),\ - (isnull(pain_stam_pct) ? src.pain_stam_pct : pain_stam_pct)) - -#undef EMBEDID diff --git a/code/datums/mutations/actions.dm b/code/datums/mutations/actions.dm index fad4dc587ff..e5b3e0e5290 100644 --- a/code/datums/mutations/actions.dm +++ b/code/datums/mutations/actions.dm @@ -371,14 +371,9 @@ var/obj/item/bodypart/L = spikey.checkembedded() - L.embedded_objects -= spikey //this is where it would deal damage, if it transfers chems it removes itself so no damage spikey.forceMove(get_turf(L)) transfered.visible_message("[spikey] falls out of [transfered]!") - if(!transfered.has_embedded_objects()) - transfered.clear_alert("embeddedobject") - SEND_SIGNAL(transfered, COMSIG_CLEAR_MOOD_EVENT, "embedded") - spikey.unembedded() //spider webs /datum/mutation/human/webbing diff --git a/code/game/atoms.dm b/code/game/atoms.dm index ba13f2a9bf0..31b94535f41 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -16,6 +16,13 @@ ///Intearaction flags var/interaction_flags_atom = NONE + var/flags_ricochet = NONE + + ///When a projectile tries to ricochet off this atom, the projectile ricochet chance is multiplied by this + var/ricochet_chance_mod = 1 + ///When a projectile ricochets off this atom, it deals the normal damage * this modifier to this atom + var/ricochet_damage_mod = 0.33 + ///Reagents holder var/datum/reagents/reagents = null @@ -231,7 +238,19 @@ return ..() /atom/proc/handle_ricochet(obj/projectile/P) - return + var/turf/p_turf = get_turf(P) + var/face_direction = get_dir(src, p_turf) + var/face_angle = dir2angle(face_direction) + var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180)) + var/a_incidence_s = abs(incidence_s) + if(a_incidence_s > 90 && a_incidence_s < 270) + return FALSE + if((P.flag in list("bullet", "bomb")) && P.ricochet_incidence_leeway) + if((a_incidence_s < 90 && a_incidence_s < 90 - P.ricochet_incidence_leeway) || (a_incidence_s > 270 && a_incidence_s -270 > P.ricochet_incidence_leeway)) + return + var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) + P.setAngle(new_angle_s) + return TRUE ///Can the mover object pass this atom, while heading for the target turf /atom/proc/CanPass(atom/movable/mover, turf/target) diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index 84e623360e0..101a8234bb6 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -92,6 +92,8 @@ Class Procs: pressure_resistance = 15 max_integrity = 200 layer = BELOW_OBJ_LAYER //keeps shit coming out of the machine from ending up underneath it. + flags_ricochet = RICOCHET_HARD + ricochet_chance_mod = 0.3 anchored = TRUE interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT diff --git a/code/game/machinery/deployable.dm b/code/game/machinery/deployable.dm index a9573e2a1ee..ed9d16a5e6e 100644 --- a/code/game/machinery/deployable.dm +++ b/code/game/machinery/deployable.dm @@ -168,6 +168,7 @@ to_chat(user, "[src] is now in [mode] mode.") /obj/item/grenade/barrier/prime() + . = ..() new /obj/structure/barricade/security(get_turf(src.loc)) switch(mode) if(VERTICAL) diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 004af45df28..f5b7d2b2440 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -12,6 +12,7 @@ armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 70) CanAtmosPass = ATMOS_PASS_DENSITY flags_1 = PREVENT_CLICK_UNDER_1 + ricochet_chance_mod = 0.8 damage_deflection = 10 interaction_flags_atom = INTERACT_ATOM_UI_INTERACT diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm index 1193ce213c2..119a0893d47 100644 --- a/code/game/objects/effects/mines.dm +++ b/code/game/objects/effects/mines.dm @@ -28,6 +28,7 @@ s.set_up(3, 1, src) s.start() mineEffect(victim) + SEND_SIGNAL(src, COMSIG_MINE_TRIGGERED) triggered = 1 qdel(src) @@ -42,11 +43,22 @@ /obj/effect/mine/explosive/mineEffect(mob/victim) explosion(loc, range_devastation, range_heavy, range_light, range_flash) - /obj/effect/mine/stun name = "stun mine" var/stun_time = 80 +/obj/effect/mine/shrapnel + name = "shrapnel mine" + var/shrapnel_type = /obj/projectile/bullet/shrapnel + var/shrapnel_magnitude = 3 + +/obj/effect/mine/shrapnel/mineEffect(mob/victim) + AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_magnitude) + +/obj/effect/mine/shrapnel/sting + name = "stinger mine" + shrapnel_type = /obj/projectile/bullet/pellet/stingball + /obj/effect/mine/stun/mineEffect(mob/living/victim) if(isliving(victim)) victim.Paralyze(stun_time) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index cdc84541f51..12ff3c5d8a4 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -205,19 +205,18 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb if(B && B.loc == loc) qdel(src) -/obj/item/ComponentInitialize()// this proc says it's for initializing components, but we're initializing elements too because it's you and me against the world >:) +/obj/item/ComponentInitialize() . = ..() + // this proc says it's for initializing components, but we're initializing elements too because it's you and me against the world >:) + if(!LAZYLEN(embedding)) + if(GLOB.embedpocalypse) + embedding = EMBED_POINTY + name = "pointy [name]" + else if(GLOB.stickpocalypse) + embedding = EMBED_HARMLESS + name = "sticky [name]" - if(embedding) - AddElement(/datum/element/embed, embedding) - else if(GLOB.embedpocalypse) - embedding = EMBED_POINTY - AddElement(/datum/element/embed, embedding) - name = "pointy [name]" - else if(GLOB.stickpocalypse) - embedding = EMBED_HARMLESS - AddElement(/datum/element/embed, embedding) - name = "sticky [name]" + updateEmbedding() if(GLOB.rpg_loot_items) AddComponent(/datum/component/fantasy) @@ -920,7 +919,9 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb return /obj/item/proc/unembedded() - return + if(item_flags & DROPDEL) + QDEL_NULL(src) + return TRUE /obj/item/proc/canStrip(mob/stripper, mob/owner) SHOULD_BE_PURE(TRUE) @@ -929,13 +930,17 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb /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 explicitly define the pain multiplier and jostle pain mult to be 0? If so, return true. - */ -/obj/item/proc/is_embed_harmless() +///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/isEmbedHarmless() if(embedding) return !isnull(embedding["pain_mult"]) && !isnull(embedding["jostle_pain_mult"]) && embedding["pain_mult"] == 0 && embedding["jostle_pain_mult"] == 0 +///In case we want to do something special (like self delete) upon failing to embed in something, return true +/obj/item/proc/failedEmbed() + if(item_flags & DROPDEL) + QDEL_NULL(src) + return TRUE + ///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)) @@ -945,3 +950,47 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb to_chat(user, "You set [src] down gently on the ground.") 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 targetting 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. + * + * Arguments: + * * target- Either a body part, a carbon, or a closed turf. What are we hitting? + * * forced- Do we want this to go through 100%? + */ +/obj/item/proc/tryEmbed(atom/target, forced=FALSE, silent=FALSE) + if(!isbodypart(target) && !iscarbon(target) && !isclosedturf(target)) + return + if(!forced && !LAZYLEN(embedding)) + return + + if(SEND_SIGNAL(src, COMSIG_EMBED_TRY_FORCE, target, forced, silent)) + return TRUE + 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 + +///For when you want to add/update the embedding on an item. Uses the vars in [/obj/item/embedding], and defaults to config values for values that aren't set. Will automatically detach previous embed elements on this item. +/obj/item/proc/updateEmbedding() + if(!islist(embedding) || !LAZYLEN(embedding)) + return + + AddElement(/datum/element/embed,\ + embed_chance = (!isnull(embedding["embed_chance"]) ? embedding["embed_chance"] : EMBED_CHANCE),\ + fall_chance = (!isnull(embedding["fall_chance"]) ? embedding["fall_chance"] : EMBEDDED_ITEM_FALLOUT),\ + pain_chance = (!isnull(embedding["pain_chance"]) ? embedding["pain_chance"] : EMBEDDED_PAIN_CHANCE),\ + pain_mult = (!isnull(embedding["pain_mult"]) ? embedding["pain_mult"] : EMBEDDED_PAIN_MULTIPLIER),\ + remove_pain_mult = (!isnull(embedding["remove_pain_mult"]) ? embedding["remove_pain_mult"] : EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER),\ + rip_time = (!isnull(embedding["rip_time"]) ? embedding["rip_time"] : EMBEDDED_UNSAFE_REMOVAL_TIME),\ + ignore_throwspeed_threshold = (!isnull(embedding["ignore_throwspeed_threshold"]) ? embedding["ignore_throwspeed_threshold"] : FALSE),\ + impact_pain_mult = (!isnull(embedding["impact_pain_mult"]) ? embedding["impact_pain_mult"] : EMBEDDED_IMPACT_PAIN_MULTIPLIER),\ + jostle_chance = (!isnull(embedding["jostle_chance"]) ? embedding["jostle_chance"] : EMBEDDED_JOSTLE_CHANCE),\ + jostle_pain_mult = (!isnull(embedding["jostle_pain_mult"]) ? embedding["jostle_pain_mult"] : EMBEDDED_JOSTLE_PAIN_MULTIPLIER),\ + pain_stam_pct = (!isnull(embedding["pain_stam_pct"]) ? embedding["pain_stam_pct"] : EMBEDDED_PAIN_STAM_PCT),\ + embed_chance_turf_mod = (!isnull(embedding["embed_chance_turf_mod"]) ? embedding["embed_chance_turf_mod"] : EMBED_CHANCE_TURF_MOD)) + return TRUE diff --git a/code/game/objects/items/grenades/antigravity.dm b/code/game/objects/items/grenades/antigravity.dm index a4bc207be03..313b91acd71 100644 --- a/code/game/objects/items/grenades/antigravity.dm +++ b/code/game/objects/items/grenades/antigravity.dm @@ -8,10 +8,11 @@ var/duration = 300 /obj/item/grenade/antigravity/prime() + . = ..() update_mob() for(var/turf/T in view(range,src)) T.AddElement(/datum/element/forced_gravity, forced_value) addtimer(CALLBACK(T, /datum/.proc/_RemoveElement, list(forced_value)), duration) - qdel(src) + resolve() diff --git a/code/game/objects/items/grenades/chem_grenade.dm b/code/game/objects/items/grenades/chem_grenade.dm index ae5fc2dc426..c39d9783710 100644 --- a/code/game/objects/items/grenades/chem_grenade.dm +++ b/code/game/objects/items/grenades/chem_grenade.dm @@ -178,6 +178,7 @@ if(stage != GRENADE_READY) return + . = ..() var/list/datum/reagents/reactants = list() for(var/obj/item/reagent_containers/glass/G in beakers) reactants += G.reagents diff --git a/code/game/objects/items/grenades/clusterbuster.dm b/code/game/objects/items/grenades/clusterbuster.dm index 2e052aefa03..6e4687d72c3 100644 --- a/code/game/objects/items/grenades/clusterbuster.dm +++ b/code/game/objects/items/grenades/clusterbuster.dm @@ -15,6 +15,7 @@ var/segment_chance = 35 /obj/item/grenade/clusterbuster/prime() + . = ..() update_mob() var/numspawned = rand(min_spawned,max_spawned) var/again = 0 @@ -29,7 +30,7 @@ new payload_spawner(drop_location(), payload, numspawned)//Launches payload playsound(src, prime_sound, 75, TRUE, -3) - qdel(src) + resolve() ////////////////////// //Clusterbang segment @@ -62,7 +63,7 @@ /obj/item/grenade/clusterbuster/segment/prime() new payload_spawner(drop_location(), payload, rand(min_spawned,max_spawned)) playsound(src, prime_sound, 75, TRUE, -3) - qdel(src) + resolve() ////////////////////////////////// //The payload spawner effect diff --git a/code/game/objects/items/grenades/emgrenade.dm b/code/game/objects/items/grenades/emgrenade.dm index 936258344df..4ed36176dc4 100644 --- a/code/game/objects/items/grenades/emgrenade.dm +++ b/code/game/objects/items/grenades/emgrenade.dm @@ -5,6 +5,7 @@ item_state = "emp" /obj/item/grenade/empgrenade/prime() + . = ..() update_mob() empulse(src, 4, 10) - qdel(src) + resolve() diff --git a/code/game/objects/items/grenades/festive.dm b/code/game/objects/items/grenades/festive.dm index a7ae03bd125..af68f7566a3 100644 --- a/code/game/objects/items/grenades/festive.dm +++ b/code/game/objects/items/grenades/festive.dm @@ -109,9 +109,10 @@ obj/item/grenade/firecracker/preprime(mob/user, delayoverride, msg = TRUE, volum addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) /obj/item/grenade/firecracker/prime() + . = ..() update_mob() var/explosion_loc = get_turf(src) - qdel(src) + resolve() explosion(explosion_loc,-1,-1,2) diff --git a/code/game/objects/items/grenades/flashbang.dm b/code/game/objects/items/grenades/flashbang.dm index 0be2f717907..cbce3cff51f 100644 --- a/code/game/objects/items/grenades/flashbang.dm +++ b/code/game/objects/items/grenades/flashbang.dm @@ -7,6 +7,7 @@ var/flashbang_range = 7 //how many tiles away the mob will be stunned. /obj/item/grenade/flashbang/prime() + . = ..() update_mob() var/flashbang_turf = get_turf(src) if(!flashbang_turf) @@ -16,7 +17,7 @@ new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_WHITE, (flashbang_range + 2), 4, 2) for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf)) bang(get_turf(M), M) - qdel(src) + resolve() /obj/item/grenade/flashbang/proc/bang(turf/T , mob/living/M) if(M.stat == DEAD) //They're dead! @@ -39,3 +40,91 @@ M.Paralyze(5) M.Knockdown(30) M.soundbang_act(1, max(200/max(1,distance), 60), rand(0, 5)) + +/obj/item/grenade/stingbang + name = "stingbang" + icon_state = "timeg" + item_state = "flashbang" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + var/flashbang_range = 1 //how many tiles away the mob will be stunned. + shrapnel_type = /obj/projectile/bullet/pellet/stingball + shrapnel_radius = 5 + custom_premium_price = 700 // mostly gotten through cargo, but throw in one for the sec vendor ;) + +/obj/item/grenade/stingbang/mega + name = "mega stingbang" + shrapnel_type = /obj/projectile/bullet/pellet/stingball/mega + shrapnel_radius = 12 + +/obj/item/grenade/stingbang/prime() + if(iscarbon(loc)) + var/mob/living/carbon/C = loc + var/obj/item/bodypart/B = C.get_holding_bodypart_of_item(src) + if(B) + C.visible_message("[src] goes off in [C]'s hand, blowing [C.p_their()] [B.name] to bloody shreds!", "[src] goes off in your hand, blowing your [B.name] to bloody shreds!") + B.dismember() + + . = ..() + update_mob() + var/flashbang_turf = get_turf(src) + if(!flashbang_turf) + return + do_sparks(rand(5, 9), FALSE, src) + playsound(flashbang_turf, 'sound/weapons/flashbang.ogg', 50, TRUE, 8, 0.9) + new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_WHITE, (flashbang_range + 2), 2, 1) + for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf)) + pop(get_turf(M), M) + resolve() + +/obj/item/grenade/stingbang/proc/pop(turf/T , mob/living/M) + if(M.stat == DEAD) //They're dead! + return + M.show_message("POP", MSG_AUDIBLE) + var/distance = max(0,get_dist(get_turf(src),T)) +//Flash + if(M.flash_act(affect_silicon = 1)) + M.Paralyze(max(10/max(1,distance), 5)) + M.Knockdown(max(100/max(1,distance), 60)) + +//Bang + if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this. + M.Paralyze(20) + M.Knockdown(200) + M.soundbang_act(1, 200, 10, 15) + if(M.apply_damages(10, 10)) + to_chat(M, "The blast from \the [src] bruises and burns you!") + + // only checking if they're on top of the tile, cause being one tile over will be its own punishment + +// Grenade that releases more shrapnel the more times you use it in hand between priming and detonation (sorta like the 9bang from MW3), for admin goofs +/obj/item/grenade/primer + name = "rotfrag grenade" + desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases shrapnel shards." + icon_state = "timeg" + item_state = "flashbang" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + var/rots_per_mag = 3 /// how many times we need to "rotate" the charge in hand per extra tile of magnitude + shrapnel_type = /obj/projectile/bullet/shrapnel + var/rots = 1 /// how many times we've "rotated" the charge + +/obj/item/grenade/primer/attack_self(mob/user) + . = ..() + if(active) + user.playsound_local(user, 'sound/misc/box_deploy.ogg', 50, TRUE) + rots++ + user.changeNext_move(CLICK_CD_RAPID) + +/obj/item/grenade/primer/prime() + shrapnel_radius = round(rots / rots_per_mag) + . = ..() + resolve() + +/obj/item/grenade/primer/stingbang + name = "rotsting" + desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases stingballs." + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + rots_per_mag = 2 + shrapnel_type = /obj/projectile/bullet/pellet/stingball diff --git a/code/game/objects/items/grenades/ghettobomb.dm b/code/game/objects/items/grenades/ghettobomb.dm index e7e13460668..7eb71d05db7 100644 --- a/code/game/objects/items/grenades/ghettobomb.dm +++ b/code/game/objects/items/grenades/ghettobomb.dm @@ -64,9 +64,10 @@ preprime(user, null, FALSE) /obj/item/grenade/iedcasing/prime() //Blowing that can up + . = ..() update_mob() explosion(src.loc,-1,-1,2, flame_range = 4) // small explosion, plus a very large fireball. - qdel(src) + resolve() /obj/item/grenade/iedcasing/change_det_time() return //always be random. diff --git a/code/game/objects/items/grenades/grenade.dm b/code/game/objects/items/grenades/grenade.dm index 4d4277aef9a..641b0f65627 100644 --- a/code/game/objects/items/grenades/grenade.dm +++ b/code/game/objects/items/grenades/grenade.dm @@ -18,6 +18,22 @@ var/display_timer = 1 var/clumsy_check = GRENADE_CLUMSY_FUMBLE var/sticky = FALSE + // I moved the explosion vars and behavior to base grenades because we want all grenades to call [/obj/item/grenade/proc/prime] so we can send COMSIG_GRENADE_PRIME + ///how big of a devastation explosion radius on prime + var/ex_dev = 0 + ///how big of a heavy explosion radius on prime + var/ex_heavy = 0 + ///how big of a light explosion radius on prime + var/ex_light = 0 + ///how big of a flame explosion radius on prime + var/ex_flame = 0 + + // dealing with creating a [/datum/component/pellet_cloud] on prime + /// if set, will spew out projectiles of this type + var/shrapnel_type + /// the higher this number, the more projectiles are created as shrapnel + var/shrapnel_radius + var/shrapnel_initialized /obj/item/grenade/suicide_act(mob/living/carbon/user) user.visible_message("[user] primes [src], then eats it! It looks like [user.p_theyre()] trying to commit suicide!") @@ -80,12 +96,23 @@ add_fingerprint(user) if(msg) to_chat(user, "You prime [src]! [capitalize(DisplayTimeText(det_time))]!") + if(shrapnel_type && shrapnel_radius) + shrapnel_initialized = TRUE + AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_radius) playsound(src, 'sound/weapons/armbomb.ogg', volume, TRUE) active = TRUE icon_state = initial(icon_state) + "_active" + SEND_SIGNAL(src, COMSIG_GRENADE_ARMED, det_time, delayoverride) addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) /obj/item/grenade/proc/prime() + if(shrapnel_type && shrapnel_radius && !shrapnel_initialized) // add a second check for adding the component in case whatever triggered the grenade went straight to prime (badminnery for example) + shrapnel_initialized = TRUE + AddComponent(/datum/component/pellet_cloud, projectile_type=shrapnel_type, magnitude=shrapnel_radius) + + SEND_SIGNAL(src, COMSIG_GRENADE_PRIME) + if(ex_dev || ex_heavy || ex_light || ex_flame) + explosion(loc, ex_dev, ex_heavy, ex_light, flame_range = ex_flame) /obj/item/grenade/proc/update_mob() if(ismob(loc)) @@ -143,3 +170,10 @@ . = ..() if(active) user.throw_item(target) + +/// Don't call qdel() directly on the grenade after it booms, call this instead so it can still resolve its pellet_cloud component if it has shrapnel, then the component will qdel it +/obj/item/grenade/proc/resolve() + if(shrapnel_type) + moveToNullspace() + else + qdel(src) diff --git a/code/game/objects/items/grenades/hypno.dm b/code/game/objects/items/grenades/hypno.dm index 5d7652bb303..d9c954fb92d 100644 --- a/code/game/objects/items/grenades/hypno.dm +++ b/code/game/objects/items/grenades/hypno.dm @@ -8,6 +8,7 @@ var/flashbang_range = 7 /obj/item/grenade/hypnotic/prime() + . = ..() update_mob() var/flashbang_turf = get_turf(src) if(!flashbang_turf) @@ -17,7 +18,7 @@ new /obj/effect/dummy/lighting_obj (flashbang_turf, LIGHT_COLOR_PURPLE, (flashbang_range + 2), 4, 2) for(var/mob/living/M in get_hearers_in_view(flashbang_range, flashbang_turf)) bang(get_turf(M), M) - qdel(src) + resolve() /obj/item/grenade/hypnotic/proc/bang(turf/T, mob/living/M) if(M.stat == DEAD) //They're dead! diff --git a/code/game/objects/items/grenades/plastic.dm b/code/game/objects/items/grenades/plastic.dm index b0e3d45c9cb..a1b169189d3 100644 --- a/code/game/objects/items/grenades/plastic.dm +++ b/code/game/objects/items/grenades/plastic.dm @@ -44,6 +44,8 @@ /obj/item/grenade/c4/prime() if(QDELETED(src)) return + + . = ..() var/turf/location if(target) if(!QDELETED(target)) @@ -59,7 +61,7 @@ explosion(get_step(T, aim_dir), boom_sizes[1], boom_sizes[2], boom_sizes[3]) else explosion(location, boom_sizes[1], boom_sizes[2], boom_sizes[3]) - qdel(src) + resolve() //assembly stuff /obj/item/grenade/c4/receive_signal() @@ -102,7 +104,7 @@ I.throw_range = max(1, (I.throw_range - 3)) if(I.embedding) I.embedding["embed_chance"] = 0 - I.AddElement(/datum/element/embed, I.embedding) + I.updateEmbedding() else if(istype(AM, /mob/living)) plastic_overlay.layer = FLOAT_LAYER @@ -147,7 +149,7 @@ shout_syndicate_crap(user) explosion(user,0,2,0) //Cheap explosion imitation because putting prime() here causes runtimes user.gib(1, 1) - qdel(src) + resolve() // X4 is an upgraded directional variant of c4 which is relatively safe to be standing next to. And much less safe to be standing on the other side of. // C4 is intended to be used for infiltration, and destroying tech. X4 is intended to be used for heavy breaching and tight spaces. diff --git a/code/game/objects/items/grenades/smokebomb.dm b/code/game/objects/items/grenades/smokebomb.dm index 3bcfee09384..590312772ef 100644 --- a/code/game/objects/items/grenades/smokebomb.dm +++ b/code/game/objects/items/grenades/smokebomb.dm @@ -20,6 +20,7 @@ ///Here we generate some smoke and also damage blobs??? for some reason. Honestly not sure why we do that. /obj/item/grenade/smokebomb/prime() + . = ..() update_mob() playsound(src, 'sound/effects/smoke.ogg', 50, TRUE, -3) var/datum/effect_system/smoke_spread/bad/smoke = new @@ -29,4 +30,4 @@ for(var/obj/structure/blob/B in view(8,src)) var/damage = round(30/(get_dist(B,src)+1)) B.take_damage(damage, BURN, "melee", 0) - qdel(src) + resolve() diff --git a/code/game/objects/items/grenades/spawnergrenade.dm b/code/game/objects/items/grenades/spawnergrenade.dm index ec92cf69354..dfa0b6b63d1 100644 --- a/code/game/objects/items/grenades/spawnergrenade.dm +++ b/code/game/objects/items/grenades/spawnergrenade.dm @@ -8,6 +8,7 @@ var/deliveryamt = 1 // amount of type to deliver /obj/item/grenade/spawnergrenade/prime() // Prime now just handles the two loops that query for people in lockers and people who can see it. + . = ..() update_mob() if(spawner_type && deliveryamt) // Make a quick flash @@ -20,7 +21,7 @@ var/list/spawned = spawn_and_random_walk(spawner_type, T, deliveryamt, walk_chance=50, admin_spawn=((flags_1 & ADMIN_SPAWNED_1) ? TRUE : FALSE)) afterspawn(spawned) - qdel(src) + resolve() /obj/item/grenade/spawnergrenade/proc/afterspawn(list/mob/spawned) return diff --git a/code/game/objects/items/grenades/syndieminibomb.dm b/code/game/objects/items/grenades/syndieminibomb.dm index f7c68316741..446b27d5a40 100644 --- a/code/game/objects/items/grenades/syndieminibomb.dm +++ b/code/game/objects/items/grenades/syndieminibomb.dm @@ -4,27 +4,44 @@ icon = 'icons/obj/grenade.dmi' icon_state = "syndicate" item_state = "flashbang" - + ex_dev = 1 + ex_heavy = 2 + ex_light = 4 + ex_flame = 2 /obj/item/grenade/syndieminibomb/prime() + . = ..() update_mob() - explosion(src.loc,1,2,4,flame_range = 2) - qdel(src) + resolve() /obj/item/grenade/syndieminibomb/concussion name = "HE Grenade" desc = "A compact shrapnel grenade meant to devastate nearby organisms and cause some damage in the process. Pull pin and throw opposite direction." icon_state = "concussion" + ex_heavy = 2 + ex_light = 3 + ex_flame = 3 -/obj/item/grenade/syndieminibomb/concussion/prime() - update_mob() - explosion(src.loc,0,2,3,flame_range = 3) - qdel(src) - -/obj/item/grenade/syndieminibomb/concussion/frag +/obj/item/grenade/frag name = "frag grenade" - desc = "Fire in the hole." + desc = "An anti-personnel fragmentation grenade, this weapon excels at killing soft targets by shredding them with metal shrapnel." icon_state = "frag" + shrapnel_type = /obj/projectile/bullet/shrapnel + shrapnel_radius = 4 + ex_heavy = 1 + ex_light = 3 + ex_flame = 4 + +/obj/item/grenade/frag/mega + name = "FRAG grenade" + desc = "An anti-everything fragmentation grenade, this weapon excels at killing anything any everything by shredding them with metal shrapnel." + shrapnel_type = /obj/projectile/bullet/shrapnel/mega + shrapnel_radius = 12 + +/obj/item/grenade/frag/prime() + . = ..() + update_mob() + resolve() /obj/item/grenade/gluon desc = "An advanced grenade that releases a harmful stream of gluons inducing radiation in those nearby. These gluon streams will also make victims feel exhausted, and induce shivering. This extreme coldness will also likely wet any nearby floors." @@ -37,6 +54,7 @@ var/stamina_damage = 30 /obj/item/grenade/gluon/prime() + . = ..() update_mob() playsound(loc, 'sound/effects/empulse.ogg', 50, TRUE) radiation_pulse(src, rad_damage) @@ -47,4 +65,4 @@ for(var/mob/living/carbon/L in T) L.adjustStaminaLoss(stamina_damage) L.adjust_bodytemperature(-230) - qdel(src) + resolve() diff --git a/code/game/objects/items/melee/transforming.dm b/code/game/objects/items/melee/transforming.dm index 0bf164d81ea..a2e10426784 100644 --- a/code/game/objects/items/melee/transforming.dm +++ b/code/game/objects/items/melee/transforming.dm @@ -23,7 +23,7 @@ if(attack_verb_off.len) attack_verb = attack_verb_off if(embedding) - RemoveElement(/datum/element/embed, embedding) + updateEmbedding() if(sharpness) AddComponent(/datum/component/butchering, 50, 100, 0, hitsound) @@ -56,7 +56,7 @@ icon_state = icon_state_on w_class = w_class_on if(embedding) - AddElement(/datum/element/embed, embedding) + updateEmbedding() else force = initial(force) throwforce = initial(throwforce) @@ -67,7 +67,7 @@ icon_state = initial(icon_state) w_class = initial(w_class) if(embedding) - RemoveElement(/datum/element/embed, embedding) + disableEmbedding() transform_messages(user, supress_message_text) add_fingerprint(user) diff --git a/code/game/objects/items/shrapnel.dm b/code/game/objects/items/shrapnel.dm new file mode 100644 index 00000000000..cd3436846e6 --- /dev/null +++ b/code/game/objects/items/shrapnel.dm @@ -0,0 +1,68 @@ +/obj/item/shrapnel // frag grenades + name = "shrapnel shard" + embedding = list(embed_chance=70, ignore_throwspeed_threshold=TRUE, fall_chance=4, embed_chance_turf_mod=-100) + custom_materials = list(/datum/material/iron=50) + armour_penetration = -20 + icon = 'icons/obj/shards.dmi' + icon_state = "large" + w_class = WEIGHT_CLASS_TINY + item_flags = DROPDEL + +/obj/item/shrapnel/stingball // stingbang grenades + name = "stingball" + embedding = list(embed_chance=90, fall_chance=3, jostle_chance=7, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.7, pain_mult=5, jostle_pain_mult=6, rip_time=15, embed_chance_turf_mod=-100) + icon_state = "tiny" + +/obj/item/shrapnel/bullet // bullets + name = "bullet" + icon = 'icons/obj/ammo.dmi' + icon_state = "s-casing" + item_flags = NONE + +/obj/item/shrapnel/bullet/c38 // .38 round + name = "\improper .38 bullet" + +/obj/item/shrapnel/bullet/c38/dumdum // .38 DumDum round + name = "\improper .38 DumDum bullet" + embedding = list(embed_chance=70, fall_chance=7, jostle_chance=7, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=10, embed_chance_turf_mod=-100) + +/obj/projectile/bullet/shrapnel + name = "flying shrapnel shard" + damage = 9 + range = 10 + armour_penetration = -30 + dismemberment = 5 + ricochets_max = 2 + ricochet_chance = 40 + shrapnel_type = /obj/item/shrapnel + ricochet_incidence_leeway = 60 + hit_stunned_targets = TRUE + +/obj/projectile/bullet/shrapnel/mega + name = "flying shrapnel hunk" + range = 25 + dismemberment = 10 + ricochets_max = 4 + ricochet_chance = 90 + ricochet_decay_chance = 0.9 + +/obj/projectile/bullet/pellet/stingball + name = "stingball pellet" + damage = 3 + stamina = 8 + ricochets_max = 4 + ricochet_chance = 66 + ricochet_decay_chance = 1 + ricochet_decay_damage = 0.9 + ricochet_auto_aim_angle = 10 + ricochet_auto_aim_range = 2 + ricochet_incidence_leeway = 0 + shrapnel_type = /obj/item/shrapnel/stingball + +/obj/projectile/bullet/pellet/stingball/mega + name = "megastingball pellet" + ricochets_max = 6 + ricochet_chance = 110 + +/obj/projectile/bullet/pellet/stingball/on_ricochet(atom/A) + hit_stunned_targets = TRUE // ducking will save you from the first wave, but not the rebounds diff --git a/code/game/objects/items/stacks/tape.dm b/code/game/objects/items/stacks/tape.dm index 37084a8d886..f7760622c2c 100644 --- a/code/game/objects/items/stacks/tape.dm +++ b/code/game/objects/items/stacks/tape.dm @@ -36,7 +36,7 @@ return I.embedding = conferred_embed - I.AddElement(/datum/element/embed, I.embedding) + I.updateEmbedding() to_chat(user, "You finish wrapping [I] with [src].") I.name = "[prefix] [I.name]" diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index 3918eb4f71a..d2dd01fa76a 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -479,7 +479,7 @@ /obj/item/grenade/smokebomb = 4, /obj/item/grenade/empgrenade = 1, /obj/item/grenade/empgrenade = 1, - /obj/item/grenade/syndieminibomb/concussion/frag = 10, + /obj/item/grenade/frag = 10, /obj/item/grenade/gluon = 4, /obj/item/grenade/chem_grenade/incendiary = 2, /obj/item/grenade/chem_grenade/facid = 1, diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm index 8c3391b2cf3..9f8632c9366 100644 --- a/code/game/objects/items/storage/boxes.dm +++ b/code/game/objects/items/storage/boxes.dm @@ -280,6 +280,16 @@ for(var/i in 1 to 7) new /obj/item/grenade/flashbang(src) +/obj/item/storage/box/stingbangs + name = "box of stingbangs (WARNING)" + desc = "WARNING: These devices are extremely dangerous and can cause severe injuries or death in repeated use." + icon_state = "secbox" + illustration = "flashbang" + +/obj/item/storage/box/stingbangs/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/grenade/stingbang(src) + /obj/item/storage/box/flashes name = "box of flashbulbs" desc = "WARNING: Flashes can cause serious eye damage, protective eyewear is required." @@ -1250,7 +1260,7 @@ new/obj/item/sparkler(src) new/obj/item/grenade/firecracker(src) if(prob(20)) - new /obj/item/grenade/syndieminibomb/concussion/frag(src) + new /obj/item/grenade/frag(src) else new /obj/item/toy/snappop(src) diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm index 7c9d15d4543..4faf199a166 100644 --- a/code/game/objects/items/storage/uplink_kits.dm +++ b/code/game/objects/items/storage/uplink_kits.dm @@ -9,8 +9,8 @@ new /obj/item/binoculars(src) // 2 tc? new /obj/item/encryptionkey/syndicate(src) // 2 tc new /obj/item/storage/box/syndie_kit/space(src) //4 tc - new /obj/item/grenade/syndieminibomb/concussion/frag(src) // ~2 tc each? - new /obj/item/grenade/syndieminibomb/concussion/frag(src) + new /obj/item/grenade/frag(src) // ~2 tc each? + new /obj/item/grenade/frag(src) new /obj/item/flashlight/emp(src) if("bloodyspai") diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index af4bec92789..72e722a5611 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -280,8 +280,8 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 force = 2 throwforce = 20 //20 + 2 (WEIGHT_CLASS_SMALL) * 4 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = 28 damage on hit due to guaranteed embedding throw_speed = 4 - embedding = list("pain_mult" = 4, "embed_chance" = 100, "fall_chance" = 0) - + embedding = list("pain_mult" = 4, "embed_chance" = 100, "fall_chance" = 0, "embed_chance_turf_mod" = 15) + armour_penetration = 40 w_class = WEIGHT_CLASS_SMALL sharpness = IS_SHARP diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm index a6e2a869de9..ff1d87437e9 100644 --- a/code/game/objects/obj_defense.dm +++ b/code/game/objects/obj_defense.dm @@ -71,7 +71,8 @@ /obj/bullet_act(obj/projectile/P) . = ..() playsound(src, P.hitsound, 50, TRUE) - visible_message("[src] is hit by \a [P]!", null, null, COMBAT_MESSAGE_RANGE) + if(P.suppressed != SUPPRESSED_VERY) + visible_message("[src] is hit by \a [P]!", null, null, COMBAT_MESSAGE_RANGE) if(!QDELETED(src)) //Bullet on_hit effect might have already destroyed this object take_damage(P.damage, P.damage_type, P.flag, 0, turn(P.dir, 180), P.armour_penetration) diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 06921570865..c5ba180eedd 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -326,3 +326,8 @@ // Should move all contained objects to it's location. /obj/proc/dump_contents() CRASH("Unimplemented.") + +/obj/handle_ricochet(obj/projectile/P) + . = ..() + if(. && ricochet_damage_mod) + take_damage(P.damage * ricochet_damage_mod, P.damage_type, P.flag, 0, turn(P.dir, 180), P.armour_penetration) // pass along ricochet_damage_mod damage to the structure for the ricochet diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index 27a0a05d2ef..90e411ebac1 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -4,6 +4,8 @@ max_integrity = 300 interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT layer = BELOW_OBJ_LAYER + flags_ricochet = RICOCHET_HARD + ricochet_chance_mod = 0.5 var/climb_time = 20 var/climb_stun = 20 diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 2d3280c1c8b..81c00d78159 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -27,6 +27,8 @@ var/real_explosion_block //ignore this, just use explosion_block var/breaksound = "shatter" var/hitsound = 'sound/effects/Glasshit.ogg' + flags_ricochet = RICOCHET_HARD + ricochet_chance_mod = 0.4 /obj/structure/window/examine(mob/user) @@ -370,6 +372,7 @@ state = RWINDOW_SECURE glass_type = /obj/item/stack/sheet/rglass rad_insulation = RAD_HEAVY_INSULATION + ricochet_chance_mod = 0.8 //this is shitcode but all of construction is shitcode and needs a refactor, it works for now //If you find this like 4 years later and construction still hasn't been refactored, I'm so sorry for this @@ -675,6 +678,7 @@ explosion_block = 3 glass_type = /obj/item/stack/sheet/titaniumglass glass_amount = 2 + ricochet_chance_mod = 0.9 /obj/structure/window/shuttle/narsie_act() add_atom_colour("#3C3434", FIXED_COLOUR_PRIORITY) diff --git a/code/game/turfs/closed/wall/mineral_walls.dm b/code/game/turfs/closed/wall/mineral_walls.dm index ec1fa3e49f2..221159354aa 100644 --- a/code/game/turfs/closed/wall/mineral_walls.dm +++ b/code/game/turfs/closed/wall/mineral_walls.dm @@ -189,7 +189,8 @@ icon = 'icons/turf/walls/shuttle_wall.dmi' icon_state = "map-shuttle" explosion_block = 3 - flags_1 = CAN_BE_DIRTY_1 | CHECK_RICOCHET_1 + flags_1 = CAN_BE_DIRTY_1 + flags_ricochet = RICOCHET_SHINY | RICOCHET_HARD sheet_type = /obj/item/stack/sheet/mineral/titanium smooth = SMOOTH_MORE|SMOOTH_DIAGONAL canSmoothWith = list(/turf/closed/wall/mineral/titanium, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/window/shuttle, /obj/structure/shuttle/engine/heater, /obj/structure/falsewall/titanium) diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm index af11bc1329e..b871632f411 100644 --- a/code/game/turfs/closed/walls.dm +++ b/code/game/turfs/closed/walls.dm @@ -12,6 +12,8 @@ baseturfs = /turf/open/floor/plating + flags_ricochet = RICOCHET_HARD + ///lower numbers are harder. Used to determine the probability of a hulk smashing through. Also, (hardness - 40) is used as a modifier for objects trying to embed in this (hardness of 30 results in a -10% chance) var/hardness = 40 var/slicing_duration = 100 //default time taken to slice the wall @@ -40,17 +42,6 @@ /turf/closed/wall/attack_tk() return -/turf/closed/wall/handle_ricochet(obj/projectile/P) //A huge pile of shitcode! - var/turf/p_turf = get_turf(P) - var/face_direction = get_dir(src, p_turf) - var/face_angle = dir2angle(face_direction) - var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180)) - if(abs(incidence_s) > 90 && abs(incidence_s) < 270) - return FALSE - var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) - P.setAngle(new_angle_s) - return TRUE - /turf/closed/wall/proc/dismantle_wall(devastated=0, explode=0) if(devastated) devastate_wall() diff --git a/code/modules/antagonists/blob/structures/shield.dm b/code/modules/antagonists/blob/structures/shield.dm index b837697af6d..bd0e8c62c68 100644 --- a/code/modules/antagonists/blob/structures/shield.dm +++ b/code/modules/antagonists/blob/structures/shield.dm @@ -38,21 +38,8 @@ desc = "A solid wall of slightly twitching tendrils with a reflective glow." damaged_desc = "A wall of twitching tendrils with a reflective glow." icon_state = "blob_glow" - flags_1 = CHECK_RICOCHET_1 + flags_ricochet = RICOCHET_SHINY point_return = 8 max_integrity = 100 brute_resist = 0.5 explosion_block = 2 - -/obj/structure/blob/shield/reflective/handle_ricochet(obj/projectile/P) - var/turf/p_turf = get_turf(P) - var/face_direction = get_dir(src, p_turf) - var/face_angle = dir2angle(face_direction) - var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180)) - if(abs(incidence_s) > 90 && abs(incidence_s) < 270) - return FALSE - var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) - P.setAngle(new_angle_s) - if(!(P.reflectable & REFLECT_FAKEPROJECTILE)) - visible_message("[P] reflects off [src]!") - return TRUE diff --git a/code/modules/cargo/packs.dm b/code/modules/cargo/packs.dm index 80d4660809e..2c01ac1d231 100644 --- a/code/modules/cargo/packs.dm +++ b/code/modules/cargo/packs.dm @@ -252,7 +252,7 @@ /datum/supply_pack/security group = "Security" - access = ACCESS_SECURITY + access_any = ACCESS_FORENSICS_LOCKERS //| ACCESS_SECURITY crate_type = /obj/structure/closet/crate/secure/gear /datum/supply_pack/security/ammo @@ -290,6 +290,16 @@ /obj/item/gun/energy/disabler) crate_name = "disabler crate" +/datum/supply_pack/security/dumdum + name = ".38 DumDum Speedloader" + desc = "Contains one speedloader of .38 DumDum ammunition, good for embedding in soft targets. Requires Security or Forensics access to open." + cost = 1200 + access = FALSE + small_item = TRUE + access_any = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS) + contains = list(/obj/item/ammo_box/c38/dumdum) + crate_name = ".38 match crate" + /datum/supply_pack/security/forensics name = "Forensics Crate" desc = "Stay hot on the criminal's heels with Nanotrasen's Detective Essentials(tm). Contains a forensics scanner, six evidence bags, camera, tape recorder, white crayon, and of course, a fedora. Requires Security access to open." @@ -320,6 +330,16 @@ /obj/item/gun/energy/laser) crate_name = "laser crate" +/datum/supply_pack/security/match + name = ".38 Match Grade Speedloader" + desc = "Contains one speedloader of match grade .38 ammunition, perfect for showing off trickshots. Requires Security or Forensics access to open." + cost = 1200 + access = FALSE + small_item = TRUE + access_any = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS) + contains = list(/obj/item/ammo_box/c38/match) + crate_name = ".38 match crate" + /datum/supply_pack/security/securitybarriers name = "Security Barrier Grenades" desc = "Stem the tide with four Security Barrier grenades. Requires Security access to open." @@ -348,6 +368,20 @@ /obj/item/clothing/head/beret/sec/navyhos) crate_name = "security clothing crate" +/datum/supply_pack/security/stingpack + name = "Stingbang Grenade Pack" + desc = "Contains five \"stingbang\" grenades, perfect for stopping riots and playing morally unthinkable pranks. Requires Security access to open." + cost = 2500 + contains = list(/obj/item/storage/box/stingbangs) + crate_name = "stingbang grenade pack crate" + +/datum/supply_pack/security/stingpack/single + name = "Stingbang Single-Pack" + desc = "Contains one \"stingbang\" grenade, perfect for playing meanhearted pranks. Requires Security access to open." + cost = 1400 + small_item = TRUE + contains = list(/obj/item/grenade/stingbang) + /datum/supply_pack/security/supplies name = "Security Supplies Crate" desc = "Contains seven flashbangs, seven teargas grenades, six flashes, and seven handcuffs. Requires Security access to open." diff --git a/code/modules/events/wizard/embeddies.dm b/code/modules/events/wizard/embeddies.dm index 087bf68627a..fe08b9c7432 100644 --- a/code/modules/events/wizard/embeddies.dm +++ b/code/modules/events/wizard/embeddies.dm @@ -14,7 +14,7 @@ if(!I.embedding || I.embedding == EMBED_HARMLESS) I.embedding = EMBED_POINTY - I.AddElement(/datum/element/embed, I.embedding) + I.updateEmbedding() I.name = "pointy [I.name]" GLOB.embedpocalypse = TRUE @@ -40,7 +40,7 @@ if(!I.embedding) I.embedding = EMBED_HARMLESS - I.AddElement(/datum/element/embed, I.embedding) + I.updateEmbedding() I.name = "sticky [I.name]" GLOB.stickpocalypse = TRUE diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm index 948c63fd5eb..2e2674daf28 100644 --- a/code/modules/mob/living/blood.dm +++ b/code/modules/mob/living/blood.dm @@ -90,7 +90,7 @@ //We want an accurate reading of .len listclearnulls(BP.embedded_objects) for(var/obj/item/embeddies in BP.embedded_objects) - if(!embeddies.is_embed_harmless()) + if(!embeddies.isEmbedHarmless()) temp_bleed += 0.5 if(brutedamage >= 20) diff --git a/code/modules/mob/living/carbon/alien/alien_defense.dm b/code/modules/mob/living/carbon/alien/alien_defense.dm index e5302c209d3..bf1350a708c 100644 --- a/code/modules/mob/living/carbon/alien/alien_defense.dm +++ b/code/modules/mob/living/carbon/alien/alien_defense.dm @@ -21,6 +21,8 @@ In all, this is a lot like the monkey code. /N switch(M.a_intent) if ("help") + if(M == src && check_self_for_injuries()) + return set_resting(FALSE) AdjustStun(-60) AdjustKnockdown(-60) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index c712c56d9c1..4f97b382844 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -217,6 +217,16 @@ "[usr] [internal ? "opens" : "closes"] the valve on your [ITEM.name].", null, null, usr) to_chat(usr, "You [internal ? "open" : "close"] the valve on [src]'s [ITEM.name].") + if(href_list["embedded_object"] && usr.canUseTopic(src, BE_CLOSE, NO_DEXTERITY)) + var/obj/item/bodypart/L = locate(href_list["embedded_limb"]) in bodyparts + if(!L) + return + var/obj/item/I = locate(href_list["embedded_object"]) in L.embedded_objects + if(!I || I.loc != src) //no item, no limb, or item is not in limb or in the person anymore + return + SEND_SIGNAL(src, COMSIG_CARBON_EMBED_RIP, I, L) + return + /mob/living/carbon/on_fall() . = ..() loc.handle_fall(src)//it's loc so it doesn't call the mob's handle_fall which does nothing diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index a852b103fb4..3915dbce699 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -258,6 +258,9 @@ to_chat(M, "You can't put [p_them()] out with just your bare hands!") return + if(M == src && check_self_for_injuries()) + return + if(!(mobility_flags & MOBILITY_STAND)) if(buckled) to_chat(M, "You need to unbuckle [src] first to do that!") @@ -305,6 +308,27 @@ playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) +/// Check ourselves to see if we've got any shrapnel, return true if we do. This is a much simpler version of what humans do, we only indicate we're checking ourselves if there's actually shrapnel +/mob/living/carbon/proc/check_self_for_injuries() + if(stat == DEAD || stat == UNCONSCIOUS) + return + + var/embeds = FALSE + for(var/X in bodyparts) + var/obj/item/bodypart/LB = X + for(var/obj/item/I in LB.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("[src] examines [p_them()]self.", \ + "You check yourself for shrapnel.") + if(I.isEmbedHarmless()) + to_chat(src, "\t There is \a [I] stuck to your [LB.name]!") + else + to_chat(src, "\t There is \a [I] embedded in your [LB.name]!") + + return embeds + /mob/living/carbon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0) var/obj/item/organ/eyes/eyes = getorganslot(ORGAN_SLOT_EYES) diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index d0041f8022b..edf7c7b0a25 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -32,14 +32,36 @@ else if(get_bodypart(BODY_ZONE_HEAD)) . += "It appears that [t_his] brain is missing..." - var/list/missing = get_missing_limbs() + var/list/msg = list("") + var/list/missing = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) + var/list/disabled = list() + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + if(BP.disabled) + disabled += BP + missing -= BP.body_zone + for(var/obj/item/I in BP.embedded_objects) + if(I.isEmbedHarmless()) + msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] stuck to [t_his] [BP.name]!\n" + else + msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!\n" + + for(var/X in disabled) + var/obj/item/bodypart/BP = X + var/damage_text + if(!(BP.get_damage(include_stamina = FALSE) >= BP.max_damage)) //Stamina is disabling the limb + damage_text = "limp and lifeless" + else + damage_text = (BP.brute_dam >= BP.burn_dam) ? BP.heavy_brute_msg : BP.heavy_burn_msg + msg += "[capitalize(t_his)] [BP.name] is [damage_text]!\n" + for(var/t in missing) if(t==BODY_ZONE_HEAD) - . += "[t_His] [parse_zone(t)] is missing!" + msg += "[t_His] [parse_zone(t)] is missing!\n" continue - . += "[t_His] [parse_zone(t)] is missing!" + msg += "[t_His] [parse_zone(t)] is missing!\n" + - var/list/msg = list("") var/temp = getBruteLoss() if(!(user == src && src.hal_screwyhud == SCREWYHUD_HEALTHY)) //fake healthy if(temp) diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 67fba038a1b..3e628a3c0d6 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -146,7 +146,7 @@ disabled += BP missing -= BP.body_zone for(var/obj/item/I in BP.embedded_objects) - if(I.is_embed_harmless()) + if(I.isEmbedHarmless()) msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] stuck to [t_his] [BP.name]!\n" else msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!\n" diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index d254cdf87df..ade58f69064 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -222,7 +222,7 @@ var/obj/item/I = locate(href_list["embedded_object"]) in L.embedded_objects if(!I || I.loc != src) //no item, no limb, or item is not in limb or in the person anymore return - SEND_SIGNAL(src, COMSIG_HUMAN_EMBED_RIP, I, L) + SEND_SIGNAL(src, COMSIG_CARBON_EMBED_RIP, I, L) return if(href_list["item"]) //canUseTopic check for this is handled by mob/Topic() diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 604ef492a92..a7cf9926767 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -669,7 +669,7 @@ ..() -/mob/living/carbon/human/proc/check_self_for_injuries() +/mob/living/carbon/human/check_self_for_injuries() if(stat == DEAD || stat == UNCONSCIOUS) return @@ -733,7 +733,7 @@ to_chat(src, "\t Your [LB.name][isdisabled][self_aware ? " has " : " is "][status].") for(var/obj/item/I in LB.embedded_objects) - if(I.is_embed_harmless()) + if(I.isEmbedHarmless()) to_chat(src, "\t There is \a [I] stuck to your [LB.name]!") else to_chat(src, "\t There is \a [I] embedded in your [LB.name]!") diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 2234d279a44..507ff78cbd3 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -1,9 +1,14 @@ -/mob/living/proc/run_armor_check(def_zone = null, attack_flag = "melee", absorb_text = null, soften_text = null, armour_penetration, penetrated_text) +/mob/living/proc/run_armor_check(def_zone = null, attack_flag = "melee", absorb_text = null, soften_text = null, silent=FALSE, armour_penetration, penetrated_text) var/armor = getarmor(def_zone, attack_flag) + if(armor <= 0) + return armor + if(silent) + return max(0, armor - armour_penetration) + //the if "armor" check is because this is used for everything on /living, including humans - if(armor > 0 && armour_penetration) + if(armour_penetration) armor = max(0, armor - armour_penetration) if(penetrated_text) to_chat(src, "[penetrated_text]") @@ -14,14 +19,13 @@ to_chat(src, "[absorb_text]") else to_chat(src, "Your armor absorbs the blow!") - else if(armor > 0) + else if(soften_text) to_chat(src, "[soften_text]") else to_chat(src, "Your armor softens the blow!") return armor - /mob/living/proc/getarmor(def_zone, type) return 0 diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm index ccce6c3af2b..508722ecf2a 100644 --- a/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm +++ b/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm @@ -15,4 +15,4 @@ /obj/item/throwing_star/ninja name = "ninja throwing star" throwforce = 30 - embedding = list("pain_mult" = 6, "embed_chance" = 100, "fall_chance" = 0) + embedding = list("pain_mult" = 6, "embed_chance" = 100, "fall_chance" = 0, "embed_chance_turf_mod" = 15) diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm index 9de0f6d1f9b..b1a3dc0a76e 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -237,7 +237,7 @@ throwforce = 35 playsound(user, 'sound/weapons/saberon.ogg', 5, TRUE) to_chat(user, "[src] is now active.") - AddElement(/datum/element/embed, embedding) + updateEmbedding() update_icon() /obj/item/pen/edagger/update_icon_state() diff --git a/code/modules/projectiles/ammunition/_firing.dm b/code/modules/projectiles/ammunition/_firing.dm index ba3bb803070..d569c1a92c9 100644 --- a/code/modules/projectiles/ammunition/_firing.dm +++ b/code/modules/projectiles/ammunition/_firing.dm @@ -1,17 +1,21 @@ /obj/item/ammo_casing/proc/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from) distro += variance - for (var/i = max(1, pellets), i > 0, i--) - var/targloc = get_turf(target) - ready_proj(target, user, quiet, zone_override, fired_from) + var/targloc = get_turf(target) + ready_proj(target, user, quiet, zone_override, fired_from) + if(pellets == 1) if(distro) //We have to spread a pixel-precision bullet. throw_proj was called before so angles should exist by now... if(randomspread) spread = round((rand() - 0.5) * distro) else //Smart spread - spread = round((i / pellets - 0.5) * distro) + spread = round(1 - 0.5) * distro if(!throw_proj(target, targloc, user, params, spread)) - return 0 - if(i > 1) - newshot() + return FALSE + else + if(isnull(BB)) + return FALSE + AddComponent(/datum/component/pellet_cloud, projectile_type, pellets) + SEND_SIGNAL(src, COMSIG_PELLET_CLOUD_INIT, target, user, fired_from, randomspread, spread, zone_override, params, distro) + if(click_cooldown_override) user.changeNext_move(click_cooldown_override) else diff --git a/code/modules/projectiles/ammunition/ballistic/lmg.dm b/code/modules/projectiles/ammunition/ballistic/lmg.dm index 2ef9a8047ba..cf4c129d5f7 100644 --- a/code/modules/projectiles/ammunition/ballistic/lmg.dm +++ b/code/modules/projectiles/ammunition/ballistic/lmg.dm @@ -21,3 +21,8 @@ name = "7.12x82mm incendiary bullet casing" desc = "A 7.12x82mm bullet casing designed with a chemical-filled capsule on the tip that when bursted, reacts with the atmosphere to produce a fireball, engulfing the target in flames." projectile_type = /obj/projectile/bullet/incendiary/mm712x82 + +/obj/item/ammo_casing/mm712x82/match + name = "7.12x82mm match bullet casing" + desc = "A 7.12x82mm bullet casing manufactured to unfailingly high standards, you could pull off some cool trickshots with this." + projectile_type = /obj/projectile/bullet/mm712x82_match diff --git a/code/modules/projectiles/ammunition/ballistic/revolver.dm b/code/modules/projectiles/ammunition/ballistic/revolver.dm index df118602cc3..4fa129d1517 100644 --- a/code/modules/projectiles/ammunition/ballistic/revolver.dm +++ b/code/modules/projectiles/ammunition/ballistic/revolver.dm @@ -6,6 +6,12 @@ caliber = "357" projectile_type = /obj/projectile/bullet/a357 +/obj/item/ammo_casing/a357/match + name = ".357 match bullet casing" + desc = "A .357 bullet casing, manufactured to exceedingly high standards." + caliber = "357" + projectile_type = /obj/projectile/bullet/a357/match + // 7.62x38mmR (Nagant Revolver) /obj/item/ammo_casing/n762 @@ -27,6 +33,21 @@ desc = "A .38 \"TRAC\" bullet casing." projectile_type = /obj/projectile/bullet/c38/trac +/obj/item/ammo_casing/c38/match + name = ".38 Match bullet casing" + desc = "A .38 bullet casing, manufactured to exceedingly high standards." + projectile_type = /obj/projectile/bullet/c38/match + +/obj/item/ammo_casing/c38/match/bouncy + name = ".38 Rubber bullet casing" + desc = "A .38 rubber bullet casing, manufactured to exceedingly high standards." + projectile_type = /obj/projectile/bullet/c38/match/bouncy + +/obj/item/ammo_casing/c38/dumdum + name = ".38 DumDum bullet casing" + desc = "A .38 DumDum bullet casing." + projectile_type = /obj/projectile/bullet/c38/dumdum + /obj/item/ammo_casing/c38/hotshot name = ".38 Hot Shot bullet casing" desc = "A .38 Hot Shot bullet casing." diff --git a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm index b0218577271..cc5122052db 100644 --- a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm +++ b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm @@ -7,6 +7,11 @@ multiple_sprites = AMMO_BOX_PER_BULLET item_flags = NO_MAT_REDEMPTION +/obj/item/ammo_box/a357/match + name = "speed loader (.357 Match)" + desc = "Designed to quickly reload revolvers. These rounds are manufactured within extremely tight tolerances, making them easy to show off trickshots with." + ammo_type = /obj/item/ammo_casing/a357/match + /obj/item/ammo_box/c38 name = "speed loader (.38)" desc = "Designed to quickly reload revolvers." @@ -21,6 +26,21 @@ desc = "Designed to quickly reload revolvers. TRAC bullets embed a tracking implant within the target's body." ammo_type = /obj/item/ammo_casing/c38/trac +/obj/item/ammo_box/c38/match + name = "speed loader (.38 Match)" + desc = "Designed to quickly reload revolvers. These rounds are manufactured within extremely tight tolerances, making them easy to show off trickshots with." + ammo_type = /obj/item/ammo_casing/c38/match + +/obj/item/ammo_box/c38/match/bouncy + name = "speed loader (.38 Rubber)" + desc = "Designed to quickly reload revolvers. These rounds are incredibly bouncy and MOSTLY nonlethal, making them great to show off trickshots with." + ammo_type = /obj/item/ammo_casing/c38/match/bouncy + +/obj/item/ammo_box/c38/dumdum + name = "speed loader (.38 DumDum)" + desc = "Designed to quickly reload revolvers. DumDum bullets shatter on impact and shred the target's innards, likely getting caught inside." + ammo_type = /obj/item/ammo_casing/c38/dumdum + /obj/item/ammo_box/c38/hotshot name = "speed loader (.38 Hot Shot)" desc = "Designed to quickly reload revolvers. Hot Shot bullets contain an incendiary payload." diff --git a/code/modules/projectiles/boxes_magazines/external/lmg.dm b/code/modules/projectiles/boxes_magazines/external/lmg.dm index 6310ec19ee4..616c4e49ef9 100644 --- a/code/modules/projectiles/boxes_magazines/external/lmg.dm +++ b/code/modules/projectiles/boxes_magazines/external/lmg.dm @@ -17,6 +17,10 @@ name = "box magazine (Incendiary 7.12x82mm)" ammo_type = /obj/item/ammo_casing/mm712x82/incen +/obj/item/ammo_box/magazine/mm712x82/match + name = "box magazine (Match 7.12x82mm)" + ammo_type = /obj/item/ammo_casing/mm712x82/match + /obj/item/ammo_box/magazine/mm712x82/update_icon() ..() icon_state = "a762-[round(ammo_count(),10)]" diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 0d3d71bafe1..7deef5f30eb 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -43,10 +43,25 @@ var/nondirectional_sprite = FALSE //Set TRUE to prevent projectiles from having their sprites rotated based on firing angle var/spread = 0 //amount (in degrees) of projectile spread animate_movement = NO_STEPS //Use SLIDE_STEPS in conjunction with legacy + /// how many times we've ricochet'd so far (instance variable, not a stat) var/ricochets = 0 - var/ricochets_max = 2 - var/ricochet_chance = 30 - var/force_hit = FALSE //If the object being hit can pass ths damage on to something else, it should not do it for this bullet. + /// how many times we can ricochet max + var/ricochets_max = 0 + /// 0-100, the base chance of ricocheting, before being modified by the atom we shoot and our chance decay + var/ricochet_chance = 0 + /// 0-1 (or more, I guess) multiplier, the ricochet_chance is modified by multiplying this after each ricochet + var/ricochet_decay_chance = 0.7 + /// 0-1 (or more, I guess) multiplier, the projectile's damage is modified by multiplying this after each ricochet + var/ricochet_decay_damage = 0.7 + /// On ricochet, if nonzero, we consider all mobs within this range of our projectile at the time of ricochet to home in on like Revolver Ocelot, as governed by ricochet_auto_aim_angle + var/ricochet_auto_aim_range = 0 + /// On ricochet, if ricochet_auto_aim_range is nonzero, we'll consider any mobs within this range of the normal angle of incidence to home in on, higher = more auto aim + var/ricochet_auto_aim_angle = 30 + /// the angle of impact must be within this many degrees of the struck surface, set to 0 to allow any angle + var/ricochet_incidence_leeway = 40 + + ///If the object being hit can pass ths damage on to something else, it should not do it for this bullet + var/force_hit = FALSE //Hitscan var/hitscan = FALSE //Whether this is hitscan. If it is, speed is basically ignored. @@ -109,6 +124,11 @@ var/temporary_unstoppable_movement = FALSE + ///If defined, on hit we create an item of this type then call hitby() on the hit target with this + var/shrapnel_type + ///If TRUE, hit mobs even if they're on the floor and not our target + var/hit_stunned_targets = FALSE + /obj/projectile/Initialize() . = ..() permutated = list() @@ -120,6 +140,7 @@ on_range() /obj/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range + SEND_SIGNAL(src, COMSIG_PROJECTILE_RANGE_OUT) qdel(src) //to get the correct limb (if any) for the projectile hit message @@ -140,6 +161,9 @@ /obj/projectile/proc/on_hit(atom/target, blocked = FALSE) if(fired_from) SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle) + // i know that this is probably more with wands and gun mods in mind, but it's a bit silly that the projectile on_hit signal doesn't ping the projectile itself. + // maybe we care what the projectile thinks! See about combining these via args some time when it's not 5AM + SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, Angle) var/turf/target_loca = get_turf(target) var/hitx @@ -185,7 +209,9 @@ var/limb_hit = L.check_limb_hit(def_zone)//to get the correct message info. if(limb_hit) organ_hit_text = " in \the [parse_zone(limb_hit)]" - if(suppressed) + if(suppressed==SUPPRESSED_VERY) + playsound(loc, hitsound, 5, TRUE, -1) + else if(suppressed) playsound(loc, hitsound, 5, TRUE, -1) to_chat(L, "You're shot by \a [src][organ_hit_text]!") else @@ -216,7 +242,23 @@ return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume /obj/projectile/proc/on_ricochet(atom/A) - return + if(!ricochet_auto_aim_angle || !ricochet_auto_aim_range) + return + + var/mob/living/unlucky_sob + var/best_angle = ricochet_auto_aim_angle + if(firer && HAS_TRAIT(firer, TRAIT_NICE_SHOT)) + best_angle += NICE_SHOT_RICOCHET_BONUS + for(var/mob/living/L in range(ricochet_auto_aim_range, src.loc)) + if(L.stat == DEAD || !isInSight(src, L)) + continue + var/our_angle = abs(closer_angle_difference(Angle, Get_Angle(src.loc, L.loc))) + if(our_angle < best_angle) + best_angle = our_angle + unlucky_sob = L + + if(unlucky_sob) + setAngle(Get_Angle(src, unlucky_sob.loc)) /obj/projectile/proc/store_hitscan_collision(datum/point/pcache) beam_segments[beam_index] = pcache @@ -226,12 +268,14 @@ /obj/projectile/Bump(atom/A) var/datum/point/pcache = trajectory.copy_to() var/turf/T = get_turf(A) - if(check_ricochet(A) && check_ricochet_flag(A) && ricochets < ricochets_max) + if(ricochets < ricochets_max && check_ricochet_flag(A) && check_ricochet(A)) ricochets++ if(A.handle_ricochet(src)) on_ricochet(A) ignore_source_check = TRUE decayedRange = max(0, decayedRange - reflect_range_decrease) + ricochet_chance *= ricochet_decay_chance + damage *= ricochet_decay_damage range = decayedRange if(hitscan) store_hitscan_collision(pcache) @@ -308,14 +352,21 @@ return T //Returns null if nothing at all was found. -/obj/projectile/proc/check_ricochet() - if(prob(ricochet_chance)) +/obj/projectile/proc/check_ricochet(atom/A) + var/chance = ricochet_chance * A.ricochet_chance_mod + if(firer && HAS_TRAIT(firer, TRAIT_NICE_SHOT)) + chance += NICE_SHOT_RICOCHET_BONUS + if(prob(chance)) return TRUE return FALSE /obj/projectile/proc/check_ricochet_flag(atom/A) - if(A.flags_1 & CHECK_RICOCHET_1) + if((flag in list("energy", "laser")) && (A.flags_ricochet & RICOCHET_SHINY)) return TRUE + + if((flag in list("bomb", "bullet")) && (A.flags_ricochet & RICOCHET_HARD)) + return TRUE + return FALSE /obj/projectile/proc/return_predicted_turf_after_moves(moves, forced_angle) //I say predicted because there's no telling that the projectile won't change direction/location in flight. @@ -363,6 +414,8 @@ if(fired_from) SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original) //If no angle needs to resolve it from xo/yo! + if(shrapnel_type) + AddElement(/datum/element/embed, projectile_payload = shrapnel_type) if(!log_override && firer && original) log_combat(firer, original, "fired at", src, "from [get_area_name(src, TRUE)]") if(direct_target) @@ -548,7 +601,9 @@ else var/mob/living/L = target if(!direct_target) - var/checking = MOBILITY_USE | MOBILITY_STAND | MOBILITY_MOVE + var/checking = NONE + if(!hit_stunned_targets) + checking = MOBILITY_USE | MOBILITY_MOVE | MOBILITY_STAND if(!((L.mobility_flags & checking) == checking) || !(L.stat == CONSCIOUS)) //If they're able to 1. stand or 2. use items or 3. move, AND they are not softcrit, they are not stunned enough to dodge projectiles passing over. return FALSE return TRUE diff --git a/code/modules/projectiles/projectile/bullets/lmg.dm b/code/modules/projectiles/projectile/bullets/lmg.dm index 07532f5a926..9fc449c2705 100644 --- a/code/modules/projectiles/projectile/bullets/lmg.dm +++ b/code/modules/projectiles/projectile/bullets/lmg.dm @@ -42,3 +42,12 @@ name = "7.12x82mm incendiary bullet" damage = 20 fire_stacks = 3 + +/obj/projectile/bullet/mm712x82_match + name = "7.12x82mm match bullet" + damage = 40 + ricochets_max = 2 + ricochet_chance = 60 + ricochet_auto_aim_range = 4 + ricochet_incidence_leeway = 35 + diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm index b91aec9b3a6..18470345538 100644 --- a/code/modules/projectiles/projectile/bullets/revolver.dm +++ b/code/modules/projectiles/projectile/bullets/revolver.dm @@ -15,10 +15,43 @@ /obj/projectile/bullet/c38 name = ".38 bullet" damage = 25 + ricochets_max = 2 + ricochet_chance = 50 + ricochet_auto_aim_angle = 10 + ricochet_auto_aim_range = 3 + +/obj/projectile/bullet/c38/match + name = ".38 Match bullet" + ricochets_max = 4 + ricochet_chance = 100 + ricochet_auto_aim_angle = 40 + ricochet_auto_aim_range = 5 + ricochet_incidence_leeway = 50 + ricochet_decay_chance = 1 + ricochet_decay_damage = 1 + +/obj/projectile/bullet/c38/match/bouncy + name = ".38 Rubber bullet" + damage = 10 + stamina = 30 + armour_penetration = -30 + ricochets_max = 6 + ricochet_incidence_leeway = 70 + ricochet_chance = 130 + ricochet_decay_damage = 0.8 + shrapnel_type = NONE + +/obj/projectile/bullet/c38/dumdum + name = ".38 DumDum bullet" + damage = 15 + armour_penetration = -30 + ricochets_max = 0 + shrapnel_type = /obj/item/shrapnel/bullet/c38/dumdum /obj/projectile/bullet/c38/trac name = ".38 TRAC bullet" damage = 10 + ricochets_max = 0 /obj/projectile/bullet/c38/trac/on_hit(atom/target, blocked = FALSE) . = ..() @@ -36,6 +69,7 @@ /obj/projectile/bullet/c38/hotshot //similar to incendiary bullets, but do not leave a flaming trail name = ".38 Hot Shot bullet" damage = 20 + ricochets_max = 0 /obj/projectile/bullet/c38/hotshot/on_hit(atom/target, blocked = FALSE) . = ..() @@ -48,6 +82,7 @@ name = ".38 Iceblox bullet" damage = 20 var/temperature = 100 + ricochets_max = 0 /obj/projectile/bullet/c38/iceblox/on_hit(atom/target, blocked = FALSE) . = ..() @@ -60,3 +95,13 @@ /obj/projectile/bullet/a357 name = ".357 bullet" damage = 60 + +// admin only really, for ocelot memes +/obj/projectile/bullet/a357/match + name = ".357 match bullet" + ricochets_max = 5 + ricochet_chance = 140 + ricochet_auto_aim_angle = 50 + ricochet_auto_aim_range = 6 + ricochet_incidence_leeway = 80 + ricochet_decay_chance = 1 diff --git a/code/modules/spells/spell_types/summonitem.dm b/code/modules/spells/spell_types/summonitem.dm index 37237dbc144..9132436b283 100644 --- a/code/modules/spells/spell_types/summonitem.dm +++ b/code/modules/spells/spell_types/summonitem.dm @@ -78,18 +78,6 @@ break M.dropItemToGround(item_to_retrieve) - if(iscarbon(M)) //Edge case housekeeping - var/mob/living/carbon/C = M - for(var/X in C.bodyparts) - var/obj/item/bodypart/part = X - if(item_to_retrieve in part.embedded_objects) - part.embedded_objects -= item_to_retrieve - to_chat(C, "The [item_to_retrieve] that was embedded in your [L] has mysteriously vanished. How fortunate!") - if(!C.has_embedded_objects()) - C.clear_alert("embeddedobject") - SEND_SIGNAL(C, COMSIG_CLEAR_MOOD_EVENT, "embedded") - break - else if(istype(item_to_retrieve.loc, /obj/machinery/portable_atmospherics/)) //Edge cases for moved machinery var/obj/machinery/portable_atmospherics/P = item_to_retrieve.loc diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm index ea1298c3661..a91837e9fe0 100644 --- a/code/modules/surgery/bodyparts/helpers.dm +++ b/code/modules/surgery/bodyparts/helpers.dm @@ -137,29 +137,24 @@ disabled += zone return disabled -//Remove all embedded objects from all limbs on the carbon mob -/mob/living/carbon/proc/remove_all_embedded_objects() - var/turf/T = get_turf(src) +///Remove a specific embedded item from the carbon mob +/mob/living/carbon/proc/remove_embedded_object(obj/item/I) + SEND_SIGNAL(src, COMSIG_CARBON_EMBED_REMOVAL, I) +///Remove all embedded objects from all limbs on the carbon mob +/mob/living/carbon/proc/remove_all_embedded_objects() for(var/X in bodyparts) var/obj/item/bodypart/L = X for(var/obj/item/I in L.embedded_objects) - L.embedded_objects -= I - I.forceMove(T) - I.unembedded() - - clear_alert("embeddedobject") - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "embedded") + remove_embedded_object(I) /mob/living/carbon/proc/has_embedded_objects(include_harmless=FALSE) - . = 0 for(var/X in bodyparts) var/obj/item/bodypart/L = X for(var/obj/item/I in L.embedded_objects) - if(!include_harmless && I.is_embed_harmless()) + if(!include_harmless && I.isEmbedHarmless()) continue - return 1 - + return TRUE //Helper for quickly creating a new limb - used by augment code in species.dm spec_attacked_by /mob/living/carbon/proc/newBodyPart(zone, robotic, fixed_icon) diff --git a/code/modules/surgery/remove_embedded_object.dm b/code/modules/surgery/remove_embedded_object.dm index cac7924c846..c4e923b4b99 100644 --- a/code/modules/surgery/remove_embedded_object.dm +++ b/code/modules/surgery/remove_embedded_object.dm @@ -30,7 +30,7 @@ var/objects = 0 for(var/obj/item/I in L.embedded_objects) objects++ - SEND_SIGNAL(H, COMSIG_HUMAN_EMBED_REMOVAL, I, L) + H.remove_embedded_object(I) if(objects > 0) display_results(user, target, "You successfully remove [objects] objects from [H]'s [L.name].", diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm index 90e8dd50520..ca43beab618 100644 --- a/code/modules/uplink/uplink_items.dm +++ b/code/modules/uplink/uplink_items.dm @@ -801,6 +801,13 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item)) mixture that'll ignite anyone struck by the bullet. Some men just want to watch the world burn." item = /obj/item/ammo_box/magazine/mm712x82/incen +/datum/uplink_item/ammo/machinegun/match + name = "7.12x82mm (Match) Box Magazine" + desc = "A 50-round magazine of 7.12x82mm ammunition for use in the L6 SAW; you didn't know there was a demand for match grade \ + precision bullet hose ammo, but these rounds are finely tuned and perfect for ricocheting off walls all fancy-like." + item = /obj/item/ammo_box/magazine/mm712x82/match + cost = 10 + /datum/uplink_item/ammo/rocket include_modes = list(/datum/game_mode/nuclear) diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index 4e9d617b685..fc8f33daccc 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -534,10 +534,10 @@ GLOBAL_LIST_EMPTY(vending_products) for(var/i = 0, i < num_shards, i++) var/obj/item/shard/shard = new /obj/item/shard(get_turf(C)) shard.embedding = list(embed_chance = 100, ignore_throwspeed_threshold = TRUE, impact_pain_mult=1, pain_chance=5) - shard.AddElement(/datum/element/embed, shard.embedding) + shard.updateEmbedding() C.hitby(shard, skipcatch = TRUE, hitpush = FALSE) shard.embedding = list() - shard.AddElement(/datum/element/embed, shard.embedding) + shard.updateEmbedding() if(4) // paralyze this binch // the new paraplegic gets like 4 lines of losing their legs so skip them visible_message("[C]'s spinal cord is obliterated with a sickening crunch!", ignored_mobs = list(C)) diff --git a/code/modules/vending/security.dm b/code/modules/vending/security.dm index 3ad57d1eddb..45509c454b2 100644 --- a/code/modules/vending/security.dm +++ b/code/modules/vending/security.dm @@ -20,7 +20,8 @@ /obj/item/coin/antagtoken = 1, /obj/item/clothing/head/helmet/blueshirt = 1, /obj/item/clothing/suit/armor/vest/blueshirt = 1, - /obj/item/clothing/gloves/tackler = 5) + /obj/item/clothing/gloves/tackler = 5, + /obj/item/grenade/stingbang = 1) refill_canister = /obj/item/vending_refill/security default_price = 650 extra_price = 700 diff --git a/tgstation.dme b/tgstation.dme index f03387ddd2e..6aca9e76c96 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -320,7 +320,6 @@ #include "code\datums\dna.dm" #include "code\datums\dog_fashion.dm" #include "code\datums\ductnet.dm" -#include "code\datums\embedding_behavior.dm" #include "code\datums\emotes.dm" #include "code\datums\ert.dm" #include "code\datums\explosion.dm" @@ -408,11 +407,13 @@ #include "code\datums\components\manual_breathing.dm" #include "code\datums\components\material_container.dm" #include "code\datums\components\mirage_border.dm" +#include "code\datums\components\mirv.dm" #include "code\datums\components\mood.dm" #include "code\datums\components\nanites.dm" #include "code\datums\components\ntnet_interface.dm" #include "code\datums\components\orbiter.dm" #include "code\datums\components\paintable.dm" +#include "code\datums\components\pellet_cloud.dm" #include "code\datums\components\pricetag.dm" #include "code\datums\components\punchcooldown.dm" #include "code\datums\components\rad_insulation.dm" @@ -421,7 +422,6 @@ #include "code\datums\components\riding.dm" #include "code\datums\components\rot.dm" #include "code\datums\components\rotation.dm" -#include "code\datums\components\shrapnel.dm" #include "code\datums\components\shrink.dm" #include "code\datums\components\sizzle.dm" #include "code\datums\components\slippery.dm" @@ -975,6 +975,7 @@ #include "code\game\objects\items\sharpener.dm" #include "code\game\objects\items\shields.dm" #include "code\game\objects\items\shooting_range.dm" +#include "code\game\objects\items\shrapnel.dm" #include "code\game\objects\items\signs.dm" #include "code\game\objects\items\singularityhammer.dm" #include "code\game\objects\items\spear.dm"