## About The Pull Request
Finishes #66471
At burden level nine (or through a deadly genetic breakdown), you now
turn into a psyker.
This splits your skull in half and transforms it into a weird fleshy
mass. You become blind, but your skull is perfectly suited for sending
out psychic waves. You get potent psy abilities.
First one is brainwave echolocation, inspired by Gehennites (but not as
laggy).
Secondly, you get the ability of Psychic Walls, which act similarly to
wizard ones, but last shorter, and cause projectiles to ricochet off
them.
Thirdly, you get a projectile boost ability, this temporarily lets you
fire guns twice as fast and gives them homing to the target you clicked.
Lastly, you get the ability of psychic projection. This terrifies the
victim, fucking their screen up and causing them to rapidfire any gun
they have in their general direction (they'll probably miss you)
With most of the abilities being based around guns, a burden level nine
chaplain now gets a new rite, Transmogrify. This lets them turn their
null rod into a 5-shot 18 damage .77 revolver. The revolver possesses a
weaker version of antimagic (protects against mind and unholy spells,
but not wizard/cult ones). It is reloaded by a prayer action (can also
only be performed by a max burdened person).
General Video: https://streamable.com/w3kkrk
Psychic Projection Video: https://streamable.com/4ibu7o

![image](https://user-images.githubusercontent.com/23585223/204150279-a6cf8e2f-c678-476e-b72c-6088cd8b684b.png)

## Why It's Good For The Game
Rewards the burdened chaplain with some pretty cool stuff for going
through hell like losing half his limbs, cause the current psychics dont
cut it as much as probably necessary, adds echolocation which can be
used for neat stuff in the future (bat organs for DNA infuser for
example).

## Changelog
🆑 Fikou, sprites from Halcyon, some old code from Basilman and
Armhulen.
refactor: Honorbound and Burdened mutations are brain traumas now.
add: Psykers. Become a psyker through the path of the burdened, or a
genetic breakdown.
add: Echolocation Component.
/🆑

Co-authored-by: tralezab <spamqetuo2@gmail.com>
Co-authored-by: tralezab <40974010+tralezab@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
This commit is contained in:
Fikou
2022-11-29 21:13:28 +01:00
committed by GitHub
parent f0d87f7554
commit 35b5ac0c4e
37 changed files with 985 additions and 377 deletions
+2 -1
View File
@@ -45,7 +45,8 @@
else if(ismob(parent))
RegisterSignal(parent, COMSIG_MOB_RECEIVE_MAGIC, PROC_REF(block_receiving_magic), override = TRUE)
RegisterSignal(parent, COMSIG_MOB_RESTRICT_MAGIC, PROC_REF(restrict_casting_magic), override = TRUE)
to_chat(parent, span_warning("Magic seems to flee from you. You are immune to spells but are unable to cast magic."))
if(!HAS_TRAIT(parent, TRAIT_ANTIMAGIC_NO_SELFBLOCK))
to_chat(parent, span_warning("Magic seems to flee from you. You are immune to spells but are unable to cast magic."))
else
return COMPONENT_INCOMPATIBLE
+183
View File
@@ -0,0 +1,183 @@
/datum/component/echolocation
/// Radius of our view.
var/echo_range = 4
/// Time between echolocations.
var/cooldown_time = 2 SECONDS
/// Time for the image to start fading out.
var/image_expiry_time = 1.5 SECONDS
/// Time for the image to fade in.
var/fade_in_time = 0.5 SECONDS
/// Time for the image to fade out and delete itself.
var/fade_out_time = 0.5 SECONDS
/// Are images static? If yes, spawns them on the turf and makes them not change location. Otherwise they change location and pixel shift with the original.
var/images_are_static = TRUE
/// With mobs that have this echo group in their echolocation receiver trait, we share echo images.
var/echo_group = null
/// Ref of the client color we give to the echolocator.
var/client_color
/// Associative list of world.time when created to a list of the images.
var/list/images = list()
/// Associative list of world.time when created to a list of receivers.
var/list/receivers = list()
/// All the saved appearances, keyed by icon-icon_state.
var/static/list/saved_appearances = list()
/// Typecache of all the allowed paths to render.
var/static/list/allowed_paths
/// Typecache of turfs that are dangerous, to give them a special icon.
var/static/list/danger_turfs
/// A matrix that turns everything except #ffffff into pure blackness, used for our images (the outlines are #ffffff).
var/static/list/black_white_matrix = list(85, 85, 85, 0, 85, 85, 85, 0, 85, 85, 85, 0, 0, 0, 0, 1, -254, -254, -254, 0)
/// Cooldown for the echolocation.
COOLDOWN_DECLARE(cooldown_last)
/datum/component/echolocation/Initialize(echo_range, cooldown_time, image_expiry_time, fade_in_time, fade_out_time, images_are_static, echo_group, echo_icon, color_path)
. = ..()
var/mob/living/echolocator = parent
if(!istype(echolocator))
return COMPONENT_INCOMPATIBLE
if(!danger_turfs)
danger_turfs = typecacheof(list(/turf/open/space, /turf/open/openspace, /turf/open/chasm, /turf/open/lava))
if(!allowed_paths)
allowed_paths = typecacheof(list(/turf/closed, /obj, /mob/living)) + danger_turfs
if(!isnull(echo_range))
src.echo_range = echo_range
if(!isnull(cooldown_time))
src.cooldown_time = cooldown_time
if(!isnull(image_expiry_time))
src.image_expiry_time = image_expiry_time
if(!isnull(fade_in_time))
src.fade_in_time = fade_in_time
if(!isnull(fade_out_time))
src.fade_out_time = fade_out_time
if(!isnull(images_are_static))
src.images_are_static = images_are_static
if(!isnull(echo_group))
src.echo_group = echo_group
if(!isnull(color_path))
client_color = echolocator.add_client_colour(color_path)
ADD_TRAIT(echolocator, TRAIT_ECHOLOCATION_RECEIVER, echo_group || REF(src))
echolocator.become_blind(ECHOLOCATION_TRAIT)
echolocator.overlay_fullscreen("echo", /atom/movable/screen/fullscreen/echo, echo_icon)
START_PROCESSING(SSfastprocess, src)
/datum/component/echolocation/Destroy(force, silent)
STOP_PROCESSING(SSfastprocess, src)
var/mob/living/echolocator = parent
QDEL_NULL(client_color)
REMOVE_TRAIT(echolocator, TRAIT_ECHOLOCATION_RECEIVER, echo_group || REF(src))
echolocator.cure_blind(ECHOLOCATION_TRAIT)
echolocator.clear_fullscreen("echo")
for(var/timeframe in images)
delete_images(timeframe)
return ..()
/datum/component/echolocation/process()
var/mob/living/echolocator = parent
if(echolocator.stat == DEAD)
return
echolocate()
/datum/component/echolocation/proc/echolocate()
if(!COOLDOWN_FINISHED(src, cooldown_last))
return
COOLDOWN_START(src, cooldown_last, cooldown_time)
var/mob/living/echolocator = parent
var/list/filtered = list()
var/list/seen = dview(echo_range, echolocator.loc)
for(var/atom/seen_atom as anything in seen)
if(seen_atom.invisibility > echolocator.see_invisible || !seen_atom.alpha)
continue
if(allowed_paths[seen_atom.type])
filtered += seen_atom
if(!length(filtered))
return
var/current_time = "[world.time]"
images[current_time] = list()
receivers[current_time] = list()
for(var/mob/living/viewer in filtered)
if(HAS_TRAIT_FROM(viewer, TRAIT_ECHOLOCATION_RECEIVER, echo_group))
receivers[current_time] += viewer
for(var/atom/filtered_atom as anything in filtered)
show_image(saved_appearances["[filtered_atom.icon]-[filtered_atom.icon_state]"] || generate_appearance(filtered_atom), filtered_atom, current_time)
addtimer(CALLBACK(src, .proc/fade_images, current_time), image_expiry_time)
/datum/component/echolocation/proc/show_image(image/input_appearance, atom/input, current_time)
var/image/final_image = image(input_appearance)
final_image.layer += EFFECTS_LAYER
final_image.plane = FULLSCREEN_PLANE
final_image.loc = images_are_static ? get_turf(input) : input
final_image.dir = input.dir
final_image.alpha = 0
if(images_are_static)
final_image.pixel_x = input.pixel_x
final_image.pixel_y = input.pixel_y
images[current_time] += final_image
for(var/mob/living/echolocate_receiver as anything in receivers[current_time])
if(echolocate_receiver == input)
continue
if(echolocate_receiver.client)
echolocate_receiver.client.images += final_image
animate(final_image, alpha = 255, time = fade_in_time)
/datum/component/echolocation/proc/generate_appearance(atom/input)
var/use_outline = TRUE
var/mutable_appearance/copied_appearance = new /mutable_appearance()
copied_appearance.appearance = input
if(istype(input, /obj/machinery/door/airlock)) //i hate you
copied_appearance.icon = 'icons/obj/doors/airlocks/station/public.dmi'
copied_appearance.icon_state = "closed"
else if(danger_turfs[input.type])
copied_appearance.icon = 'icons/turf/floors.dmi'
copied_appearance.icon_state = "danger"
use_outline = FALSE
copied_appearance.color = black_white_matrix
if(use_outline)
copied_appearance.filters += outline_filter(size = 1, color = COLOR_WHITE)
if(!images_are_static)
copied_appearance.pixel_x = 0
copied_appearance.pixel_y = 0
copied_appearance.transform = matrix()
if(!iscarbon(input)) //wacky overlay people get generated everytime
saved_appearances["[input.icon]-[input.icon_state]"] = copied_appearance
return copied_appearance
/datum/component/echolocation/proc/fade_images(from_when)
for(var/image_echo in images[from_when])
animate(image_echo, alpha = 0, time = fade_out_time)
addtimer(CALLBACK(src, .proc/delete_images, from_when), fade_out_time)
/datum/component/echolocation/proc/delete_images(from_when)
for(var/mob/living/echolocate_receiver as anything in receivers[from_when])
if(!echolocate_receiver.client)
continue
for(var/image_echo in images[from_when])
echolocate_receiver.client.images -= image_echo
images -= from_when
receivers -= from_when
/atom/movable/screen/fullscreen/echo
icon_state = "echo"
layer = ECHO_LAYER
show_when_dead = TRUE
/atom/movable/screen/fullscreen/echo/Initialize(mapload)
. = ..()
particles = new /particles/echo()
/atom/movable/screen/fullscreen/echo/Destroy()
QDEL_NULL(particles)
return ..()
/particles/echo
icon = 'icons/effects/particles/echo.dmi'
icon_state = list("echo1" = 1, "echo2" = 1, "echo3" = 2)
width = 480
height = 480
count = 1000
spawning = 0.5
lifespan = 2 SECONDS
fade = 1 SECONDS
gravity = list(0, -0.1)
position = generator(GEN_BOX, list(-240, -240), list(240, 240), NORMAL_RAND)
drift = generator(GEN_VECTOR, list(-0.1, 0), list(0.1, 0))
rotation = generator(GEN_NUM, 0, 360, NORMAL_RAND)
-6
View File
@@ -171,13 +171,7 @@
*/
/datum/component/religious_tool/proc/generate_available_sects(mob/user)
var/list/sects_to_pick = list()
var/human_highpriest = ishuman(user)
var/mob/living/carbon/human/highpriest = user
for(var/path in subtypesof(/datum/religion_sect))
if(human_highpriest && initial(easy_access_sect.invalidating_qualities))
var/datum/species/highpriest_species = highpriest.dna.species
if(initial(easy_access_sect.invalidating_qualities) in highpriest_species.inherent_traits)
continue
var/list/sect = list()
var/datum/religion_sect/not_a_real_instance_rs = path
sect["name"] = initial(not_a_real_instance_rs.name)
+7 -2
View File
@@ -565,7 +565,10 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
skin_tone = GLOB.skin_tones[deconstruct_block(get_uni_identity_block(structure, DNA_SKIN_TONE_BLOCK), GLOB.skin_tones.len)]
eye_color_left = sanitize_hexcolor(get_uni_identity_block(structure, DNA_EYE_COLOR_LEFT_BLOCK))
eye_color_right = sanitize_hexcolor(get_uni_identity_block(structure, DNA_EYE_COLOR_RIGHT_BLOCK))
facial_hairstyle = GLOB.facial_hairstyles_list[deconstruct_block(get_uni_identity_block(structure, DNA_FACIAL_HAIRSTYLE_BLOCK), GLOB.facial_hairstyles_list.len)]
if(HAS_TRAIT(src, TRAIT_SHAVED))
hairstyle = "Shaved"
else
facial_hairstyle = GLOB.facial_hairstyles_list[deconstruct_block(get_uni_identity_block(structure, DNA_FACIAL_HAIRSTYLE_BLOCK), GLOB.facial_hairstyles_list.len)]
if(HAS_TRAIT(src, TRAIT_BALD))
hairstyle = "Bald"
else
@@ -836,7 +839,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
ForceContractDisease(new/datum/disease/gastrolosis())
to_chat(src, span_notice("Oh, I actually feel quite alright!"))
else
switch(rand(0,5))
switch(rand(0,6))
if(0)
investigate_log("has been gibbed by DNA instability.", INVESTIGATE_DEATHS)
gib()
@@ -866,6 +869,8 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
if(5)
to_chat(src, span_phobia("LOOK UP!"))
addtimer(CALLBACK(src, PROC_REF(something_horrible_mindmelt)), 30)
if(6)
psykerize()
/mob/living/carbon/human/proc/something_horrible_mindmelt()
if(!is_blind())
@@ -1,189 +0,0 @@
///Burdened grants some more mutations upon injuring yourself sufficiently
/datum/mutation/human/burdened
name = "Burdened"
desc = "Less of a genome and more of a forceful rewrite of genes. Nothing Nanotrasen supplies allows for a genetic restructure like this... \
The user feels compelled to injure themselves in various incapacitating and horrific ways. Oddly enough, this gene seems to be connected \
to several other ones, possibly ready to trigger more genetic changes in the future."
quality = POSITIVE //so it gets carried over on revives
locked = TRUE
text_gain_indication = "<span class='notice'>You feel burdened!</span>"
text_lose_indication = "<span class='warning'>You no longer feel the need to burden yourself!</span>"
/// goes from 0 to 6 (but can be beyond 6, just does nothing) and gives rewards. increased by disabling yourself with debuffs
var/burden_level = 0
/datum/mutation/human/burdened/on_acquiring(mob/living/carbon/human/owner)
if(..())
return
RegisterSignal(owner, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(organ_added_burden))
RegisterSignal(owner, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(organ_removed_burden))
RegisterSignal(owner, COMSIG_CARBON_ATTACH_LIMB, PROC_REF(limbs_added_burden))
RegisterSignal(owner, COMSIG_CARBON_REMOVE_LIMB, PROC_REF(limbs_removed_burden))
RegisterSignal(owner, COMSIG_CARBON_GAIN_ADDICTION, PROC_REF(addict_added_burden))
RegisterSignal(owner, COMSIG_CARBON_LOSE_ADDICTION, PROC_REF(addict_removed_burden))
RegisterSignal(owner, COMSIG_CARBON_GAIN_MUTATION, PROC_REF(mutation_added_burden))
RegisterSignal(owner, COMSIG_CARBON_LOSE_MUTATION, PROC_REF(mutation_removed_burden))
RegisterSignal(owner, COMSIG_CARBON_GAIN_TRAUMA, PROC_REF(trauma_added_burden))
RegisterSignal(owner, COMSIG_CARBON_LOSE_TRAUMA, PROC_REF(trauma_removed_burden))
/datum/mutation/human/burdened/on_losing(mob/living/carbon/human/owner)
. = ..()
UnregisterSignal(owner, list(
COMSIG_CARBON_GAIN_ORGAN,
COMSIG_CARBON_LOSE_ORGAN,
COMSIG_CARBON_ATTACH_LIMB,
COMSIG_CARBON_REMOVE_LIMB,
COMSIG_CARBON_GAIN_ADDICTION,
COMSIG_CARBON_LOSE_ADDICTION,
COMSIG_CARBON_GAIN_MUTATION,
COMSIG_CARBON_LOSE_MUTATION,
COMSIG_CARBON_GAIN_TRAUMA,
COMSIG_CARBON_LOSE_TRAUMA,
))
/**
* Called by hooked signals whenever burden_level var needs to go up or down by 1.
* Sends messages on burden level, gives powers and takes them if needed, etc
*
* Arguments:
* * increase: whether to tick burden_level up or down 1
*/
/datum/mutation/human/burdened/proc/update_burden(increase)
//adjust burden
burden_level = increase ? burden_level + 1 : burden_level - 1
if(burden_level < 0) //basically a clamp with a stack on it, because this shouldn't be happening
stack_trace("somehow, burden mutation is removing more burden than it's adding.")
burden_level = 0
//send a message and handle rewards
switch(burden_level)
if(0)
to_chat(owner, span_warning("You feel no weight on your shoulders. You are not feeling [GLOB.deity]'s suffering."))
if(1)
if(increase)
to_chat(owner, span_notice("You begin to feel the scars on [GLOB.deity]. You must continue to burden yourself."))
else
to_chat(owner, span_warning("The weight on your shoulders feels lighter. You are barely feeling [GLOB.deity]'s suffering."))
if(2)
if(increase)
to_chat(owner, span_notice("You have done well to understand [GLOB.deity]. You are almost at a breakthrough."))
else
to_chat(owner, span_warning("The weight on your shoulders feels lighter. You have lost some universal truths."))
dna.remove_mutation(/datum/mutation/human/telepathy)
dna.remove_mutation(/datum/mutation/human/mute)
owner.remove_filter("burden_outline")
if(3)
if(increase)
to_chat(owner, span_notice("Your suffering is only a fraction of [GLOB.deity]'s, and yet the universal truths are coming to you."))
dna.add_mutation(/datum/mutation/human/telepathy)
dna.add_mutation(/datum/mutation/human/mute)
owner.add_filter("burden_outline", 9, list("type" = "outline", "color" = "#6c6eff"))
else
to_chat(owner, span_warning("The weight on your shoulders feels lighter. You feel like you're about to forget."))
if(4)
if(increase)
to_chat(owner, span_notice("The weight on your shoulders is immense. [GLOB.deity] is shattered across the cosmos."))
else
to_chat(owner, span_warning("The weight on your shoulders feels lighter. You're growing further from your goal."))
if(5)
if(increase)
to_chat(owner, span_notice("You're on the cusp of another breakthrough. [GLOB.deity] lost everything."))
else
to_chat(owner, span_warning("The weight on your shoulders feels lighter. You have lost some universal truths."))
dna.remove_mutation(/datum/mutation/human/telekinesis)
dna.remove_mutation(/datum/mutation/human/mindreader)
if(6)
to_chat(owner, span_notice("You have finally broken yourself enough to understand [GLOB.deity]. It's all so clear to you."))
dna.add_mutation(/datum/mutation/human/telekinesis)
dna.add_mutation(/datum/mutation/human/mindreader)
/// Signal to decrease burden_level (see update_burden proc) if an organ is added
/datum/mutation/human/burdened/proc/organ_added_burden(mob/burdened, obj/item/organ/new_organ, special)
SIGNAL_HANDLER
if(special) //aheals
return
if(istype(new_organ, /obj/item/organ/internal/eyes))
var/obj/item/organ/internal/eyes/new_eyes = new_organ
if(new_eyes.tint < TINT_BLIND) //unless you added unworking eyes (flashlight eyes), this is removing burden
update_burden(FALSE)
return
update_burden(FALSE)//working organ
/// Signal to increase burden_level (see update_burden proc) if an organ is removed
/datum/mutation/human/burdened/proc/organ_removed_burden(mob/burdened, obj/item/organ/old_organ, special)
SIGNAL_HANDLER
if(special) //aheals
return
if(istype(old_organ, /obj/item/organ/internal/eyes))
var/obj/item/organ/internal/eyes/old_eyes = old_organ
if(old_eyes.tint < TINT_BLIND) //unless you were already blinded by them (flashlight eyes), this is adding burden!
update_burden(TRUE)
update_burden(TRUE)//lost organ
/// Signal to decrease burden_level (see update_burden proc) if a limb is added
/datum/mutation/human/burdened/proc/limbs_added_burden(datum/source, obj/item/bodypart/new_limb, special)
SIGNAL_HANDLER
if(special) //something we don't wanna consider, like instaswapping limbs
return
update_burden(FALSE)
/// Signal to increase burden_level (see update_burden proc) if a limb is removed
/datum/mutation/human/burdened/proc/limbs_removed_burden(datum/source, obj/item/bodypart/old_limb, special)
SIGNAL_HANDLER
if(special) //something we don't wanna consider, like instaswapping limbs
return
update_burden(TRUE)
/// Signal to increase burden_level (see update_burden proc) if an addiction is added
/datum/mutation/human/burdened/proc/addict_added_burden(datum/addiction/new_addiction, datum/mind/addict_mind)
SIGNAL_HANDLER
update_burden(TRUE)
/// Signal to decrease burden_level (see update_burden proc) if an addiction is removed
/datum/mutation/human/burdened/proc/addict_removed_burden(datum/addiction/old_addiction, datum/mind/nonaddict_mind)
SIGNAL_HANDLER
update_burden(FALSE)
/// Signal to increase burden_level (see update_burden proc) if a mutation is added
/datum/mutation/human/burdened/proc/mutation_added_burden(mob/living/carbon/burdened, datum/mutation/human/mutation_type, class)
SIGNAL_HANDLER
if(class == MUT_OTHER) //getting a mutation can give a mutation as a reward, which in turn triggers this, which then rewards a mutation, which in turn...
return
if(initial(mutation_type.quality) == NEGATIVE)
update_burden(TRUE)
/// Signal to decrease burden_level (see update_burden proc) if a mutation is removed
/datum/mutation/human/burdened/proc/mutation_removed_burden(mob/living/carbon/burdened, datum/mutation/human/mutation_type)
SIGNAL_HANDLER
if(class == MUT_OTHER) //see above
return
if(initial(mutation_type.quality) == NEGATIVE)
update_burden(FALSE)
/// Signal to increase burden_level (see update_burden proc) if a trauma is added
/datum/mutation/human/burdened/proc/trauma_added_burden(mob/living/carbon/burdened, datum/brain_trauma/trauma_added)
SIGNAL_HANDLER
if(istype(trauma_added, /datum/brain_trauma/severe))
update_burden(TRUE)
/// Signal to decrease burden_level (see update_burden proc) if a trauma is removed
/datum/mutation/human/burdened/proc/trauma_removed_burden(mob/living/carbon/burdened, datum/brain_trauma/trauma_removed)
SIGNAL_HANDLER
if(istype(trauma_removed, /datum/brain_trauma/severe))
update_burden(FALSE)
@@ -1,318 +0,0 @@
///Honorbound prevents you from attacking the unready, the just, or the innocent
/datum/mutation/human/honorbound
name = "Honorbound"
desc = "Less of a genome and more of a forceful rewrite of genes. Nothing Nanotrasen supplies allows for a genetic restructure like this... \
The user feels compelled to follow supposed \"rules of combat\" but in reality they physically are unable to. \
Their brain is rewired to excuse any curious inabilities that arise from this odd effect."
quality = POSITIVE //so it gets carried over on revives
power_path = /datum/action/cooldown/spell/pointed/declare_evil
locked = TRUE
text_gain_indication = "<span class='notice'>You feel honorbound!</span>"
text_lose_indication = "<span class='warning'>You feel unshackled from your code of honor!</span>"
/// list of guilty people
var/list/guilty = list()
/datum/mutation/human/honorbound/on_acquiring(mob/living/carbon/human/owner)
if(..())
return
//moodlet
owner.add_mood_event("honorbound", /datum/mood_event/honorbound)
//checking spells cast by honorbound
RegisterSignal(owner, COMSIG_MOB_CAST_SPELL, PROC_REF(spell_check))
RegisterSignal(owner, COMSIG_MOB_FIRED_GUN, PROC_REF(staff_check))
//signals that check for guilt
RegisterSignal(owner, COMSIG_PARENT_ATTACKBY, PROC_REF(attackby_guilt))
RegisterSignal(owner, COMSIG_ATOM_HULK_ATTACK, PROC_REF(hulk_guilt))
RegisterSignal(owner, COMSIG_ATOM_ATTACK_HAND, PROC_REF(hand_guilt))
RegisterSignal(owner, COMSIG_ATOM_ATTACK_PAW, PROC_REF(paw_guilt))
RegisterSignal(owner, COMSIG_ATOM_BULLET_ACT, PROC_REF(bullet_guilt))
RegisterSignal(owner, COMSIG_ATOM_HITBY, PROC_REF(thrown_guilt))
//signal that checks for dishonorable attacks
RegisterSignal(owner, COMSIG_MOB_CLICKON, PROC_REF(attack_honor))
/datum/mutation/human/honorbound/on_losing(mob/living/carbon/human/owner)
owner.clear_mood_event("honorbound")
UnregisterSignal(owner, list(
COMSIG_PARENT_ATTACKBY,
COMSIG_ATOM_HULK_ATTACK,
COMSIG_ATOM_ATTACK_HAND,
COMSIG_ATOM_ATTACK_PAW,
COMSIG_ATOM_BULLET_ACT,
COMSIG_ATOM_HITBY,
COMSIG_MOB_CLICKON,
COMSIG_MOB_CAST_SPELL,
COMSIG_MOB_FIRED_GUN,
))
. = ..()
/// Signal to see if the mutation allows us to attack a target
/datum/mutation/human/honorbound/proc/attack_honor(mob/living/carbon/human/honorbound, atom/clickingon, list/modifiers)
SIGNAL_HANDLER
if(modifiers[ALT_CLICK] || modifiers[SHIFT_CLICK] || modifiers[CTRL_CLICK] || modifiers[MIDDLE_CLICK])
return
if(!isliving(clickingon))
return
var/mob/living/clickedmob = clickingon
var/obj/item/weapon = honorbound.get_active_held_item()
if(!honorbound.DirectAccess(clickedmob) && !isgun(weapon))
return
if(weapon?.item_flags & NOBLUDGEON)
return
if(!honorbound.combat_mode && (HAS_TRAIT(clickedmob, TRAIT_ALLOWED_HONORBOUND_ATTACK) || ((!weapon || !weapon.force) && !LAZYACCESS(modifiers, RIGHT_CLICK))))
return
if(!is_honorable(honorbound, clickedmob))
return (COMSIG_MOB_CANCEL_CLICKON)
/**
* Called by hooked signals whenever someone attacks the person with this mutation
* Checks if the attacker should be considered guilty and adds them to the guilty list if true
*
* Arguments:
* * user: person who attacked the honorbound
* * declaration: if this wasn't an attack, but instead the honorbound spending favor on declaring this person guilty
*/
/datum/mutation/human/honorbound/proc/guilty(mob/living/user, declaration = FALSE)
if(user in guilty)
return
var/datum/mind/guilty_conscience = user.mind
if(guilty_conscience) //sec and medical are immune to becoming guilty through attack (we don't check holy because holy shouldn't be able to attack eachother anyways)
var/datum/job/job = guilty_conscience.assigned_role
if(job.departments_bitflags & (DEPARTMENT_BITFLAG_MEDICAL | DEPARTMENT_BITFLAG_SECURITY))
return
if(declaration)
to_chat(owner, span_notice("[user] is now considered guilty by [GLOB.deity] from your declaration."))
else
to_chat(owner, span_notice("[user] is now considered guilty by [GLOB.deity] for attacking you first."))
to_chat(user, span_danger("[GLOB.deity] no longer considers you innocent!"))
guilty += user
/**
* Called by attack_honor signal to check whether an attack should be allowed or not
*
* Arguments:
* * honorbound_human: typecasted owner of mutation
* * target_creature: person honorbound_human is attacking
*/
/datum/mutation/human/honorbound/proc/is_honorable(mob/living/carbon/human/honorbound_human, mob/living/target_creature)
var/is_guilty = (target_creature in guilty)
//THE UNREADY (Applies over ANYTHING else!)
if(honorbound_human == target_creature)
return TRUE //oh come on now
if(target_creature.IsSleeping() || target_creature.IsUnconscious() || HAS_TRAIT(target_creature, TRAIT_RESTRAINED))
to_chat(honorbound_human, span_warning("There is no honor in attacking the <b>unready</b>."))
return FALSE
//THE JUST (Applies over guilt except for med, so you best be careful!)
if(ishuman(target_creature))
var/mob/living/carbon/human/target_human = target_creature
var/datum/job/job = target_human.mind?.assigned_role
var/is_holy = target_human.mind?.holy_role
if(is_holy || (job?.departments_bitflags & DEPARTMENT_BITFLAG_SECURITY))
to_chat(honorbound_human, span_warning("There is nothing righteous in attacking the <b>just</b>."))
return FALSE
if(job?.departments_bitflags & DEPARTMENT_BITFLAG_MEDICAL)
to_chat(honorbound_human, span_warning("If you truly think this healer is not <b>innocent</b>, declare them guilty."))
return FALSE
//THE INNOCENT
if(!is_guilty)
to_chat(honorbound_human, span_warning("There is nothing righteous in attacking the <b>innocent</b>."))
return FALSE
return TRUE
// SIGNALS THAT ARE FOR BEING ATTACKED FIRST (GUILTY)
/datum/mutation/human/honorbound/proc/attackby_guilt(datum/source, obj/item/I, mob/attacker)
SIGNAL_HANDLER
if(I.force && I.damtype != STAMINA)
guilty(attacker)
/datum/mutation/human/honorbound/proc/hulk_guilt(datum/source, mob/attacker)
SIGNAL_HANDLER
guilty(attacker)
/datum/mutation/human/honorbound/proc/hand_guilt(datum/source, mob/living/attacker)
SIGNAL_HANDLER
if(attacker.combat_mode)
guilty(attacker)
/datum/mutation/human/honorbound/proc/paw_guilt(datum/source, mob/living/attacker)
SIGNAL_HANDLER
guilty(attacker)
/datum/mutation/human/honorbound/proc/bullet_guilt(datum/source, obj/projectile/proj)
SIGNAL_HANDLER
var/mob/living/shot_honorbound = source
var/static/list/guilty_projectiles = typecacheof(list(
/obj/projectile/beam,
/obj/projectile/bullet,
/obj/projectile/magic,
))
if(!is_type_in_typecache(proj, guilty_projectiles))
return
if((proj.damage_type == STAMINA))
return
if(!proj.nodamage && proj.damage < shot_honorbound.health && isliving(proj.firer))
guilty(proj.firer)
/datum/mutation/human/honorbound/proc/thrown_guilt(datum/source, atom/movable/thrown_movable, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum)
SIGNAL_HANDLER
if(isitem(thrown_movable))
var/mob/living/honorbound = source
var/obj/item/thrown_item = thrown_movable
var/mob/thrown_by = thrown_item.thrownby?.resolve()
if(thrown_item.throwforce < honorbound.health && ishuman(thrown_by))
guilty(thrown_by)
//spell checking
/datum/mutation/human/honorbound/proc/spell_check(mob/user, datum/action/cooldown/spell/spell_cast)
SIGNAL_HANDLER
punishment(user, spell_cast.school)
/datum/mutation/human/honorbound/proc/staff_check(mob/user, obj/item/gun/gun_fired, target, params, zone_override)
SIGNAL_HANDLER
if(!istype(gun_fired, /obj/item/gun/magic))
return
var/obj/item/gun/magic/offending_staff = gun_fired
punishment(user, offending_staff.school)
/**
* Called when a spell is casted or a magic gun is fired, checks the signal and punishes accordingly
*
* Arguments:
* * user: typecasted owner of mutation
* * school: school of magic casted from the staff/spell
*/
/datum/mutation/human/honorbound/proc/punishment(mob/living/carbon/human/user, school)
switch(school)
if(SCHOOL_HOLY, SCHOOL_MIME, SCHOOL_RESTORATION)
return
if(SCHOOL_NECROMANCY, SCHOOL_FORBIDDEN, SCHOOL_SANGUINE)
to_chat(user, span_userdanger("[GLOB.deity] is enraged by your use of forbidden magic!"))
lightningbolt(user)
owner.add_mood_event("honorbound", /datum/mood_event/banished)
user.dna.remove_mutation(/datum/mutation/human/honorbound)
user.mind.holy_role = NONE
to_chat(user, span_userdanger("You have been excommunicated! You are no longer holy!"))
else
to_chat(user, span_userdanger("[GLOB.deity] is angered by your use of [school] magic!"))
lightningbolt(user)
owner.add_mood_event("honorbound", /datum/mood_event/holy_smite)//permanently lose your moodlet after this
/datum/action/cooldown/spell/pointed/declare_evil
name = "Declare Evil"
desc = "If someone is so obviously an evil of this world you can spend a huge amount of favor to declare them guilty."
button_icon_state = "declaration"
ranged_mousepointer = 'icons/effects/mouse_pointers/honorbound.dmi'
school = SCHOOL_HOLY
cooldown_time = 0
invocation = "This is an error!"
invocation_type = INVOCATION_SHOUT
spell_requirements = SPELL_REQUIRES_HUMAN
active_msg = "You prepare to declare a sinner..."
deactive_msg = "You decide against a declaration."
/// The amount of favor required to declare on someone
var/required_favor = 150
/// A ref to our owner's honorbound mutation
var/datum/mutation/human/honorbound/honor_mutation
/// The declaration that's shouted in invocation. Set in New()
var/declaration = "By the divine light of my deity, you are an evil of this world that must be wrought low!"
/datum/action/cooldown/spell/pointed/declare_evil/New()
. = ..()
declaration = "By the divine light of [GLOB.deity], you are an evil of this world that must be wrought low!"
/datum/action/cooldown/spell/pointed/declare_evil/Destroy()
// If we had an owner, Destroy() called Remove(), and already handled this
if(honor_mutation)
UnregisterSignal(honor_mutation, COMSIG_PARENT_QDELETING)
honor_mutation = null
return ..()
/datum/action/cooldown/spell/pointed/declare_evil/Grant(mob/grant_to)
if(!ishuman(grant_to))
return FALSE
var/mob/living/carbon/human/human_owner = grant_to
var/datum/mutation/human/honorbound/honor_mut = human_owner.dna?.check_mutation(/datum/mutation/human/honorbound)
if(QDELETED(honor_mut))
return FALSE
RegisterSignal(honor_mut, COMSIG_PARENT_QDELETING, PROC_REF(on_honor_mutation_lost))
honor_mutation = honor_mut
return ..()
/datum/action/cooldown/spell/pointed/declare_evil/Remove(mob/living/remove_from)
. = ..()
UnregisterSignal(honor_mutation, COMSIG_PARENT_QDELETING)
honor_mutation = null
/// If we lose our honor mutation somehow, self-delete (and clear references)
/datum/action/cooldown/spell/pointed/declare_evil/proc/on_honor_mutation_lost(datum/source)
SIGNAL_HANDLER
qdel(src)
/datum/action/cooldown/spell/pointed/declare_evil/can_cast_spell(feedback = TRUE)
. = ..()
if(!.)
return FALSE
// This shouldn't technically be a possible state, but you never know
if(!honor_mutation)
return FALSE
if(GLOB.religious_sect.favor < required_favor)
if(feedback)
to_chat(owner, span_warning("You need at least 150 favor to declare someone evil!"))
return FALSE
return TRUE
/datum/action/cooldown/spell/pointed/declare_evil/is_valid_target(atom/cast_on)
. = ..()
if(!.)
return FALSE
if(!isliving(cast_on))
to_chat(owner, span_warning("You can only declare living beings evil!"))
return FALSE
var/mob/living/living_cast_on = cast_on
if(living_cast_on.stat == DEAD)
to_chat(owner, span_warning("Declaration on the dead? Really?"))
return FALSE
// sec and medical are immune to becoming guilty through attack
// (we don't check holy, because holy shouldn't be able to attack eachother anyways)
if(!living_cast_on.key || !living_cast_on.mind)
to_chat(owner, span_warning("There is no evil a vacant mind can do."))
return FALSE
// also handles any kind of issues with self declarations
if(living_cast_on.mind.holy_role)
to_chat(owner, span_warning("Followers of [GLOB.deity] cannot be evil!"))
return FALSE
// cannot declare security as evil
if(living_cast_on.mind.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_SECURITY)
to_chat(owner, span_warning("Members of security are uncorruptable! You cannot declare one evil!"))
return FALSE
return TRUE
/datum/action/cooldown/spell/pointed/declare_evil/before_cast(mob/living/cast_on)
. = ..()
if(. & SPELL_CANCEL_CAST)
return
invocation = "[cast_on]! [declaration]"
/datum/action/cooldown/spell/pointed/declare_evil/cast(mob/living/cast_on)
. = ..()
GLOB.religious_sect.adjust_favor(-required_favor, owner)
honor_mutation.guilty(cast_on, declaration = TRUE)