Conflicts!!!

This commit is contained in:
Artur
2020-06-22 17:39:24 +03:00
395 changed files with 84463 additions and 336540 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

@@ -109,7 +109,7 @@
AM.visible_message("<span class='boldwarning'>[AM] falls into [parent]!</span>", "<span class='userdanger'>[oblivion_message]</span>")
if (isliving(AM))
var/mob/living/L = AM
L.notransform = TRUE
L.mob_transforming = TRUE
L.Paralyze(200)
var/oldtransform = AM.transform

View File

@@ -124,9 +124,9 @@
category = CAT_MISC
subcategory = CAT_TOOL
/datum/crafting_recipe/bruise_pack
name = "Bruise Pack"
result = /obj/item/stack/medical/bruise_pack/one
/datum/crafting_recipe/brute_pack
name = "Suture Pack"
result = /obj/item/stack/medical/suture/one
time = 1
reqs = list(/obj/item/stack/medical/gauze = 1,
/datum/reagent/medicine/styptic_powder = 10)
@@ -134,8 +134,8 @@
subcategory = CAT_TOOL
/datum/crafting_recipe/burn_pack
name = "Burn Ointment"
result = /obj/item/stack/medical/ointment/one
name = "Regenerative Mesh"
result = /obj/item/stack/medical/mesh/one
time = 1
reqs = list(/obj/item/stack/medical/gauze = 1,
/datum/reagent/medicine/silver_sulfadiazine = 10)

View File

