This commit is contained in:
Timothy Teakettle
2020-06-10 23:14:25 +01:00
parent 8ec5672b85
commit 4c55e86da7
74 changed files with 1495 additions and 386 deletions

View File

@@ -38,8 +38,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)

View File

@@ -3,16 +3,17 @@
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.
In addition, there are 2 cases of embedding: embedding, and sticking
- Embedding involves harmful and dangerous embeds, whether they cause brute damage, stamina damage, or a mix. This is the default behavior for embeddings, for when something is "pointy"
@@ -23,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
*/
@@ -31,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
@@ -46,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
@@ -53,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,
@@ -63,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
@@ -79,35 +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)
@@ -119,84 +127,107 @@
/////////////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("<span class='danger'>[weapon] embeds itself in [victim]'s [L.name]!</span>","<span class='userdanger'>[weapon] embeds itself in your [L.name]!</span>")
victim.visible_message("<span class='danger'>[weapon] embeds itself in [victim]'s [limb.name]!</span>",ignored_mobs=victim)
to_chat(victim, "<span class='userdanger'>[weapon] embeds itself in your [limb.name]!</span>")
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("<span class='danger'>[weapon] sticks itself to [victim]'s [L.name]!</span>","<span class='userdanger'>[weapon] sticks itself to your [L.name]!</span>")
victim.visible_message("<span class='danger'>[weapon] sticks itself to [victim]'s [limb.name]!</span>",ignored_mobs=victim)
to_chat(victim, "<span class='userdanger'>[weapon] sticks itself to your [limb.name]!</span>")
/// 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.lying)
if(victim.m_intent == MOVE_INTENT_WALK || !(victim.mobility_flags & MOBILITY_STAND))
chance *= 0.5
if(prob(chance))
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, "<span class='userdanger'>[weapon] embedded in your [L.name] jostles and stings!</span>")
limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage)
to_chat(victim, "<span class='userdanger'>[weapon] embedded in your [limb.name] jostles and stings!</span>")
/// 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("<span class='danger'>[weapon] falls out of [victim.name]'s [L.name]!</span>","<span class='userdanger'>[weapon] falls out of your [L.name]!</span>")
limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage)
victim.visible_message("<span class='danger'>[weapon] falls out of [victim.name]'s [limb.name]!</span>", ignored_mobs=victim)
to_chat(victim, "<span class='userdanger'>[weapon] falls out of your [limb.name]!</span>")
else
victim.visible_message("<span class='danger'>[weapon] falls off of [victim.name]'s [L.name]!</span>","<span class='userdanger'>[weapon] falls off of your [L.name]!</span>")
victim.visible_message("<span class='danger'>[weapon] falls off of [victim.name]'s [limb.name]!</span>", ignored_mobs=victim)
to_chat(victim, "<span class='userdanger'>[weapon] falls off of your [limb.name]!</span>")
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("<span class='warning'>[victim] attempts to remove [weapon] from [victim.p_their()] [L.name].</span>","<span class='notice'>You attempt to remove [weapon] from your [L.name]... (It will take [DisplayTimeText(time_taken)].)</span>")
victim.visible_message("<span class='warning'>[victim] attempts to remove [weapon] from [victim.p_their()] [limb.name].</span>","<span class='notice'>You attempt to remove [weapon] from your [limb.name]... (It will take [DisplayTimeText(time_taken)].)</span>")
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("<span class='notice'>[victim] successfully rips [weapon] out of [victim.p_their()] [L.name]!</span>", "<span class='notice'>You successfully remove [weapon] from your [L.name].</span>")
victim.visible_message("<span class='notice'>[victim] successfully rips [weapon] out of [victim.p_their()] [limb.name]!</span>", "<span class='notice'>You successfully remove [weapon] from your [limb.name].</span>")
else
victim.visible_message("<span class='notice'>[victim] successfully rips [weapon] off of [victim.p_their()] [L.name]!</span>", "<span class='notice'>You successfully remove [weapon] from your [L.name].</span>")
victim.visible_message("<span class='notice'>[victim] successfully rips [weapon] off of [victim.p_their()] [limb.name]!</span>", "<span class='notice'>You successfully remove [weapon] from your [limb.name].</span>")
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)
@@ -209,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, "<span class='userdanger'>\The [weapon] that was embedded in your [limb.name] disappears!</span>")
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, "<span class='userdanger'>[weapon] embedded in your [L.name] hurts!</span>")
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, "<span class='userdanger'>[weapon] embedded in your [limb.name] hurts!</span>")
if(prob(fall_chance))
fallOutHuman()
fallOutCarbon()
////////////////////////////////////////
@@ -297,6 +349,7 @@
if(do_after(us, 30, target = parent))
us.put_in_hands(weapon)
weapon.unembedded()
qdel(src)
@@ -304,4 +357,5 @@
/datum/component/embedded/proc/itemMoved()
weapon.invisibility = initial(weapon.invisibility)
weapon.visible_message("<span class='notice'>[weapon] falls loose from [parent].</span>")
weapon.unembedded()
qdel(src)

