Files
Bubberstation/code/datums/elements/embed.dm
SkyratBot 45d9944091 [MIRROR] Heretic Knock Path [MDB IGNORE] (#23810)
* Heretic Knock Path (#78108)

## About The Pull Request

other changes: GODMODEd mobs cannot receive embeds or bleed, admins can
now use the traitor panel to give heretics a focus

adds a new heretic path, the path of knock
its a path about opening shit and having access
wound opening included, and stealing
this is its award icon

![ascended](https://github.com/tgstation/tgstation/assets/70376633/01473bf2-5c44-4574-850c-83fb5db204fd)
its knowledge is as follows:

### A Locksmith’s Secret
starting knowledge, unlocks the key blade which also works as a crowbar

https://github.com/tgstation/tgstation/assets/70376633/3690232d-5687-4b0c-a9cc-b6374e7f1850

### Grasp of Knock
it literally just opens stuff (also makes a knocking sound)
unbolts bolted airlocks and opens them, opens locked closets, opens
mechas, logs you into consoles
(comms consoles are with barebones head-level access, no buying shuttle,
but hey you can shitpost over comms)
Sidepaths: Ashen Eyes, Codex Cicatrix

https://github.com/tgstation/tgstation/assets/70376633/8b890d69-ee03-4d12-99dd-dde7b4483cd4

### Key Keepers Burden
transmute a rod,wallet, and some id card to create an eldritch id card
(very original naming), the ID card used is not preserved
this ID card functions essentially as a superior agent card, using other
IDs on it makes it be consumed by the eldritch ID and have its accesses
and forms added into it, you can use it inhand to turn it into any of
the cards that were consumed
in addition you can hit two airlocks with it to link them together to
create portals under the doors, which has a green glow
going through the portal as a Heretic gets you to the other destination
going through as a nonheretic lands you in a random onstation airlock,
SM chamber included if youre unlucky
1 id card can only have 1 set of portals, making another destroys the
former set, one of the airlocks being destroyed also destroys them

https://github.com/tgstation/tgstation/assets/70376633/e96a518e-b35d-44aa-9a7c-8f2103feab6f

### Rite Of Passage
transmute a white crayon, a multitool, and a plank to create consecrated
lintel
heretics can use this cool looking book to create a 8 second shield that
knocks back any nonheretic that tries to pass
also its ranged

https://github.com/tgstation/tgstation/assets/70376633/036e0875-c422-433e-87b3-71328cb2bf8a

### Mark Of Knock
the mansus grasp will now mark its victim for like 10 seconds
marked victims are denied access by all objects, public airlocks
included

https://github.com/tgstation/tgstation/assets/70376633/6187ef36-30f4-4a92-af21-e5b288afb869

### Burglars Finesse
steal a random item from the victims backpack (or other storage item if
they dont have a backpack) and puts it into your hand
the victim will probably hear you and also gets a chat message about
this

https://github.com/tgstation/tgstation/assets/70376633/2529fa78-616d-4a46-ae18-3cb22efb1ab1

### Ritual of knowledge
this is nothing new i put this here to keep it in order

### Apetra Vulnera (sidepath with flesh)
the victim receives bleed wounds on every single bodypart that has more
than 15 brute
if they dont have a bodypart that has >= 15 brute they get a random
wound anyway so
side paths are: blood siphon and void cloak

https://github.com/tgstation/tgstation/assets/70376633/3c2cd21e-edbc-4956-8c2d-cd9a42b87f33

### Wave of Desperation (sidepath with flesh)
cannot be casted uncuffed with no bola, can be casted cuffed with no
bola, with a bola and no cuffs
adjacent mobs are knocked down, mobs are repulsed away, your cuffs and
bola are destroyed, and you gain a status effect that:
after 12 seconds makes you unconscious for 20 seconds
5 min cooldown

https://github.com/tgstation/tgstation/assets/70376633/da480921-d5dd-4b46-b2e8-0cf543640bf9

### Opening Blade
your blade has a 35% chance to cause a weeping avulsion on hit

https://github.com/tgstation/tgstation/assets/70376633/b6fd2837-6b0a-4a5a-bc7b-b9c3f7f715d1

### Caretakers Last Refuge
you can only cast this when not near sentient living beings
while in refuge you are invincible and near transparent, cannot use your
hands or spells
also immune to damage slowdown, being hit with a null rod cancels this
also if you lose your focus you get out of refuge

https://github.com/tgstation/tgstation/assets/70376633/f053cfd8-2a16-4195-8004-17df077983ca

https://github.com/tgstation/tgstation/assets/70376633/72330486-5273-4123-a108-b437b56120c4

### Many secrets behind the Spider Door (Ascension)
ritual needs 3 bodies without organs in their chest
when successfully performed you ascend and;
open a tear in reality (not the BoH one) which;
Polls all ghosts with sentient mob enabled to spawn and siege the
station, ghosts can interact with the portal to spawn as a random
eldritch mob
spawned mobs are loyal to whoever ascended and on examine can identify
their master
also fills the entire room with purple light

also the heretics opening blade is upgraded to a 65% chance, and they
gain Ascended Shapeshift which allows them to shapeshift into eldritch
mobs, and its not 1 choice only

https://github.com/tgstation/tgstation/assets/70376633/8d06286e-789d-442f-b33c-878d26deab07

## Why It's Good For The Game

its cool i think and an option for those wanting to steal and be sneaky
and stuff

## Changelog
🆑
add: heretic knock path and its respective items and award
/🆑

---------

Co-authored-by: MrMelbert <51863163+MrMelbert@ users.noreply.github.com>
Co-authored-by: Jacquerel <hnevard@ gmail.com>

* Heretic Knock Path

---------

Co-authored-by: jimmyl <70376633+mc-oofert@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@ users.noreply.github.com>
Co-authored-by: Jacquerel <hnevard@ gmail.com>
2023-09-19 13:28:35 -07:00

218 lines
9.4 KiB
Plaintext

/*
The presence of this element allows an item (or a projectile carrying an item) to embed itself in a carbon when it is thrown into a target (whether by hand, gun, or explosive wave) with either
at least 4 throwspeed (EMBED_THROWSPEED_THRESHOLD) or ignore_throwspeed_threshold set to TRUE. Items meant to be used as shrapnel for projectiles should have ignore_throwspeed_threshold set to true.
Whether we're dealing with a direct /obj/item (throwing a knife at someone) or an /obj/projectile with a shrapnel_type, how we handle things plays out the same, with one extra step separating them.
Items simply make their COMSIG_MOVABLE_IMPACT_ZONE check, while projectiles check on COMSIG_PROJECTILE_SELF_ON_HIT.
Upon a projectile hitting a valid target, it spawns whatever type of payload it has defined, then has that try to embed itself in the target on its own.
Otherwise non-embeddable or stickable items can be made embeddable/stickable through wizard events/sticky tape/admin memes.
*/
/datum/element/embed
element_flags = ELEMENT_BESPOKE
argument_hash_start_idx = 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
var/fall_chance
var/pain_chance
var/pain_mult
var/remove_pain_mult
var/impact_pain_mult
var/rip_time
var/ignore_throwspeed_threshold
var/jostle_chance
var/jostle_pain_mult
var/pain_stam_pct
var/payload_type
/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, projectile_payload=/obj/item/shard)
. = ..()
if(!isitem(target) && !isprojectile(target))
return ELEMENT_INCOMPATIBLE
RegisterSignal(target, COMSIG_ELEMENT_ATTACH, PROC_REF(severancePackage))
if(isitem(target))
RegisterSignal(target, COMSIG_MOVABLE_IMPACT_ZONE, PROC_REF(checkEmbed))
RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(examined))
RegisterSignal(target, COMSIG_EMBED_TRY_FORCE, PROC_REF(tryForceEmbed))
RegisterSignal(target, COMSIG_ITEM_DISABLE_EMBED, PROC_REF(detachFromWeapon))
else
payload_type = projectile_payload
RegisterSignal(target, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(checkEmbedProjectile))
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
initialized = TRUE
/datum/element/embed/Detach(obj/target)
. = ..()
if(isitem(target))
UnregisterSignal(target, list(COMSIG_MOVABLE_IMPACT_ZONE, COMSIG_ELEMENT_ATTACH, COMSIG_MOVABLE_IMPACT, COMSIG_ATOM_EXAMINE, COMSIG_EMBED_TRY_FORCE, COMSIG_ITEM_DISABLE_EMBED))
else
UnregisterSignal(target, list(COMSIG_PROJECTILE_SELF_ON_HIT, COMSIG_ELEMENT_ATTACH))
/// Checking to see if we're gonna embed into a human
/datum/element/embed/proc/checkEmbed(obj/item/weapon, mob/living/carbon/victim, hit_zone, blocked, datum/thrownthing/throwingdatum, forced=FALSE)
SIGNAL_HANDLER
if(forced)
embed_object(weapon, victim, hit_zone, throwingdatum)
return TRUE
if(blocked || !istype(victim) || HAS_TRAIT(victim, TRAIT_PIERCEIMMUNE))
return FALSE
if(victim.status_flags & GODMODE)
return FALSE
var/flying_speed = throwingdatum?.speed || weapon.throw_speed
if(flying_speed < EMBED_THROWSPEED_THRESHOLD && !ignore_throwspeed_threshold)
return FALSE
if(!roll_embed_chance(weapon, victim, hit_zone, throwingdatum))
return FALSE
embed_object(weapon, victim, hit_zone, throwingdatum)
return TRUE
/// Actually sticks the object to a victim
/datum/element/embed/proc/embed_object(obj/item/weapon, mob/living/carbon/victim, hit_zone, datum/thrownthing/throwingdatum)
var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || pick(victim.bodyparts)
victim.AddComponent(/datum/component/embedded,\
weapon,\
throwingdatum,\
part = limb,\
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)
///A different embed element has been attached, so we'll detach and let them handle things
/datum/element/embed/proc/severancePackage(obj/weapon, datum/element/E)
SIGNAL_HANDLER
if(istype(E, /datum/element/embed))
Detach(weapon)
///If we don't want to be embeddable anymore (deactivating an e-dagger for instance)
/datum/element/embed/proc/detachFromWeapon(obj/weapon)
SIGNAL_HANDLER
Detach(weapon)
///Someone inspected our embeddable item
/datum/element/embed/proc/examined(obj/item/I, mob/user, list/examine_list)
SIGNAL_HANDLER
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, we create the shrapnel_type object and then forcefully try to embed it on its
* behalf. DO NOT EVER add an embed element to the payload and let it do the rest.
* That's awful, and it'll limit us to drop-deletable shrapnels in the worry of stuff like
* arrows and harpoons being embeddable even when not let loose by their weapons.
*/
/datum/element/embed/proc/checkEmbedProjectile(obj/projectile/source, atom/movable/firer, atom/hit, angle, hit_zone)
SIGNAL_HANDLER
if(!source.can_embed_into(hit))
Detach(source)
return // we don't care
var/obj/item/payload = new payload_type(get_turf(hit))
if(istype(payload, /obj/item/shrapnel/bullet))
payload.name = source.name
SEND_SIGNAL(source, COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED, payload)
var/mob/living/carbon/C = hit
var/obj/item/bodypart/limb = C.get_bodypart(hit_zone)
if(!limb)
limb = C.get_bodypart()
if(!tryForceEmbed(payload, limb))
payload.failedEmbed()
Detach(source)
/**
* 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, and possibly
* AGAIN if we actually embed. This way, we save on at least one message.
*
* Arguments:
* * embedding_item- the item we're trying to insert into the target
* * target- what we're trying to shish-kabob, either a bodypart or a carbon
* * hit_zone- if our target is a carbon, try to hit them in this zone, if we don't have one, pick a random one. If our target is a bodypart, we already know where we're hitting.
* * forced- if we want this to succeed 100%
*/
/datum/element/embed/proc/tryForceEmbed(obj/item/embedding_item, atom/target, hit_zone, forced=FALSE)
SIGNAL_HANDLER
var/obj/item/bodypart/limb
var/mob/living/carbon/victim
if(iscarbon(target))
victim = target
if(!hit_zone)
limb = pick(victim.bodyparts)
hit_zone = limb.body_zone
else if(isbodypart(target))
limb = target
hit_zone = limb.body_zone
victim = limb.owner
if(!forced && !roll_embed_chance(embedding_item, victim, hit_zone))
return
return checkEmbed(embedding_item, victim, hit_zone, forced=TRUE) // Don't repeat the embed roll, we already did it
/// Calculates the actual chance to embed based on armour penetration and throwing speed, then returns true if we pass that probability check
/datum/element/embed/proc/roll_embed_chance(obj/item/embedding_item, mob/living/victim, hit_zone, datum/thrownthing/throwingdatum)
var/actual_chance = embed_chance
if(throwingdatum?.speed > embedding_item.throw_speed)
actual_chance += (throwingdatum.speed - embedding_item.throw_speed) * EMBED_CHANCE_SPEED_BONUS
if(embedding_item.isEmbedHarmless()) // all the armor in the world won't save you from a kick me sign
return prob(actual_chance)
var/armor = max(victim.run_armor_check(hit_zone, BULLET, silent=TRUE), victim.run_armor_check(hit_zone, BOMB, silent=TRUE)) * 0.5 // we'll be nice and take the better of bullet and bomb armor, halved
if(!armor) // we only care about armor penetration if there's actually armor to penetrate
return prob(actual_chance)
//Keep this above 1, as it is a multiplier for the pen_mod for determining actual embed chance.
var/penetrative_behaviour = embedding_item.weak_against_armour ? ARMOR_WEAKENED_MULTIPLIER : 1
var/pen_mod = -(armor * penetrative_behaviour) // if our shrapnel is weak into armor, then we restore our armor to the full value.
actual_chance += pen_mod // doing the armor pen as a separate calc just in case this ever gets expanded on
if(actual_chance <= 0)
victim.visible_message(span_danger("[embedding_item] bounces off [victim]'s armor, unable to embed!"), span_notice("[embedding_item] bounces off your armor, unable to embed!"), vision_distance = COMBAT_MESSAGE_RANGE)
return FALSE
return prob(actual_chance)