@@ -0,0 +1,363 @@
/*
This component is responsible for handling individual instances of embedded objects. The embeddable element is what allows an item to be embeddable and stores its embedding stats,
and when it impacts and meets the requirements to stick into something, it instantiates an embedded component. Once the item falls out, the component is destroyed, while the
element survives to embed another day.
There are 2 different things that can be embedded presently: carbons, and closed turfs (see: walls)
- Carbon embedding has all the classical embedding behavior, and tracks more events and signals. The main behaviors and hooks to look for are:
-- Every process tick, there is a chance to randomly proc pain, controlled by pain_chance. There may also be a chance for the object to fall out randomly, per fall_chance
-- Every time the mob moves, there is a chance to proc jostling pain, controlled by jostle_chance (and only 50% as likely if the mob is walking or crawling)
-- Various signals hooking into carbon topic() and the embed removal surgery in order to handle removals.
- 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"
- Sticking occurs when an item should not cause any harm while embedding (imagine throwing a sticky ball of tape at someone, rather than a shuriken). An item is considered "sticky"
when it has 0 for both pain multiplier and jostle pain multiplier. It's a bit arbitrary, but fairly straightforward.
Stickables differ from embeds in the following ways:
-- Text descriptors use phrasing like "X is stuck to Y" rather than "X is embedded in Y"
-- There is no slicing sound on impact
-- All damage checks and bloodloss are skipped for carbons
-- Pointy objects create sparks when embedding into a turf
*/
/datum/component/embedded
dupe_mode = COMPONENT_DUPE_ALLOWED
var/obj/item/bodypart/limb
var/obj/item/weapon
// all of this stuff is explained in _DEFINES/combat.dm
var/embed_chance // not like we really need it once we're already stuck in but hey
var/fall_chance
var/pain_chance
var/pain_mult
var/impact_pain_mult
var/remove_pain_mult
var/rip_time
var/ignore_throwspeed_threshold
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
var/mutable_appearance/overlay
/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,
pain_mult = EMBEDDED_PAIN_MULTIPLIER,
remove_pain_mult = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER,
impact_pain_mult = EMBEDDED_IMPACT_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,
embed_chance_turf_mod = EMBED_CHANCE_TURF_MOD)
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
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
src.weapon = I
if(!weapon.isEmbedHarmless())
harmful = TRUE
weapon.embedded(parent)
if(iscarbon(parent))
initCarbon()
else if(isclosedturf(parent))
initTurf(throwingdatum)
/datum/component/embedded/RegisterWithParent()
if(iscarbon(parent))
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/jostleCheck)
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()
UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_CARBON_EMBED_RIP, COMSIG_CARBON_EMBED_REMOVAL, COMSIG_PARENT_EXAMINE))
/datum/component/embedded/process()
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)
qdel(overlay)
return ..()
////////////////////////////////////////
/////////////HUMAN PROCS////////////////
////////////////////////////////////////
/// 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/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 [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
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 [limb.name]!</span>",ignored_mobs=victim)
to_chat(victim, "<span class='userdanger'>[weapon] sticks itself to your [limb.name]!</span>")
/// 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/victim = parent
var/chance = jostle_chance
if(victim.m_intent == MOVE_INTENT_WALK || !(victim.mobility_flags & MOBILITY_STAND))
chance *= 0.5
if(harmful && prob(chance))
var/damage = weapon.w_class * jostle_pain_mult
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 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
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 [limb.name]!</span>", ignored_mobs=victim)
to_chat(victim, "<span class='userdanger'>[weapon] falls off of your [limb.name]!</span>")
safeRemoveCarbon()
/// 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()] [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 || !limb || weapon.loc != victim || !(weapon in limb.embedded_objects))
qdel(src)
return
if(harmful)
var/damage = weapon.w_class * remove_pain_mult
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()] [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()] [limb.name]!</span>", "<span class='notice'>You successfully remove [weapon] from your [limb.name].</span>")
safeRemoveCarbon(TRUE)
/// 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/safeRemoveCarbon(to_hands)
var/mob/living/carbon/victim = parent
limb.embedded_objects -= 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)
else
weapon.forceMove(get_turf(victim))
if(!victim.has_embedded_objects())
victim.clear_alert("embeddedobject")
SEND_SIGNAL(victim, COMSIG_CLEAR_MOOD_EVENT, "embedded")
qdel(src)
/// 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)
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
var/damage = weapon.w_class * pain_mult
var/chance = pain_chance
if(pain_stam_pct && IS_STAMCRIT(victim)) //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))
fallOutCarbon()
////////////////////////////////////////
//////////////TURF PROCS////////////////
////////////////////////////////////////
/// Turfs are much lower maintenance, since we don't care if they're in pain, but since they don't bleed or scream, we draw an overlay to show their status.
/// The only difference pointy/sticky items make here is text descriptors and pointy objects making a spark shower on impact.
/datum/component/embedded/proc/initTurf(datum/thrownthing/throwingdatum)
var/turf/closed/hit = parent
// we can't store the item IN the turf (cause turfs are just kinda... there), so we fake it by making the item invisible and bailing if it moves due to a blast
weapon.forceMove(hit)
weapon.invisibility = INVISIBILITY_ABSTRACT
RegisterSignal(weapon, COMSIG_MOVABLE_MOVED, .proc/itemMoved)
var/pixelX = rand(-2, 2)
var/pixelY = rand(-1, 3) // bias this upwards since in-hands are usually on the lower end of the sprite
switch(throwingdatum.init_dir)
if(NORTH)
pixelY -= 2
if(SOUTH)
pixelY += 2
if(WEST)
pixelX += 2
if(EAST)
pixelX -= 2
if(throwingdatum.init_dir in list(NORTH, WEST, NORTHWEST, SOUTHWEST))
overlay = mutable_appearance(icon=weapon.righthand_file,icon_state=weapon.item_state)
else
overlay = mutable_appearance(icon=weapon.lefthand_file,icon_state=weapon.item_state)
var/matrix/M = matrix()
M.Translate(pixelX, pixelY)
overlay.transform = M
hit.add_overlay(overlay, TRUE)
if(harmful)
hit.visible_message("<span class='danger'>[weapon] embeds itself in [hit]!</span>")
playsound(hit,'sound/weapons/bladeslice.ogg', 70)
var/datum/effect_system/spark_spread/sparks = new
sparks.set_up(1, 1, parent)
sparks.attach(parent)
sparks.start()
else
hit.visible_message("<span class='danger'>[weapon] sticks itself to [hit]!</span>")
/datum/component/embedded/proc/examineTurf(datum/source, mob/user, list/examine_list)
if(harmful)
examine_list += "\t <a href='?src=[REF(src)];embedded_object=[REF(weapon)]' class='warning'>There is \a [weapon] embedded in [parent]!</a>"
else
examine_list += "\t <a href='?src=[REF(src)];embedded_object=[REF(weapon)]' class='warning'>There is \a [weapon] stuck to [parent]!</a>"
/// Someone is ripping out the item from the turf by hand
/datum/component/embedded/Topic(datum/source, href_list)
var/mob/living/us = usr
if(in_range(us, parent) && locate(href_list["embedded_object"]) == weapon)
if(harmful)
us.visible_message("<span class='notice'>[us] begins unwedging [weapon] from [parent].</span>", "<span class='notice'>You begin unwedging [weapon] from [parent]...</span>")
else
us.visible_message("<span class='notice'>[us] begins unsticking [weapon] from [parent].</span>", "<span class='notice'>You begin unsticking [weapon] from [parent]...</span>")
if(do_after(us, 30, target = parent))
us.put_in_hands(weapon)
weapon.unembedded()
qdel(src)
/// This proc handles if something knocked the invisible item loose from the turf somehow (probably an explosion). Just make it visible and say it fell loose, then get outta here.
/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,74 @@
/**
* omen.dm: For when you want someone to have a really bad day
*
* When you attach an omen component to someone, they start running the risk of all sorts of bad environmental injuries, like nearby vending machines randomly falling on you,
* or hitting your head really hard when you slip and fall, or... well, for now those two are all I have. More will come.
*
* Omens are removed once the victim is either maimed by one of the possible injuries, or if they receive a blessing (read: bashing with a bible) from the chaplain.
*/
/datum/component/omen
dupe_mode = COMPONENT_DUPE_UNIQUE
/// Whatever's causing the omen, if there is one. Destroying the vessel won't stop the omen, but we destroy the vessel (if one exists) upon the omen ending
var/obj/vessel
/datum/component/omen/Initialize(silent=FALSE, vessel)
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
var/mob/person = parent
if(!silent)
to_chat(person, "<span class='warning'>You get a bad feeling...</span>")
src.vessel = vessel
/datum/component/omen/Destroy(force, silent)
if(vessel)
vessel.visible_message("<span class='warning'>[vessel] burns up in a sinister flash, taking an evil energy with it...</span>")
vessel = null
return ..()
/datum/component/omen/RegisterWithParent()
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/check_accident)
RegisterSignal(parent, COMSIG_LIVING_STATUS_KNOCKDOWN, .proc/check_slip)
RegisterSignal(parent, COMSIG_ADD_MOOD_EVENT, .proc/check_bless)
/datum/component/omen/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_LIVING_STATUS_KNOCKDOWN, COMSIG_MOVABLE_MOVED, COMSIG_ADD_MOOD_EVENT))
/**
* check_accident() is called each step we take
*
* While we're walking around, roll to see if there's any environmental hazards (currently only vending machines) on one of the adjacent tiles we can trigger.
* We do the prob() at the beginning to A. add some tension for /when/ it will strike, and B. (more importantly) ameliorate the fact that we're checking up to 5 turfs's contents each time
*/
/datum/component/omen/proc/check_accident(atom/movable/our_guy)
if(!prob(15))
return
for(var/t in get_adjacent_open_turfs(our_guy))
var/turf/the_turf = t
for(var/obj/machinery/vending/darth_vendor in the_turf)
if(darth_vendor.tiltable)
darth_vendor.tilt(our_guy)
qdel(src)
return
/// If we get knocked down, see if we have a really bad slip and bash our head hard
/datum/component/omen/proc/check_slip(mob/living/our_guy, amount)
if(amount <= 0 || prob(50)) // 50% chance to bonk our head
return
var/obj/item/bodypart/the_head = our_guy.get_bodypart(BODY_ZONE_HEAD)
if(!the_head)
return
playsound(get_turf(our_guy), "sound/effects/tableheadsmash.ogg", 90, TRUE)
our_guy.visible_message("<span class='danger'>[our_guy] hits [our_guy.p_their()] head really badly falling down!</span>", "<span class='userdanger'>You hit your head really badly falling down!</span>")
the_head.receive_damage(75)
our_guy.adjustOrganLoss(ORGAN_SLOT_BRAIN, 100)
qdel(src)
/// Hijack the mood system to see if we get the blessing mood event to cancel the omen
/datum/component/omen/proc/check_bless(mob/living/our_guy, category)
if(category != "blessing")
return
to_chat(our_guy, "<span class='nicegreen'>You feel a horrible omen lifted off your shoulders!</span>")
qdel(src)

View File

@@ -0,0 +1,283 @@
/*
* 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/item/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/item/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/item/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/item/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>")
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)