View File

@@ -144,7 +144,7 @@
var/obj/item/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

View File

@@ -1,36 +1,37 @@
/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
if(isgrenade(parent))
parent.AddComponent(/datum/component/pellet_cloud, projectile_type=projectile_type)
/datum/component/shrapnel/RegisterWithParent()
/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/item/projectile/P = new projectile_type(target_turf)
var/obj/item/projectile/P = new projectile_type(target_turf)
//Shooting Code:
P.range = radius+1
if(override_projectile_range)

View File

@@ -0,0 +1,287 @@
/*
* 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
/// What's the projectile path of the shrapnel we're shooting?
var/projectile_type
/// 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/num_pellets
/// 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/radius = 4
/// The list of pellets we're responsible for tracking, once these are all accounted for, we finalize.
var/list/pellets = 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/targets_hit = list()
/// For grenades, any /mob/living's the grenade is moved onto, see [/datum/component/pellet_cloud/proc/handle_martyrs()]
var/list/bodies
/// For grenades, tracking people who die covering a grenade for achievement purposes, see [/datum/component/pellet_cloud/proc/handle_martyrs()]
var/list/purple_hearts
/// For grenades, tracking how many pellets are removed due to martyrs and how many pellets are added due to the last person to touch it being on top of it
var/pellet_delta = 0
/// how many pellets ranged out without hitting anything
var/terminated
/// how many pellets impacted something
var/hits
/// If the parent tried deleting and we're not done yet, we send it to nullspace then delete it after
var/queued_delete = FALSE
/// for if we're an ammo casing being fired
var/mob/living/shooter
/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()
RegisterSignal(parent, COMSIG_PARENT_PREQDELETED, .proc/nullspace_parent)
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_PARENT_PREQDELETED, 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/shell, 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)
shell.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(shell.BB, COMSIG_PROJECTILE_SELF_ON_HIT, .proc/pellet_hit)
RegisterSignal(shell.BB, list(COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PARENT_QDELETING), .proc/pellet_range)
pellets += shell.BB
if(!shell.throw_proj(target, targloc, shooter, params, spread))
return
if(i != num_pellets)
shell.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(obj/O, mob/living/lanced_by)
var/atom/A = parent
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
handle_martyrs(lanced_by) // 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 + pellet_delta
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(mob/living/lanced_by)
var/magnitude_absorbed
var/list/martyrs = list()
var/self_harm_radius_mult = 3
if(lanced_by && prob(60))
to_chat(lanced_by, "<span class='userdanger'>Your plan to whack someone with a grenade on a stick backfires on you, literally!</span>")
self_harm_radius_mult = 1 // we'll still give the guy who got hit some extra shredding, but not 3*radius
pellet_delta += radius
for(var/i in 1 to radius)
pew(lanced_by) // thought you could be tricky and lance someone with no ill effects!!
for(var/mob/living/body in get_turf(parent))
if(body == shooter)
pellet_delta += radius * self_harm_radius_mult
for(var/i in 1 to radius * self_harm_radius_mult)
pew(body) // free shrapnel if it goes off in your hand, and it doesn't even count towards the absorbed. fun!
else if(!(body in bodies))
martyrs += body // promoted from a corpse to a hero
for(var/M in martyrs)
var/mob/living/martyr = M
if(radius > 4)
martyr.visible_message("<b><span class='danger'>[martyr] heroically covers \the [parent] with [martyr.p_their()] body, absorbing a load of the shrapnel!</span></b>", "<span class='userdanger'>You heroically cover \the [parent] with your body, absorbing a load of the shrapnel!</span>")
magnitude_absorbed += round(radius * 0.5)
else if(radius >= 2)
martyr.visible_message("<b><span class='danger'>[martyr] heroically covers \the [parent] with [martyr.p_their()] body, absorbing some of the shrapnel!</span></b>", "<span class='userdanger'>You heroically cover \the [parent] with your body, absorbing some of the shrapnel!</span>")
magnitude_absorbed += 2
else
martyr.visible_message("<b><span class='danger'>[martyr] heroically covers \the [parent] with [martyr.p_their()] body, snuffing out the shrapnel!</span></b>", "<span class='userdanger'>You heroically cover \the [parent] with your body, snuffing out the shrapnel!</span>")
magnitude_absorbed = radius
var/pellets_absorbed = (radius ** 2) - ((radius - magnitude_absorbed - 1) ** 2)
radius -= magnitude_absorbed
pellet_delta -= round(pellets_absorbed * 0.5)
if(martyr.stat != DEAD && martyr.client)
LAZYADD(purple_hearts, martyr)
RegisterSignal(martyr, COMSIG_PARENT_QDELETING, .proc/on_target_qdel, override=TRUE)
for(var/i in 1 to round(pellets_absorbed * 0.5))
pew(martyr)
if(radius < 1)
break
///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]++
if(targets_hit[target] == 1)
RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/on_target_qdel, override=TRUE)
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]
UnregisterSignal(target, COMSIG_PARENT_QDELETING)
if(num_hits > 1)
target.visible_message("<span class='danger'>[target] is hit by [num_hits] [proj_name]s!</span>", null, null, COMBAT_MESSAGE_RANGE, target)
to_chat(target, "<span class='userdanger'>You're hit by [num_hits] [proj_name]s!</span>")
else
target.visible_message("<span class='danger'>[target] is hit by a [proj_name]!</span>", null, null, COMBAT_MESSAGE_RANGE, target)
to_chat(target, "<span class='userdanger'>You're hit by a [proj_name]!</span>")
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)
UnregisterSignal(parent, COMSIG_PARENT_PREQDELETED)
if(queued_delete)
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(obj/item/nade)
if(ismob(nade.loc))
shooter = nade.loc
LAZYINITLIST(bodies)
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/grenade_dropped)
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/grenade_moved)
RegisterSignal(parent, COMSIG_MOVABLE_UNCROSSED, .proc/grenade_uncrossed)
/// Someone dropped the grenade, so set them to the shooter in case they're on top of it when it goes off
/datum/component/pellet_cloud/proc/grenade_dropped(obj/item/nade, mob/living/slick_willy)
shooter = slick_willy
grenade_moved()
/// 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))
RegisterSignal(L, COMSIG_PARENT_QDELETING, .proc/on_target_qdel, override=TRUE)
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
/// Our grenade or landmine or caseless shell or whatever tried deleting itself, so we intervene and nullspace it until we're done here
/datum/component/pellet_cloud/proc/nullspace_parent()
var/atom/movable/AM = parent
AM.moveToNullspace()
queued_delete = TRUE
return TRUE
/// 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/on_target_qdel(atom/target)
UnregisterSignal(target, COMSIG_PARENT_QDELETING)
targets_hit -= target
bodies -= target
purple_hearts -= target

View File

@@ -206,7 +206,7 @@
user.set_resting(FALSE, TRUE, FALSE)
user.forceMove(get_turf(target))
target.adjustStaminaLoss(65)
target.Paralyze(10)
target.Paralyze(10)
target.DefaultCombatKnockdown(20)
if(ishuman(target) && iscarbon(user))
target.grabbedby(user)
@@ -415,10 +415,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.embedding = list()
shard.updateEmbedding()
W.obj_destruction()
user.adjustStaminaLoss(10 * speed)
user.DefaultCombatKnockdown(40)

View File

@@ -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,99 +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
/// 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(!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
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("<span class='danger'>[weapon] bounces off [victim]'s armor!</span>", "<span class='notice'>[weapon] bounces off your armor!</span>", 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)

View File

@@ -1,71 +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

View File

@@ -490,11 +490,7 @@
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("<span class='notice'>[spikey] falls out of [transfered]!</span>")
if(!transfered.has_embedded_objects())
transfered.clear_alert("embeddedobject")
SEND_SIGNAL(transfered, COMSIG_CLEAR_MOOD_EVENT, "embedded")
spikey.unembedded()