Repository: Vanderlin-Tales-Of-Wine/Vanderlin
Pull request #1940: Buff Matron
Author: login: SarmentiCampbell
Labels: name: Feature
description: adds a new thing
name: Hell Yes
description: Nice.
Created: 2025-05-17T04:02:01Z | Merged: 2025-05-19T17:22:32Z

<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may not be viewable. -->
<!-- Note: PRs including balance changes authored by anyone other than maintainers and official devs will not be considered. -->

## About The Pull Request
Give Matron two new spells.  Brat Detector and Hag's Call.
Brat Detector is in the name, It basically check the location of a orphan that you pick, has a cooldown of 10 seconds, It also has the ability to check if they're under or above a zlevel
Hag's Call is in the name, jk, It'll give you a input, telling you to pick a orphan's before yelling it out, notifying whichever orphan you named and giving them a slight mood debuff, also allows you to pick one member of the old party. Has a cooldown of five minutes.
Give 1 point in climbing when old, making them a master in climbing, I personally think this is fine, but you guys are free to give me your opinion after trying it out.
Give 1 point in Speed and Endurance when old to counter the negative speed/end stat of being old, we want the matron to chase dem brats.

Give Matron another trait called Ear grab, basically the same as Nose grab but affect the ears and only work on youngling (I don't suggest using it without armors cause they do bite.)

Give Orphan a trait called orphan, which does nothing but do the check for the eargrab, and i just realised this is stupid, give me a minute. i'll also make it work for the brat detector and the hag's call.

![image](https://github.com/user-attachments/assets/51ed9b7e-3827-49de-929a-ef0c671dc0ad)

![image](https://github.com/user-attachments/assets/516aedff-0dd3-44a8-b05f-f22f6479331d)

<!-- Describe The Pull Request. Please be sure every change is documented or this can delay review or prevent the PR from being merged! -->

## Why It's Good For The Game

<!-- Please add a short description of why you think these changes would benefit the game. If you can't justify it in words, it might not be worth adding. -->

https://github.com/user-attachments/assets/591518f9-e2e3-462d-978c-419f1f93c414


## Pre-Merge Checklist
<!-- Don't bother filling these in while creating your Pull Request, just click the checkboxes after the Pull Request is opened and you are redirected to the page. -->
- [x] You tested this on a local server.
- [x] This code did not runtime during testing.
- [x] You documented all of your changes.
<!-- Neither the compiler nor workflow checks are perfect at detecting runtimes and errors. It is important to test your code/feature/fix locally. -->



Diff:
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 76e0c7ba68b..62ff7880155 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -56,6 +56,8 @@
 #define TRAIT_DREAM_WATCHER				"Noc Blessed" //Unique Trait of the Dream Watcher Town Elder Class, they have a chance to know about antags or gods influences.
 #define TRAIT_HOLLOWBONES				"Hollow Bones"
 #define TRAIT_AMAZING_BACK				"Light Load"
+/// applied to orphans
+#define TRAIT_ORPHAN 					"Orphan"
 
 // Divine patron trait bonuses:
 #define TRAIT_SOUL_EXAMINE				"Blessing of Necra"  //can check bodies to see if they have departed
@@ -106,7 +108,8 @@
 #define TRAIT_MALUMFIRE "Professional Smith"
 #define TRAIT_CRATEMOVER "Crate Mover"
 #define TRAIT_BURDEN "Burdened" //Gaffer stuff
-#define TRAIT_OLDPARTY "Old_party"
+#define TRAIT_OLDPARTY "Old Party"
+#define TRAIT_EARGRAB "Ear Grab"
 
 // PATRON CURSE TRAITS
 #define TRAIT_CURSE "Curse" //source
@@ -204,6 +207,7 @@ GLOBAL_LIST_INIT(roguetraits, list(
 	TRAIT_DREAM_WATCHER = span_notice("I'm blessed by Noc, my dreams tell more than the average person.."),
 	TRAIT_AMAZING_BACK = span_notice("I'm able to carry far more on my back!"),
 	TRAIT_HOLLOWBONES = span_danger("My bones are light as air, Its hard to wear armor."),
+	TRAIT_EARGRAB = span_info("I can keep a tight grip on the ear of unruly children."),
 	))
 
 #define SIGNAL_ADDTRAIT(trait_ref) ("addtrait " + trait_ref)
diff --git a/code/datums/stress/negative_events.dm b/code/datums/stress/negative_events.dm
index 50fe60ef093..71977c2cdc4 100644
--- a/code/datums/stress/negative_events.dm
+++ b/code/datums/stress/negative_events.dm
@@ -392,3 +392,13 @@
 	stressadd = 1
 	desc = span_red("I must eat my opponent's heart before he eats MINE!")
 	timer = INFINITY
+
+/datum/stressevent/mother_calling
+	timer = 1 MINUTES
+	stressadd = 2
+	desc = span_red("The Matron is calling for me by my full name..")
+
+/datum/stressevent/friend_calling
+	timer = 30 SECONDS
+	stressadd = 1
+	desc = span_red("That voice.. That old thief is calling for me, what is it now?")
diff --git a/code/modules/jobs/job_types/serfs/matron.dm b/code/modules/jobs/job_types/serfs/matron.dm
index 84b6883aa13..1e2bf9b234e 100644
--- a/code/modules/jobs/job_types/serfs/matron.dm
+++ b/code/modules/jobs/job_types/serfs/matron.dm
@@ -44,6 +44,9 @@
 		H.adjust_skillrank(/datum/skill/misc/stealing, 2, TRUE)
 		H.adjust_skillrank(/datum/skill/misc/sneaking, 1, TRUE)
 		H.adjust_skillrank(/datum/skill/misc/lockpicking, 1, TRUE)
+		H.adjust_skillrank(/datum/skill/misc/climbing, 1, TRUE) //She is a old thief.
+		H.change_stat(STATKEY_SPD, 1) //this is to counter the negative spd/end stats from being old so they can chase the orphans in case they're not listening, the other stats are left untouched.
+		H.change_stat(STATKEY_END, 1)
 	H.change_stat(STATKEY_STR, -1)
 	H.change_stat(STATKEY_INT, 2)
 	H.change_stat(STATKEY_PER, 1)
@@ -52,6 +55,9 @@
 	to_chat(H, "<span class='info'>I can gesture in thieves' cant with ,t before my speech.</span>")
 	ADD_TRAIT(H, TRAIT_THIEVESGUILD, TRAIT_GENERIC)
 	ADD_TRAIT(H, TRAIT_OLDPARTY, TRAIT_GENERIC)
+	ADD_TRAIT(H, TRAIT_EARGRAB, TRAIT_GENERIC)
+	H.mind?.AddSpell(new /obj/effect/proc_holder/spell/targeted/seek_orphan)
+	H.mind?.AddSpell(new /obj/effect/proc_holder/spell/self/hag_call)
 	shirt = /obj/item/clothing/shirt/dress/gen/black
 	armor = /obj/item/clothing/armor/leather/vest/black
 	pants = /obj/item/clothing/pants/trou/beltpants
diff --git a/code/modules/jobs/job_types/youngfolk/orphan.dm b/code/modules/jobs/job_types/youngfolk/orphan.dm
index ba5c22b172f..8b8990133e0 100644
--- a/code/modules/jobs/job_types/youngfolk/orphan.dm
+++ b/code/modules/jobs/job_types/youngfolk/orphan.dm
@@ -55,3 +55,4 @@
 	H.change_stat(STATKEY_INT, round(rand(-4,4)))
 	H.change_stat(STATKEY_CON, -1)
 	H.change_stat(STATKEY_END, -1)
+	ADD_TRAIT(H, TRAIT_ORPHAN, TRAIT_GENERIC) //Affected by Matron's Eargrab.
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index b4d962dd9a8..53999c97ba9 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -1213,18 +1213,25 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if((HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE)) || (HAS_TRAIT(H, TRAIT_EARGRAB) && age == AGE_CHILD))
 			var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD)
 			for(var/obj/item/grabbing/G in grabbedby)
 				if(G.limb_grabbed == head)
 					if(G.grabbee == pulledby)
-						if(G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
+						if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
 							visible_message("<span class='warning'>[src] struggles to break free from [pulledby]'s grip!</span>", \
 											"<span class='warning'>I struggle against [pulledby]'s grip!</span>", null, null, pulledby)
 							to_chat(pulledby, "<span class='warning'>[src] struggles against my grip!</span>")
 							playsound(src.loc, 'sound/combat/grabstruggle.ogg', 50, TRUE, -1)
 							client?.move_delay = world.time + 20
 							return TRUE
+						if(HAS_TRAIT(H, TRAIT_EARGRAB) && G.sublimb_grabbed == BODY_ZONE_PRECISE_EARS)
+							visible_message("<span class='warning'>[src] struggles to break free from [pulledby]'s grip!</span>", \
+												"<span class='warning'>I struggle against [pulledby]'s grip!</span>", null, null, pulledby)
+							to_chat(pulledby, "<span class='warning'>[src] struggles against my grip!</span>")
+							playsound(src.loc, 'sound/combat/grabstruggle.ogg', 50, TRUE, -1)
+							client?.move_delay = world.time + 20
+							return TRUE
 	return ..()
 
 /mob/living/proc/resist_buckle()
diff --git a/code/modules/spells/spell_types/invoked/matron.dm b/code/modules/spells/spell_types/invoked/matron.dm
new file mode 100644
index 00000000000..a7666ce4d08
--- /dev/null
+++ b/code/modules/spells/spell_types/invoked/matron.dm
@@ -0,0 +1,90 @@
+/obj/effect/proc_holder/spell/targeted/seek_orphan
+	name = "Brat Detector"
+	overlay_state = "recruit_acolyte"
+	recharge_time = 10 SECONDS
+	max_targets = 0
+	cast_without_targets = TRUE
+
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/cast(list/targets, mob/user = usr)
+	. = ..()
+	var/list/orphans = list()
+	for(var/mob/living/carbon/human/H in GLOB.human_list)
+		if(!H.mind)
+			continue
+		if(!HAS_TRAIT(H, TRAIT_ORPHAN))
+			continue
+		orphans += H
+
+	if(!length(orphans))
+		to_chat(user, span_red("..There isn't any"))
+		return
+
+	var/mob/living/carbon/human/tracked_orphan = browser_input_list(user, "Which One?", "Seek Orphan", orphans)
+	if(!tracked_orphan)
+		to_chat(user, span_warning("I can't find any of those brats.."))
+		return
+
+	var/message = where_orphan(user, tracked_orphan)
+	to_chat(user, span_blue("That brat is [message]."))
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/proc/where_orphan(mob/user, mob/living/carbon/human/tracked_orphan)
+	var/turf/our_turf = get_turf(user)
+	var/turf/their_turf = get_turf(tracked_orphan)
+	var/our_z = our_turf?.z
+	var/their_z = their_turf?.z
+	var/dist = get_dist(our_turf, their_turf)
+	var/dir = get_dir(our_turf, their_turf)
+	var/dir_text = dir2text(dir)
+	var/distance_text
+
+	switch(dist)
+		if(0 to 7)
+			distance_text = "very close"
+		if(8 to 15)
+			distance_text = "close"
+		if(16 to 35)
+			distance_text = "near"
+		if(36 to 49)
+			distance_text = "somewhat near"
+		if(50 to 70)
+			distance_text = "slightly far"
+		if(71 to 98)
+			distance_text = "somewhat far"
+		if(99 to 127)
+			distance_text = "far"
+		else
+			distance_text = "very far"
+
+	if(our_z != their_z)
+		var/z_text = our_z > their_z ? "below me" : "above me"
+		return "[distance_text]...and to the [dir_text], [z_text]"
+
+	return "[distance_text]...and to the [dir_text]"
+
+/obj/effect/proc_holder/spell/self/hag_call
+	name = "Hag's Call"
+	recharge_time = 1 MINUTES
+	warnie = "spellwarning"
+	overlay_state = "message"
+
+/obj/effect/proc_holder/spell/self/hag_call/cast(list/targets, mob/user)
+	var/input = browser_input_text(user, "Which one of those brats am I trying to call?", "Hag's Call")
+	if(!input)
+		return FALSE
+	if(!user.mind)
+		to_chat(user, span_warning("I do not think i'll be able to call for them.."))
+		return FALSE
+	for(var/mob/living/carbon/human/HL in GLOB.human_list)
+		if(HL.real_name == input && HL.mind)
+			if(HAS_TRAIT(HL, TRAIT_ORPHAN) || HAS_TRAIT(HL, TRAIT_OLDPARTY))
+				to_chat(HL, span_reallybig("[HL.real_name]!"))
+				HL.visible_message(span_notice("Someone is calling for me!"))
+				user.say("[HL.real_name]!!", spans = list("reallybig"))
+				if(HAS_TRAIT(HL, TRAIT_ORPHAN))
+					HL.add_stress(/datum/stressevent/mother_calling)
+				else
+					HL.add_stress(/datum/stressevent/friend_calling)
+				return ..()
+	to_chat(user, span_warning("What am I saying, That isn't one of the brats under my care?"))
+	return FALSE
diff --git a/vanderlin.dme b/vanderlin.dme
index 179bc71f3ff..7116fdfab85 100644
--- a/vanderlin.dme
+++ b/vanderlin.dme
@@ -2326,6 +2326,7 @@
 #include "code\modules\spells\spell_types\invoked\_invoked.dm"
 #include "code\modules\spells\spell_types\invoked\confessor.dm"
 #include "code\modules\spells\spell_types\invoked\jester.dm"
+#include "code\modules\spells\spell_types\invoked\matron.dm"
 #include "code\modules\spells\spell_types\invoked\monk.dm"
 #include "code\modules\spells\spell_types\invoked\necromancer.dm"
 #include "code\modules\spells\spell_types\invoked\orison_touch.dm"


Reviews:
user: login: grungussuss
body: Good pr.
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-05-17T14:30:35Z
user: login: SarmentiCampbell
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T14:36:04Z
user: login: SarmentiCampbell
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T14:41:33Z
user: login: SarmentiCampbell
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T14:44:15Z
user: login: grungussuss
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T14:51:53Z
user: login: SarmentiCampbell
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T14:59:29Z
user: login: SarmentiCampbell
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T15:03:08Z
user: login: grungussuss
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T15:11:13Z
user: login: grungussuss
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T15:22:00Z
user: login: SarmentiCampbell
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T15:22:47Z
user: login: SarmentiCampbell
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T15:23:30Z
user: login: SarmentiCampbell
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T15:25:52Z
user: login: grungussuss
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T15:35:47Z
user: login: grungussuss
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T15:36:36Z
user: login: SarmentiCampbell
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T15:42:00Z

Review comments:
diff_hunk: @@ -106,7 +107,8 @@
 #define TRAIT_MALUMFIRE "Professional Smith"
 #define TRAIT_CRATEMOVER "Crate Mover"
 #define TRAIT_BURDEN "Burdened" //Gaffer stuff
-#define TRAIT_OLDPARTY "Old_party"
+#define TRAIT_OLDPARTY "Old Party"
+#define TRAIT_EARGRAB "Motherly's Touch"
path: code/__DEFINES/traits.dm
user: login: grungussuss
body: 
```suggestion
#define TRAIT_EARGRAB "Ear Grab"
```
these are shown to admins when they look at traits, best be informative.
created_at: 2025-05-17T14:07:35Z
author_association: NONE
diff_hunk: @@ -204,6 +206,7 @@ GLOBAL_LIST_INIT(roguetraits, list(
 	TRAIT_DREAM_WATCHER = span_notice("I'm blessed by Noc, my dreams tell more than the average person.."),
 	TRAIT_AMAZING_BACK = span_notice("I'm able to carry far more on my back!"),
 	TRAIT_HOLLOWBONES = span_danger("My bones are light as air, Its hard to wear armor."),
+	TRAIT_EARGRAB = span_info("I know how to keep a tight grip on the ear of one of my unruly child."),
path: code/__DEFINES/traits.dm
user: login: grungussuss
body: 
```suggestion
	TRAIT_EARGRAB = span_info("I can keep a tight grip on the ear of unruly children."),
```
created_at: 2025-05-17T14:08:27Z
author_association: NONE
diff_hunk: @@ -392,3 +392,13 @@
 	stressadd = 1
 	desc = span_red("I must eat my opponent's heart before he eats MINE!")
 	timer = INFINITY
+
+/datum/stressevent/mother_calling
+	timer = 1 MINUTES
+	stressadd = 2
+	desc = span_red("The Matron is calling for me by my full name..")
+
+/datum/stressevent/friend_calling
+	timer = 30 SECONDS
+	stressadd = 1
+	desc = "That voice.. That old thief is calling for me, what is it now?"
path: code/datums/stress/negative_events.dm
user: login: grungussuss
body: 
```suggestion
	desc = span_red(That voice.. That old hag is calling for me, what is it now?")
```
created_at: 2025-05-17T14:08:55Z
author_association: NONE
diff_hunk: @@ -44,6 +44,9 @@
 		H.adjust_skillrank(/datum/skill/misc/stealing, 2, TRUE)
 		H.adjust_skillrank(/datum/skill/misc/sneaking, 1, TRUE)
 		H.adjust_skillrank(/datum/skill/misc/lockpicking, 1, TRUE)
+		H.adjust_skillrank(/datum/skill/misc/climbing, 1, TRUE) //She is a old thief, come on, even gaffer has it.
path: code/modules/jobs/job_types/serfs/matron.dm
user: login: grungussuss
body: 
```suggestion
		H.adjust_skillrank(/datum/skill/misc/climbing, 1, TRUE) //She is an old thief.
```
created_at: 2025-05-17T14:09:28Z
author_association: NONE
diff_hunk: @@ -55,3 +55,4 @@
 	H.change_stat(STATKEY_INT, round(rand(-4,4)))
 	H.change_stat(STATKEY_CON, -1)
 	H.change_stat(STATKEY_END, -1)
+	ADD_TRAIT(H, TRAIT_ORPHAN, TRAIT_GENERIC) //Affected by Matron's Eargrab.
path: code/modules/jobs/job_types/youngfolk/orphan.dm
user: login: grungussuss
body: instead let's check for youngling age, let her grab all younglings.
created_at: 2025-05-17T14:10:06Z
author_association: NONE
diff_hunk: @@ -1213,18 +1213,27 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE) || HAS_TRAIT(H, TRAIT_EARGRAB) && HAS_TRAIT(src, TRAIT_ORPHAN))
path: code/modules/mob/living/living.dm
user: login: grungussuss
body: 
```suggestion
		if((HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE)) || (HAS_TRAIT(H, TRAIT_EARGRAB) && HAS_TRAIT(src, TRAIT_ORPHAN)))
```
perhaps it would be better to put this as a seperate check instead? Below this one you changed.
created_at: 2025-05-17T14:13:12Z
author_association: NONE
diff_hunk: @@ -1213,18 +1213,27 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE) || HAS_TRAIT(H, TRAIT_EARGRAB) && HAS_TRAIT(src, TRAIT_ORPHAN))
 			var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD)
 			for(var/obj/item/grabbing/G in grabbedby)
 				if(G.limb_grabbed == head)
 					if(G.grabbee == pulledby)
-						if(G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
-							visible_message("<span class='warning'>[src] struggles to break free from [pulledby]'s grip!</span>", \
-											"<span class='warning'>I struggle against [pulledby]'s grip!</span>", null, null, pulledby)
-							to_chat(pulledby, "<span class='warning'>[src] struggles against my grip!</span>")
-							playsound(src.loc, 'sound/combat/grabstruggle.ogg', 50, TRUE, -1)
-							client?.move_delay = world.time + 20
-							return TRUE
+						if(HAS_TRAIT(H, TRAIT_NOSEGRAB))
path: code/modules/mob/living/living.dm
user: login: grungussuss
body: 
```suggestion
						if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
```
and remove the check below, then reduce tabs.
created_at: 2025-05-17T14:15:43Z
author_association: NONE
diff_hunk: @@ -1213,18 +1213,27 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE) || HAS_TRAIT(H, TRAIT_EARGRAB) && HAS_TRAIT(src, TRAIT_ORPHAN))
 			var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD)
 			for(var/obj/item/grabbing/G in grabbedby)
 				if(G.limb_grabbed == head)
 					if(G.grabbee == pulledby)
-						if(G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
-							visible_message("<span class='warning'>[src] struggles to break free from [pulledby]'s grip!</span>", \
-											"<span class='warning'>I struggle against [pulledby]'s grip!</span>", null, null, pulledby)
-							to_chat(pulledby, "<span class='warning'>[src] struggles against my grip!</span>")
-							playsound(src.loc, 'sound/combat/grabstruggle.ogg', 50, TRUE, -1)
-							client?.move_delay = world.time + 20
-							return TRUE
+						if(HAS_TRAIT(H, TRAIT_NOSEGRAB))
+							if(G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
path: code/modules/mob/living/living.dm
user: login: grungussuss
body: 
```suggestion
```
created_at: 2025-05-17T14:16:03Z
author_association: NONE
diff_hunk: @@ -1213,18 +1213,27 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE) || HAS_TRAIT(H, TRAIT_EARGRAB) && HAS_TRAIT(src, TRAIT_ORPHAN))
 			var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD)
 			for(var/obj/item/grabbing/G in grabbedby)
 				if(G.limb_grabbed == head)
 					if(G.grabbee == pulledby)
-						if(G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
-							visible_message("<span class='warning'>[src] struggles to break free from [pulledby]'s grip!</span>", \
-											"<span class='warning'>I struggle against [pulledby]'s grip!</span>", null, null, pulledby)
-							to_chat(pulledby, "<span class='warning'>[src] struggles against my grip!</span>")
-							playsound(src.loc, 'sound/combat/grabstruggle.ogg', 50, TRUE, -1)
-							client?.move_delay = world.time + 20
-							return TRUE
+						if(HAS_TRAIT(H, TRAIT_NOSEGRAB))
+							if(G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
+								visible_message("<span class='warning'>[src] struggles to break free from [pulledby]'s grip!</span>", \
+												"<span class='warning'>I struggle against [pulledby]'s grip!</span>", null, null, pulledby)
+								to_chat(pulledby, "<span class='warning'>[src] struggles against my grip!</span>")
+								playsound(src.loc, 'sound/combat/grabstruggle.ogg', 50, TRUE, -1)
+								client?.move_delay = world.time + 20
+								return TRUE
+						if(HAS_TRAIT(H, TRAIT_EARGRAB))
path: code/modules/mob/living/living.dm
user: login: grungussuss
body: 
```suggestion
						if(HAS_TRAIT(H, TRAIT_EARGRAB) && G.sublimb_grabbed == BODY_ZONE_PRECISE_EARS)
```
same thing, also this doesn't actually check if the grabbed person is a child? Is it supposed to do that?
created_at: 2025-05-17T14:16:30Z
author_association: NONE
diff_hunk: @@ -1213,18 +1213,27 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE) || HAS_TRAIT(H, TRAIT_EARGRAB) && HAS_TRAIT(src, TRAIT_ORPHAN))
 			var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD)
 			for(var/obj/item/grabbing/G in grabbedby)
 				if(G.limb_grabbed == head)
 					if(G.grabbee == pulledby)
-						if(G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
-							visible_message("<span class='warning'>[src] struggles to break free from [pulledby]'s grip!</span>", \
-											"<span class='warning'>I struggle against [pulledby]'s grip!</span>", null, null, pulledby)
-							to_chat(pulledby, "<span class='warning'>[src] struggles against my grip!</span>")
-							playsound(src.loc, 'sound/combat/grabstruggle.ogg', 50, TRUE, -1)
-							client?.move_delay = world.time + 20
-							return TRUE
+						if(HAS_TRAIT(H, TRAIT_NOSEGRAB))
+							if(G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
+								visible_message("<span class='warning'>[src] struggles to break free from [pulledby]'s grip!</span>", \
+												"<span class='warning'>I struggle against [pulledby]'s grip!</span>", null, null, pulledby)
+								to_chat(pulledby, "<span class='warning'>[src] struggles against my grip!</span>")
+								playsound(src.loc, 'sound/combat/grabstruggle.ogg', 50, TRUE, -1)
+								client?.move_delay = world.time + 20
+								return TRUE
+						if(HAS_TRAIT(H, TRAIT_EARGRAB))
+							if(G.sublimb_grabbed == BODY_ZONE_PRECISE_EARS)
path: code/modules/mob/living/living.dm
user: login: grungussuss
body: 
```suggestion
```
created_at: 2025-05-17T14:17:09Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+/obj/effect/proc_holder/spell/targeted/seek_orphan
+	name = "Brat Detector"
+	overlay_state = "recruit_acolyte"
+	recharge_time = 10 SECONDS
+	max_targets = 0
+	cast_without_targets = TRUE
+
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/cast(list/targets, mob/user = usr)
+	. = ..()
+	var/list/orphans = list()
+	for(var/mob/living/carbon/human/H in GLOB.mob_list)
+		if(!H.client)
+			continue
+		if(!HAS_TRAIT(H, TRAIT_ORPHAN))
+			continue
+		orphans += H
+
+	if(!length(orphans))
+		to_chat(user, span_red("..There isn't any"))
+		return
+
+	var/mob/living/carbon/human/tracked_orphan = input(user, "Which One?", "Seek Orphan") as null|anything in orphans
+	if(!tracked_orphan)
+		to_chat(user, span_warning("I can't find any of those brats.."))
+		return
+
+	var/message = where_orphan(user, tracked_orphan)
+	to_chat(user, span_blue("That brat is [message]."))
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/proc/where_orphan(mob/user, mob/living/carbon/human/tracked_orphan)
+	var/turf/our_turf = get_turf(user)
+	var/turf/their_turf = get_turf(tracked_orphan)
+	var/our_z = our_turf?.z
+	var/their_z = their_turf?.z
+	var/dist = get_dist(our_turf, their_turf)
+	var/dir = get_dir(our_turf, their_turf)
+	var/dir_text = dir2text(dir)
+	var/distance_text
+
+	switch(dist)
+		if(0 to 7)
+			distance_text = "very close"
+		if(8 to 15)
+			distance_text = "close"
+		if(16 to 35)
+			distance_text = "near"
+		if(36 to 49)
+			distance_text = "somewhat near"
+		if(50 to 70)
+			distance_text = "slightly far"
+		if(71 to 98)
+			distance_text = "somewhat far"
+		if(99 to 127)
+			distance_text = "far"
+		else
+			distance_text = "very far"
+
+	if(our_z != their_z)
+		var/z_text = our_z > their_z ? "below me" : "above me"
+		return "[distance_text]...and to the [dir_text], [z_text]"
+
+	return "[distance_text]...and to the [dir_text]"
+
+/obj/effect/proc_holder/spell/self/hag_call
+	name = "Hag's Call"
+	recharge_time = 5 MINUTES
path: code/modules/spells/spell_types/invoked/matron.dm
user: login: grungussuss
body: too much cooldown? I'd say 1 minute is enough.
created_at: 2025-05-17T14:20:14Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+/obj/effect/proc_holder/spell/targeted/seek_orphan
+	name = "Brat Detector"
+	overlay_state = "recruit_acolyte"
+	recharge_time = 10 SECONDS
+	max_targets = 0
+	cast_without_targets = TRUE
+
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/cast(list/targets, mob/user = usr)
+	. = ..()
+	var/list/orphans = list()
+	for(var/mob/living/carbon/human/H in GLOB.mob_list)
+		if(!H.client)
+			continue
+		if(!HAS_TRAIT(H, TRAIT_ORPHAN))
+			continue
+		orphans += H
+
+	if(!length(orphans))
+		to_chat(user, span_red("..There isn't any"))
+		return
+
+	var/mob/living/carbon/human/tracked_orphan = input(user, "Which One?", "Seek Orphan") as null|anything in orphans
+	if(!tracked_orphan)
+		to_chat(user, span_warning("I can't find any of those brats.."))
+		return
+
+	var/message = where_orphan(user, tracked_orphan)
+	to_chat(user, span_blue("That brat is [message]."))
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/proc/where_orphan(mob/user, mob/living/carbon/human/tracked_orphan)
+	var/turf/our_turf = get_turf(user)
+	var/turf/their_turf = get_turf(tracked_orphan)
+	var/our_z = our_turf?.z
+	var/their_z = their_turf?.z
+	var/dist = get_dist(our_turf, their_turf)
+	var/dir = get_dir(our_turf, their_turf)
+	var/dir_text = dir2text(dir)
+	var/distance_text
+
+	switch(dist)
+		if(0 to 7)
+			distance_text = "very close"
+		if(8 to 15)
+			distance_text = "close"
+		if(16 to 35)
+			distance_text = "near"
+		if(36 to 49)
+			distance_text = "somewhat near"
+		if(50 to 70)
+			distance_text = "slightly far"
+		if(71 to 98)
+			distance_text = "somewhat far"
+		if(99 to 127)
+			distance_text = "far"
+		else
+			distance_text = "very far"
+
+	if(our_z != their_z)
+		var/z_text = our_z > their_z ? "below me" : "above me"
+		return "[distance_text]...and to the [dir_text], [z_text]"
+
+	return "[distance_text]...and to the [dir_text]"
+
+/obj/effect/proc_holder/spell/self/hag_call
+	name = "Hag's Call"
+	recharge_time = 5 MINUTES
+	warnie = "spellwarning"
+	overlay_state = "message"
+
+/obj/effect/proc_holder/spell/self/hag_call/cast(list/targets, mob/user)
+	var/input = input(user, "Which one of those brats am I trying to call?")
path: code/modules/spells/spell_types/invoked/matron.dm
user: login: grungussuss
body: I'd say make an associative list of mobs with real name as key and mob ref as the value, then use `browser_input_list()` to display them.

something like:
```
var/list/valid_targets
for(mob/living/carbon/human in GLOB.human_list)
    if(HAS_TRAIT(HL, TRAIT_ORPHAN) || HAS_TRAIT(HL, TRAIT_OLDPARTY))
        valid_targets[HL.real_name] = HL
```
```
var/selected_target = browser_input_list(user, "Who does the hag call?", "THE HAG", valid_targets)

if(!selected_target)
    return FALSE
```
then continue onwards. Much cleaner and more qol.
created_at: 2025-05-17T14:26:43Z
author_association: NONE
diff_hunk: @@ -56,6 +56,7 @@
 #define TRAIT_DREAM_WATCHER				"Noc Blessed" //Unique Trait of the Dream Watcher Town Elder Class, they have a chance to know about antags or gods influences.
 #define TRAIT_HOLLOWBONES				"Hollow Bones"
 #define TRAIT_AMAZING_BACK				"Light Load"
+#define TRAIT_ORPHAN 					"Orphan" //Does nothing except for trait check
path: code/__DEFINES/traits.dm
user: login: grungussuss
body: 
```suggestion
/// applied to orphans
#define TRAIT_ORPHAN 					"Orphan"
```
created_at: 2025-05-17T14:27:26Z
author_association: NONE
diff_hunk: @@ -1213,18 +1213,27 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE) || HAS_TRAIT(H, TRAIT_EARGRAB) && HAS_TRAIT(src, TRAIT_ORPHAN))
 			var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD)
 			for(var/obj/item/grabbing/G in grabbedby)
 				if(G.limb_grabbed == head)
 					if(G.grabbee == pulledby)
-						if(G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
-							visible_message("<span class='warning'>[src] struggles to break free from [pulledby]'s grip!</span>", \
-											"<span class='warning'>I struggle against [pulledby]'s grip!</span>", null, null, pulledby)
-							to_chat(pulledby, "<span class='warning'>[src] struggles against my grip!</span>")
-							playsound(src.loc, 'sound/combat/grabstruggle.ogg', 50, TRUE, -1)
-							client?.move_delay = world.time + 20
-							return TRUE
+						if(HAS_TRAIT(H, TRAIT_NOSEGRAB))
+							if(G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
+								visible_message("<span class='warning'>[src] struggles to break free from [pulledby]'s grip!</span>", \
path: code/modules/mob/living/living.dm
user: login: grungussuss
body: preferably you fix these span classes into span macros, but it's not mandatory in your pr.
created_at: 2025-05-17T14:28:42Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+/obj/effect/proc_holder/spell/targeted/seek_orphan
+	name = "Brat Detector"
+	overlay_state = "recruit_acolyte"
+	recharge_time = 10 SECONDS
+	max_targets = 0
+	cast_without_targets = TRUE
+
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/cast(list/targets, mob/user = usr)
+	. = ..()
+	var/list/orphans = list()
+	for(var/mob/living/carbon/human/H in GLOB.mob_list)
+		if(!H.client)
+			continue
+		if(!HAS_TRAIT(H, TRAIT_ORPHAN))
+			continue
+		orphans += H
+
+	if(!length(orphans))
+		to_chat(user, span_red("..There isn't any"))
+		return
+
+	var/mob/living/carbon/human/tracked_orphan = input(user, "Which One?", "Seek Orphan") as null|anything in orphans
+	if(!tracked_orphan)
+		to_chat(user, span_warning("I can't find any of those brats.."))
+		return
+
+	var/message = where_orphan(user, tracked_orphan)
+	to_chat(user, span_blue("That brat is [message]."))
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/proc/where_orphan(mob/user, mob/living/carbon/human/tracked_orphan)
+	var/turf/our_turf = get_turf(user)
+	var/turf/their_turf = get_turf(tracked_orphan)
+	var/our_z = our_turf?.z
+	var/their_z = their_turf?.z
+	var/dist = get_dist(our_turf, their_turf)
+	var/dir = get_dir(our_turf, their_turf)
+	var/dir_text = dir2text(dir)
+	var/distance_text
+
+	switch(dist)
+		if(0 to 7)
+			distance_text = "very close"
+		if(8 to 15)
+			distance_text = "close"
+		if(16 to 35)
+			distance_text = "near"
+		if(36 to 49)
+			distance_text = "somewhat near"
+		if(50 to 70)
+			distance_text = "slightly far"
+		if(71 to 98)
+			distance_text = "somewhat far"
+		if(99 to 127)
+			distance_text = "far"
+		else
+			distance_text = "very far"
+
+	if(our_z != their_z)
+		var/z_text = our_z > their_z ? "below me" : "above me"
+		return "[distance_text]...and to the [dir_text], [z_text]"
+
+	return "[distance_text]...and to the [dir_text]"
+
+/obj/effect/proc_holder/spell/self/hag_call
+	name = "Hag's Call"
+	recharge_time = 5 MINUTES
path: code/modules/spells/spell_types/invoked/matron.dm
user: login: SarmentiCampbell
body: I'm really unsure if it'll be fine, but i could put it to 1 minutes.
created_at: 2025-05-17T14:36:04Z
author_association: NONE
diff_hunk: @@ -1213,18 +1213,27 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE) || HAS_TRAIT(H, TRAIT_EARGRAB) && HAS_TRAIT(src, TRAIT_ORPHAN))
 			var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD)
 			for(var/obj/item/grabbing/G in grabbedby)
 				if(G.limb_grabbed == head)
 					if(G.grabbee == pulledby)
-						if(G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
-							visible_message("<span class='warning'>[src] struggles to break free from [pulledby]'s grip!</span>", \
-											"<span class='warning'>I struggle against [pulledby]'s grip!</span>", null, null, pulledby)
-							to_chat(pulledby, "<span class='warning'>[src] struggles against my grip!</span>")
-							playsound(src.loc, 'sound/combat/grabstruggle.ogg', 50, TRUE, -1)
-							client?.move_delay = world.time + 20
-							return TRUE
+						if(HAS_TRAIT(H, TRAIT_NOSEGRAB))
+							if(G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
+								visible_message("<span class='warning'>[src] struggles to break free from [pulledby]'s grip!</span>", \
+												"<span class='warning'>I struggle against [pulledby]'s grip!</span>", null, null, pulledby)
+								to_chat(pulledby, "<span class='warning'>[src] struggles against my grip!</span>")
+								playsound(src.loc, 'sound/combat/grabstruggle.ogg', 50, TRUE, -1)
+								client?.move_delay = world.time + 20
+								return TRUE
+						if(HAS_TRAIT(H, TRAIT_EARGRAB))
path: code/modules/mob/living/living.dm
user: login: SarmentiCampbell
body: Yes, It'll check above if they had the orphan trait.
created_at: 2025-05-17T14:41:33Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+/obj/effect/proc_holder/spell/targeted/seek_orphan
+	name = "Brat Detector"
+	overlay_state = "recruit_acolyte"
+	recharge_time = 10 SECONDS
+	max_targets = 0
+	cast_without_targets = TRUE
+
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/cast(list/targets, mob/user = usr)
+	. = ..()
+	var/list/orphans = list()
+	for(var/mob/living/carbon/human/H in GLOB.mob_list)
+		if(!H.client)
+			continue
+		if(!HAS_TRAIT(H, TRAIT_ORPHAN))
+			continue
+		orphans += H
+
+	if(!length(orphans))
+		to_chat(user, span_red("..There isn't any"))
+		return
+
+	var/mob/living/carbon/human/tracked_orphan = input(user, "Which One?", "Seek Orphan") as null|anything in orphans
+	if(!tracked_orphan)
+		to_chat(user, span_warning("I can't find any of those brats.."))
+		return
+
+	var/message = where_orphan(user, tracked_orphan)
+	to_chat(user, span_blue("That brat is [message]."))
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/proc/where_orphan(mob/user, mob/living/carbon/human/tracked_orphan)
+	var/turf/our_turf = get_turf(user)
+	var/turf/their_turf = get_turf(tracked_orphan)
+	var/our_z = our_turf?.z
+	var/their_z = their_turf?.z
+	var/dist = get_dist(our_turf, their_turf)
+	var/dir = get_dir(our_turf, their_turf)
+	var/dir_text = dir2text(dir)
+	var/distance_text
+
+	switch(dist)
+		if(0 to 7)
+			distance_text = "very close"
+		if(8 to 15)
+			distance_text = "close"
+		if(16 to 35)
+			distance_text = "near"
+		if(36 to 49)
+			distance_text = "somewhat near"
+		if(50 to 70)
+			distance_text = "slightly far"
+		if(71 to 98)
+			distance_text = "somewhat far"
+		if(99 to 127)
+			distance_text = "far"
+		else
+			distance_text = "very far"
+
+	if(our_z != their_z)
+		var/z_text = our_z > their_z ? "below me" : "above me"
+		return "[distance_text]...and to the [dir_text], [z_text]"
+
+	return "[distance_text]...and to the [dir_text]"
+
+/obj/effect/proc_holder/spell/self/hag_call
+	name = "Hag's Call"
+	recharge_time = 5 MINUTES
+	warnie = "spellwarning"
+	overlay_state = "message"
+
+/obj/effect/proc_holder/spell/self/hag_call/cast(list/targets, mob/user)
+	var/input = input(user, "Which one of those brats am I trying to call?")
path: code/modules/spells/spell_types/invoked/matron.dm
user: login: SarmentiCampbell
body: okay, so the thing is, i wanted the matron to remember names, and write it down, i could have done it like the brat's detector but i thought it'll be better for them to yell it out and remember names.
created_at: 2025-05-17T14:44:15Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+/obj/effect/proc_holder/spell/targeted/seek_orphan
+	name = "Brat Detector"
+	overlay_state = "recruit_acolyte"
+	recharge_time = 10 SECONDS
+	max_targets = 0
+	cast_without_targets = TRUE
+
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/cast(list/targets, mob/user = usr)
+	. = ..()
+	var/list/orphans = list()
+	for(var/mob/living/carbon/human/H in GLOB.mob_list)
+		if(!H.client)
+			continue
+		if(!HAS_TRAIT(H, TRAIT_ORPHAN))
+			continue
+		orphans += H
+
+	if(!length(orphans))
+		to_chat(user, span_red("..There isn't any"))
+		return
+
+	var/mob/living/carbon/human/tracked_orphan = input(user, "Which One?", "Seek Orphan") as null|anything in orphans
+	if(!tracked_orphan)
+		to_chat(user, span_warning("I can't find any of those brats.."))
+		return
+
+	var/message = where_orphan(user, tracked_orphan)
+	to_chat(user, span_blue("That brat is [message]."))
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/proc/where_orphan(mob/user, mob/living/carbon/human/tracked_orphan)
+	var/turf/our_turf = get_turf(user)
+	var/turf/their_turf = get_turf(tracked_orphan)
+	var/our_z = our_turf?.z
+	var/their_z = their_turf?.z
+	var/dist = get_dist(our_turf, their_turf)
+	var/dir = get_dir(our_turf, their_turf)
+	var/dir_text = dir2text(dir)
+	var/distance_text
+
+	switch(dist)
+		if(0 to 7)
+			distance_text = "very close"
+		if(8 to 15)
+			distance_text = "close"
+		if(16 to 35)
+			distance_text = "near"
+		if(36 to 49)
+			distance_text = "somewhat near"
+		if(50 to 70)
+			distance_text = "slightly far"
+		if(71 to 98)
+			distance_text = "somewhat far"
+		if(99 to 127)
+			distance_text = "far"
+		else
+			distance_text = "very far"
+
+	if(our_z != their_z)
+		var/z_text = our_z > their_z ? "below me" : "above me"
+		return "[distance_text]...and to the [dir_text], [z_text]"
+
+	return "[distance_text]...and to the [dir_text]"
+
+/obj/effect/proc_holder/spell/self/hag_call
+	name = "Hag's Call"
+	recharge_time = 5 MINUTES
+	warnie = "spellwarning"
+	overlay_state = "message"
+
+/obj/effect/proc_holder/spell/self/hag_call/cast(list/targets, mob/user)
+	var/input = input(user, "Which one of those brats am I trying to call?")
path: code/modules/spells/spell_types/invoked/matron.dm
user: login: grungussuss
body: > okay, so the thing is, i wanted the matron to remember names, and write it down, i could have done it like the brat's detector but i thought it'll be better for them to yell it out and remember names.

That's fine if you want to do that, use the browser datum though, it would be `browser_input_text()`

also instead of iterating through mob_list, iterate through human_list.
created_at: 2025-05-17T14:51:53Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+/obj/effect/proc_holder/spell/targeted/seek_orphan
+	name = "Brat Detector"
+	overlay_state = "recruit_acolyte"
+	recharge_time = 10 SECONDS
+	max_targets = 0
+	cast_without_targets = TRUE
+
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/cast(list/targets, mob/user = usr)
+	. = ..()
+	var/list/orphans = list()
+	for(var/mob/living/carbon/human/H in GLOB.mob_list)
+		if(!H.client)
+			continue
+		if(!HAS_TRAIT(H, TRAIT_ORPHAN))
+			continue
+		orphans += H
+
+	if(!length(orphans))
+		to_chat(user, span_red("..There isn't any"))
+		return
+
+	var/mob/living/carbon/human/tracked_orphan = input(user, "Which One?", "Seek Orphan") as null|anything in orphans
+	if(!tracked_orphan)
+		to_chat(user, span_warning("I can't find any of those brats.."))
+		return
+
+	var/message = where_orphan(user, tracked_orphan)
+	to_chat(user, span_blue("That brat is [message]."))
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/proc/where_orphan(mob/user, mob/living/carbon/human/tracked_orphan)
+	var/turf/our_turf = get_turf(user)
+	var/turf/their_turf = get_turf(tracked_orphan)
+	var/our_z = our_turf?.z
+	var/their_z = their_turf?.z
+	var/dist = get_dist(our_turf, their_turf)
+	var/dir = get_dir(our_turf, their_turf)
+	var/dir_text = dir2text(dir)
+	var/distance_text
+
+	switch(dist)
+		if(0 to 7)
+			distance_text = "very close"
+		if(8 to 15)
+			distance_text = "close"
+		if(16 to 35)
+			distance_text = "near"
+		if(36 to 49)
+			distance_text = "somewhat near"
+		if(50 to 70)
+			distance_text = "slightly far"
+		if(71 to 98)
+			distance_text = "somewhat far"
+		if(99 to 127)
+			distance_text = "far"
+		else
+			distance_text = "very far"
+
+	if(our_z != their_z)
+		var/z_text = our_z > their_z ? "below me" : "above me"
+		return "[distance_text]...and to the [dir_text], [z_text]"
+
+	return "[distance_text]...and to the [dir_text]"
+
+/obj/effect/proc_holder/spell/self/hag_call
+	name = "Hag's Call"
+	recharge_time = 5 MINUTES
+	warnie = "spellwarning"
+	overlay_state = "message"
+
+/obj/effect/proc_holder/spell/self/hag_call/cast(list/targets, mob/user)
+	var/input = input(user, "Which one of those brats am I trying to call?")
path: code/modules/spells/spell_types/invoked/matron.dm
user: login: SarmentiCampbell
body: alright, give me a minute to do the same for the brat detector
created_at: 2025-05-17T14:59:29Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+/obj/effect/proc_holder/spell/targeted/seek_orphan
+	name = "Brat Detector"
+	overlay_state = "recruit_acolyte"
+	recharge_time = 10 SECONDS
+	max_targets = 0
+	cast_without_targets = TRUE
+
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/cast(list/targets, mob/user = usr)
+	. = ..()
+	var/list/orphans = list()
+	for(var/mob/living/carbon/human/H in GLOB.mob_list)
+		if(!H.client)
+			continue
+		if(!HAS_TRAIT(H, TRAIT_ORPHAN))
+			continue
+		orphans += H
+
+	if(!length(orphans))
+		to_chat(user, span_red("..There isn't any"))
+		return
+
+	var/mob/living/carbon/human/tracked_orphan = input(user, "Which One?", "Seek Orphan") as null|anything in orphans
+	if(!tracked_orphan)
+		to_chat(user, span_warning("I can't find any of those brats.."))
+		return
+
+	var/message = where_orphan(user, tracked_orphan)
+	to_chat(user, span_blue("That brat is [message]."))
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/proc/where_orphan(mob/user, mob/living/carbon/human/tracked_orphan)
+	var/turf/our_turf = get_turf(user)
+	var/turf/their_turf = get_turf(tracked_orphan)
+	var/our_z = our_turf?.z
+	var/their_z = their_turf?.z
+	var/dist = get_dist(our_turf, their_turf)
+	var/dir = get_dir(our_turf, their_turf)
+	var/dir_text = dir2text(dir)
+	var/distance_text
+
+	switch(dist)
+		if(0 to 7)
+			distance_text = "very close"
+		if(8 to 15)
+			distance_text = "close"
+		if(16 to 35)
+			distance_text = "near"
+		if(36 to 49)
+			distance_text = "somewhat near"
+		if(50 to 70)
+			distance_text = "slightly far"
+		if(71 to 98)
+			distance_text = "somewhat far"
+		if(99 to 127)
+			distance_text = "far"
+		else
+			distance_text = "very far"
+
+	if(our_z != their_z)
+		var/z_text = our_z > their_z ? "below me" : "above me"
+		return "[distance_text]...and to the [dir_text], [z_text]"
+
+	return "[distance_text]...and to the [dir_text]"
+
+/obj/effect/proc_holder/spell/self/hag_call
+	name = "Hag's Call"
+	recharge_time = 5 MINUTES
+	warnie = "spellwarning"
+	overlay_state = "message"
+
+/obj/effect/proc_holder/spell/self/hag_call/cast(list/targets, mob/user)
+	var/input = input(user, "Which one of those brats am I trying to call?")
path: code/modules/spells/spell_types/invoked/matron.dm
user: login: SarmentiCampbell
body: > > okay, so the thing is, i wanted the matron to remember names, and write it down, i could have done it like the brat's detector but i thought it'll be better for them to yell it out and remember names.
> 
> That's fine if you want to do that, use the browser datum though, it would be `browser_input_text()`
> 
> also instead of iterating through mob_list, iterate through human_list.

var/mob/living/carbon/human/tracked_orphan = browser_input_list(user, "Which One?", "Seek Orphan", orphans) it would be something like this right? @grungussuss 
created_at: 2025-05-17T15:03:08Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+/obj/effect/proc_holder/spell/targeted/seek_orphan
+	name = "Brat Detector"
+	overlay_state = "recruit_acolyte"
+	recharge_time = 10 SECONDS
+	max_targets = 0
+	cast_without_targets = TRUE
+
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/cast(list/targets, mob/user = usr)
+	. = ..()
+	var/list/orphans = list()
+	for(var/mob/living/carbon/human/H in GLOB.mob_list)
+		if(!H.client)
+			continue
+		if(!HAS_TRAIT(H, TRAIT_ORPHAN))
+			continue
+		orphans += H
+
+	if(!length(orphans))
+		to_chat(user, span_red("..There isn't any"))
+		return
+
+	var/mob/living/carbon/human/tracked_orphan = input(user, "Which One?", "Seek Orphan") as null|anything in orphans
+	if(!tracked_orphan)
+		to_chat(user, span_warning("I can't find any of those brats.."))
+		return
+
+	var/message = where_orphan(user, tracked_orphan)
+	to_chat(user, span_blue("That brat is [message]."))
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/proc/where_orphan(mob/user, mob/living/carbon/human/tracked_orphan)
+	var/turf/our_turf = get_turf(user)
+	var/turf/their_turf = get_turf(tracked_orphan)
+	var/our_z = our_turf?.z
+	var/their_z = their_turf?.z
+	var/dist = get_dist(our_turf, their_turf)
+	var/dir = get_dir(our_turf, their_turf)
+	var/dir_text = dir2text(dir)
+	var/distance_text
+
+	switch(dist)
+		if(0 to 7)
+			distance_text = "very close"
+		if(8 to 15)
+			distance_text = "close"
+		if(16 to 35)
+			distance_text = "near"
+		if(36 to 49)
+			distance_text = "somewhat near"
+		if(50 to 70)
+			distance_text = "slightly far"
+		if(71 to 98)
+			distance_text = "somewhat far"
+		if(99 to 127)
+			distance_text = "far"
+		else
+			distance_text = "very far"
+
+	if(our_z != their_z)
+		var/z_text = our_z > their_z ? "below me" : "above me"
+		return "[distance_text]...and to the [dir_text], [z_text]"
+
+	return "[distance_text]...and to the [dir_text]"
+
+/obj/effect/proc_holder/spell/self/hag_call
+	name = "Hag's Call"
+	recharge_time = 5 MINUTES
+	warnie = "spellwarning"
+	overlay_state = "message"
+
+/obj/effect/proc_holder/spell/self/hag_call/cast(list/targets, mob/user)
+	var/input = input(user, "Which one of those brats am I trying to call?")
path: code/modules/spells/spell_types/invoked/matron.dm
user: login: grungussuss
body: > var/mob/living/carbon/human/tracked_orphan = browser_input_list(user, "Which One?", "Seek Orphan", orphans) it would be something like this right? @grungussuss

yes.
created_at: 2025-05-17T15:11:13Z
author_association: NONE
diff_hunk: @@ -1213,18 +1213,25 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if((HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE)) || (HAS_TRAIT(H, TRAIT_EARGRAB) && HAS_TRAIT(src, TRAIT_ORPHAN)))
path: code/modules/mob/living/living.dm
user: login: grungussuss
body: check for youngling age instead of orphan trait here?
created_at: 2025-05-17T15:17:12Z
author_association: NONE
diff_hunk: @@ -1213,18 +1213,25 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if((HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE)) || (HAS_TRAIT(H, TRAIT_EARGRAB) && HAS_TRAIT(src, TRAIT_ORPHAN)))
 			var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD)
 			for(var/obj/item/grabbing/G in grabbedby)
 				if(G.limb_grabbed == head)
 					if(G.grabbee == pulledby)
-						if(G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
+						if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
path: code/modules/mob/living/living.dm
user: login: grungussuss
body: ```suggestion
						if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
```
created_at: 2025-05-17T15:21:27Z
author_association: NONE
diff_hunk: @@ -1213,18 +1213,25 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if((HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE)) || (HAS_TRAIT(H, TRAIT_EARGRAB) && HAS_TRAIT(src, TRAIT_ORPHAN)))
 			var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD)
 			for(var/obj/item/grabbing/G in grabbedby)
 				if(G.limb_grabbed == head)
 					if(G.grabbee == pulledby)
-						if(G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
+						if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
path: code/modules/mob/living/living.dm
user: login: grungussuss
body: just shows that this should be done in a better way, like seperating out these two cases (nosegrab and eargrab).
created_at: 2025-05-17T15:21:58Z
author_association: NONE
diff_hunk: @@ -1213,18 +1213,25 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if((HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE)) || (HAS_TRAIT(H, TRAIT_EARGRAB) && HAS_TRAIT(src, TRAIT_ORPHAN)))
path: code/modules/mob/living/living.dm
user: login: SarmentiCampbell
body: I prefer that it is only for orphans, but we can test this out, i guess
created_at: 2025-05-17T15:22:47Z
author_association: NONE
diff_hunk: @@ -1213,18 +1213,25 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if((HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE)) || (HAS_TRAIT(H, TRAIT_EARGRAB) && HAS_TRAIT(src, TRAIT_ORPHAN)))
 			var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD)
 			for(var/obj/item/grabbing/G in grabbedby)
 				if(G.limb_grabbed == head)
 					if(G.grabbee == pulledby)
-						if(G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
+						if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && G.sublimb_grabbed == BODY_ZONE_PRECISE_NOSE)
path: code/modules/mob/living/living.dm
user: login: SarmentiCampbell
body: personally, i don't wanna touch any of those cause i don't wanna know what'll happen  if i fuck it up tbh.
created_at: 2025-05-17T15:23:30Z
author_association: NONE
diff_hunk: @@ -1213,18 +1213,25 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if((HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE)) || (HAS_TRAIT(H, TRAIT_EARGRAB) && HAS_TRAIT(src, TRAIT_ORPHAN)))
path: code/modules/mob/living/living.dm
user: login: SarmentiCampbell
body: alright, i made it check for youngling age, also modifying pr's desc to mention this, let's hope it doesn't get abused 
created_at: 2025-05-17T15:25:52Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+/obj/effect/proc_holder/spell/targeted/seek_orphan
+	name = "Brat Detector"
+	overlay_state = "recruit_acolyte"
+	recharge_time = 10 SECONDS
+	max_targets = 0
+	cast_without_targets = TRUE
+
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/cast(list/targets, mob/user = usr)
+	. = ..()
+	var/list/orphans = list()
+	for(var/mob/living/carbon/human/H in GLOB.human_list)
+		if(!H.client)
+			continue
+		if(!HAS_TRAIT(H, TRAIT_ORPHAN))
+			continue
+		orphans += H
+
+	if(!length(orphans))
+		to_chat(user, span_red("..There isn't any"))
+		return
+
+	var/mob/living/carbon/human/tracked_orphan = browser_input_list(user, "Which One?", "Seek Orphan", orphans)
+	if(!tracked_orphan)
+		to_chat(user, span_warning("I can't find any of those brats.."))
+		return
+
+	var/message = where_orphan(user, tracked_orphan)
+	to_chat(user, span_blue("That brat is [message]."))
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/proc/where_orphan(mob/user, mob/living/carbon/human/tracked_orphan)
+	var/turf/our_turf = get_turf(user)
+	var/turf/their_turf = get_turf(tracked_orphan)
+	var/our_z = our_turf?.z
+	var/their_z = their_turf?.z
+	var/dist = get_dist(our_turf, their_turf)
+	var/dir = get_dir(our_turf, their_turf)
+	var/dir_text = dir2text(dir)
+	var/distance_text
+
+	switch(dist)
+		if(0 to 7)
+			distance_text = "very close"
+		if(8 to 15)
+			distance_text = "close"
+		if(16 to 35)
+			distance_text = "near"
+		if(36 to 49)
+			distance_text = "somewhat near"
+		if(50 to 70)
+			distance_text = "slightly far"
+		if(71 to 98)
+			distance_text = "somewhat far"
+		if(99 to 127)
+			distance_text = "far"
+		else
+			distance_text = "very far"
+
+	if(our_z != their_z)
+		var/z_text = our_z > their_z ? "below me" : "above me"
+		return "[distance_text]...and to the [dir_text], [z_text]"
+
+	return "[distance_text]...and to the [dir_text]"
+
+/obj/effect/proc_holder/spell/self/hag_call
+	name = "Hag's Call"
+	recharge_time = 1 MINUTES
+	warnie = "spellwarning"
+	overlay_state = "message"
+
+/obj/effect/proc_holder/spell/self/hag_call/cast(list/targets, mob/user)
+	var/input = browser_input_text(user, "Which one of those brats am I trying to call?", "Hag's Call")
+	if(!input)
+		return FALSE
+	if(!user.mind)
path: code/modules/spells/spell_types/invoked/matron.dm
user: login: grungussuss
body: you are checking for the matron's mind here, not the orphan's.
created_at: 2025-05-17T15:26:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+/obj/effect/proc_holder/spell/targeted/seek_orphan
+	name = "Brat Detector"
+	overlay_state = "recruit_acolyte"
+	recharge_time = 10 SECONDS
+	max_targets = 0
+	cast_without_targets = TRUE
+
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/cast(list/targets, mob/user = usr)
+	. = ..()
+	var/list/orphans = list()
+	for(var/mob/living/carbon/human/H in GLOB.human_list)
+		if(!H.client)
+			continue
+		if(!HAS_TRAIT(H, TRAIT_ORPHAN))
+			continue
+		orphans += H
+
+	if(!length(orphans))
+		to_chat(user, span_red("..There isn't any"))
+		return
+
+	var/mob/living/carbon/human/tracked_orphan = browser_input_list(user, "Which One?", "Seek Orphan", orphans)
+	if(!tracked_orphan)
+		to_chat(user, span_warning("I can't find any of those brats.."))
+		return
+
+	var/message = where_orphan(user, tracked_orphan)
+	to_chat(user, span_blue("That brat is [message]."))
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/proc/where_orphan(mob/user, mob/living/carbon/human/tracked_orphan)
+	var/turf/our_turf = get_turf(user)
+	var/turf/their_turf = get_turf(tracked_orphan)
+	var/our_z = our_turf?.z
+	var/their_z = their_turf?.z
+	var/dist = get_dist(our_turf, their_turf)
+	var/dir = get_dir(our_turf, their_turf)
+	var/dir_text = dir2text(dir)
+	var/distance_text
+
+	switch(dist)
+		if(0 to 7)
+			distance_text = "very close"
+		if(8 to 15)
+			distance_text = "close"
+		if(16 to 35)
+			distance_text = "near"
+		if(36 to 49)
+			distance_text = "somewhat near"
+		if(50 to 70)
+			distance_text = "slightly far"
+		if(71 to 98)
+			distance_text = "somewhat far"
+		if(99 to 127)
+			distance_text = "far"
+		else
+			distance_text = "very far"
+
+	if(our_z != their_z)
+		var/z_text = our_z > their_z ? "below me" : "above me"
+		return "[distance_text]...and to the [dir_text], [z_text]"
+
+	return "[distance_text]...and to the [dir_text]"
+
+/obj/effect/proc_holder/spell/self/hag_call
+	name = "Hag's Call"
+	recharge_time = 1 MINUTES
+	warnie = "spellwarning"
+	overlay_state = "message"
+
+/obj/effect/proc_holder/spell/self/hag_call/cast(list/targets, mob/user)
+	var/input = browser_input_text(user, "Which one of those brats am I trying to call?", "Hag's Call")
+	if(!input)
+		return FALSE
+	if(!user.mind)
+		to_chat(user, span_warning("I do not think i'll be able to call for them.."))
+		return FALSE
+	for(var/mob/living/carbon/human/HL in GLOB.human_list)
+		if(HL.real_name == input && HL.mind)
+			if(HAS_TRAIT(HL, TRAIT_ORPHAN) || HAS_TRAIT(HL, TRAIT_OLDPARTY))
+				to_chat(HL, span_reallybig("[HL.real_name]!"))
path: code/modules/spells/spell_types/invoked/matron.dm
user: login: grungussuss
body: it would be really funny if it showed what their name was after parsing through the accents. Say the orphan's name was Clarke, if the Matron had an elvish accent, the orphan would hear "Klarke"

(this is just a suggestion)

But if you want to try it, look into `/datum/species/proc/handle_speech`
created_at: 2025-05-17T15:28:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+/obj/effect/proc_holder/spell/targeted/seek_orphan
+	name = "Brat Detector"
+	overlay_state = "recruit_acolyte"
+	recharge_time = 10 SECONDS
+	max_targets = 0
+	cast_without_targets = TRUE
+
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/cast(list/targets, mob/user = usr)
+	. = ..()
+	var/list/orphans = list()
+	for(var/mob/living/carbon/human/H in GLOB.human_list)
+		if(!H.client)
+			continue
+		if(!HAS_TRAIT(H, TRAIT_ORPHAN))
+			continue
+		orphans += H
+
+	if(!length(orphans))
+		to_chat(user, span_red("..There isn't any"))
+		return
+
+	var/mob/living/carbon/human/tracked_orphan = browser_input_list(user, "Which One?", "Seek Orphan", orphans)
path: code/modules/spells/spell_types/invoked/matron.dm
user: login: grungussuss
body: I think it will show up as unknown if they are disguised, you might want to do an associative list with real_name as the key.
created_at: 2025-05-17T15:33:37Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+/obj/effect/proc_holder/spell/targeted/seek_orphan
+	name = "Brat Detector"
+	overlay_state = "recruit_acolyte"
+	recharge_time = 10 SECONDS
+	max_targets = 0
+	cast_without_targets = TRUE
+
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/cast(list/targets, mob/user = usr)
+	. = ..()
+	var/list/orphans = list()
+	for(var/mob/living/carbon/human/H in GLOB.human_list)
+		if(!H.client)
path: code/modules/spells/spell_types/invoked/matron.dm
user: login: grungussuss
body: ```suggestion
		if(!H.mind)
```
created_at: 2025-05-17T15:35:07Z
author_association: NONE
diff_hunk: @@ -1213,18 +1213,25 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if((HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE)) || (HAS_TRAIT(H, TRAIT_EARGRAB) && src.age == AGE_CHILD))
path: code/modules/mob/living/living.dm
user: login: grungussuss
body: ```suggestion
		if((HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE)) || (HAS_TRAIT(H, TRAIT_EARGRAB) && age == AGE_CHILD))
```
you may access variables on the datum without accessing it through src, because it is already implied.
created_at: 2025-05-17T15:35:45Z
author_association: NONE
diff_hunk: @@ -1213,18 +1213,25 @@
 		attackhostage()
 	if(ishuman(L))
 		var/mob/living/carbon/human/H = L
-		if(HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE))
+		if((HAS_TRAIT(H, TRAIT_NOSEGRAB) && !HAS_TRAIT(src, TRAIT_MISSING_NOSE)) || (HAS_TRAIT(H, TRAIT_EARGRAB) && HAS_TRAIT(src, TRAIT_ORPHAN)))
path: code/modules/mob/living/living.dm
user: login: grungussuss
body: it'll be funny, don't worry.
created_at: 2025-05-17T15:36:36Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+/obj/effect/proc_holder/spell/targeted/seek_orphan
+	name = "Brat Detector"
+	overlay_state = "recruit_acolyte"
+	recharge_time = 10 SECONDS
+	max_targets = 0
+	cast_without_targets = TRUE
+
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/cast(list/targets, mob/user = usr)
+	. = ..()
+	var/list/orphans = list()
+	for(var/mob/living/carbon/human/H in GLOB.human_list)
+		if(!H.client)
+			continue
+		if(!HAS_TRAIT(H, TRAIT_ORPHAN))
+			continue
+		orphans += H
+
+	if(!length(orphans))
+		to_chat(user, span_red("..There isn't any"))
+		return
+
+	var/mob/living/carbon/human/tracked_orphan = browser_input_list(user, "Which One?", "Seek Orphan", orphans)
+	if(!tracked_orphan)
+		to_chat(user, span_warning("I can't find any of those brats.."))
+		return
+
+	var/message = where_orphan(user, tracked_orphan)
+	to_chat(user, span_blue("That brat is [message]."))
+
+/obj/effect/proc_holder/spell/targeted/seek_orphan/proc/where_orphan(mob/user, mob/living/carbon/human/tracked_orphan)
+	var/turf/our_turf = get_turf(user)
+	var/turf/their_turf = get_turf(tracked_orphan)
+	var/our_z = our_turf?.z
+	var/their_z = their_turf?.z
+	var/dist = get_dist(our_turf, their_turf)
+	var/dir = get_dir(our_turf, their_turf)
+	var/dir_text = dir2text(dir)
+	var/distance_text
+
+	switch(dist)
+		if(0 to 7)
+			distance_text = "very close"
+		if(8 to 15)
+			distance_text = "close"
+		if(16 to 35)
+			distance_text = "near"
+		if(36 to 49)
+			distance_text = "somewhat near"
+		if(50 to 70)
+			distance_text = "slightly far"
+		if(71 to 98)
+			distance_text = "somewhat far"
+		if(99 to 127)
+			distance_text = "far"
+		else
+			distance_text = "very far"
+
+	if(our_z != their_z)
+		var/z_text = our_z > their_z ? "below me" : "above me"
+		return "[distance_text]...and to the [dir_text], [z_text]"
+
+	return "[distance_text]...and to the [dir_text]"
+
+/obj/effect/proc_holder/spell/self/hag_call
+	name = "Hag's Call"
+	recharge_time = 1 MINUTES
+	warnie = "spellwarning"
+	overlay_state = "message"
+
+/obj/effect/proc_holder/spell/self/hag_call/cast(list/targets, mob/user)
+	var/input = browser_input_text(user, "Which one of those brats am I trying to call?", "Hag's Call")
+	if(!input)
+		return FALSE
+	if(!user.mind)
path: code/modules/spells/spell_types/invoked/matron.dm
user: login: SarmentiCampbell
body: i've took this from speak with dead souls so it was uhh erm i have no excuses, i'll do it once i got time
created_at: 2025-05-17T15:42:00Z
author_association: NONE

PR comments:
user: login: aberrantQuesrist
created_at: 2025-05-17T05:45:42Z
body: Incredible.
author_association: COLLABORATOR
user: login: Ilovekodingz
created_at: 2025-05-17T13:05:56Z
body: holy peak
author_association: NONE
user: login: grungussuss
created_at: 2025-05-17T14:04:25Z
body: I had a dream, which was not all a dream.
The compiler was extinguish'd, and the dmlang
Did wander darkling in the eternal github space,
Rayless, and pathless, and the icy planemaster
Swung blind and blackening in the bugless space;
Morn came and went—and came, and brought no compile,
And men forgot their codespaces in the dread
Of this their desolation; and all hearts
Were chill'd into a selfish prayer for balance:
And they did live by git—and the gittrunk,
The palaces of crowned maintainers—the codebase,
The habitations of all things which code,
Were burnt for balance; servers were consum'd,
And coders were gather'd round their blazing VScodes
To look once more into each other's face;
Happy were those who dwelt within the eye
Of the balance prs, and their commit-access:
A fearful hope was all the world contain'd;
bugs were set  afire, but hour by hour
They fell and faded—and the cacklin players
Extinguish'd with a crash—and all was black.
The brows of coders by the despairing light
Wore an unearthly aspect, as by fits
The flashes fell upon them; some lay down
And hid their eyes and wept; and some did rest
Their chins upon their clenched hands, and smil'd;
And others hurried to and fro, and fed
Their funeral piles with fuel, and look'd up
With mad disquietude on the dull pull requests tab,
The pall of a past world; and then again
With curses cast them down upon the dust,
And gnash'd their teeth and howl'd: the wild birds shriek'd
And, terrified, did flutter on the ground,
And flap their useless wings; the wildest brutes
Came tame and tremulous; and coders crawl'd
And twin'd themselves among the multitude,
Hissing, but stingless—they were slain for food.
And War, which for a moment was no more,
Did glut himself again: a meal was bought
With blood, and each sate sullenly apart
Gorging himself in gloom: no love was left;
All earth was but one thought—and that was death
Immediate and inglorious; and the pang
Of famine fed upon all entrails—men
Died, and their bones were tombless as their flesh;
The meagre by the meagre were devour'd,
Even maintainers assail'd their headcoders, all save one,
And he was faithful to a corse, and kept
The players and balanceposters and hungering contributors at bay,
Till hunger clung them, or the dropping dead
Lur'd their lank jaws; himself sought out no food,
But with a piteous and perpetual moan,
And a quick desolate cry, licking the hand
Which answer'd not with a caress—he died.
The crowd was famish'd by degrees; but two
Of an enormous server did survive,
And they were enemies: they met beside
The dying embers of an altar-place
Where had been heap'd a mass of holy things
For an unholy usage; they rak'd up,
And shivering scrap'd with their cold skeleton hands
The feeble ashes, and their feeble breath
Blew for a little life, and made a flame
Which was a mockery; then they lifted up
Their eyes as it grew lighter, and beheld
Each other's aspects—saw, and shriek'd, and died—
Even of their mutual hideousness they died,
Unknowing who he was upon whose brow
Bugs had written code 0 compile error. The return value was void,
The populous and the powerful was a lump,
Seasonless, herbless, treeless, manless, lifeless—
A lump of death—a chaos of hard clay.
The defines, helpers and procs all stood still,
And nothing stirr'd within their silent depths;
Compilers CPUless lay rotting on the heap,
And their RAM fell down piecemeal: as they dropp'd
They slept on the abyss without a surge—
The requests were dead; the stack were in their grave,
The GPU, their mistress, had expir'd before;
The traffic were wither'd in the stagnant motherboard,
And the PCI perish'd; Darkness had no need
Of aid from them—She was the Universe.
author_association: NONE
user: login: grungussuss
created_at: 2025-05-17T14:32:06Z
body: oh yeah, doesn't seem like you're actually applying the orphan trait to orphans anywhere?
author_association: NONE
user: login: grungussuss
created_at: 2025-05-17T14:32:30Z
body: let me know if you're confused with any comments (on github).
author_association: NONE
user: login: SmokingCrackForChrist
created_at: 2025-05-17T18:39:21Z
body: I was told directly by ook that the reason Matron doesn't have master climbing is because she's old and 'not what she used to be' - this is what lets you fall down from one Z-level without any damage. (This opinion was a few months ago just as a heads up.)

Personally I'd keep climbing below master. She's already cracked as-is, being able to jump off roofs and use the city like a jungle-gym takes it a step too far in my eyes.

Otherwise, amazing PR!
author_association: NONE
user: login: HiphinTheClownshyphen
created_at: 2025-05-17T20:16:02Z
body: > I was told directly by ook that the reason Matron doesn't have master climbing is because she's old and 'not what she used to be' - this is what lets you fall down from one Z-level without any damage. (This opinion was a few months ago just as a heads up.)
> 
> Personally I'd keep climbing below master. She's already cracked as-is, being able to jump off roofs and use the city like a jungle-gym takes it a step too far in my eyes.
> 
> Otherwise, amazing PR!

Honestly, I have to defend this PR here and say that climbing skill effecting the fall is the dumb thing here, even the description of why you didn't get hurt has no idea why or how that would work, so its "with my dexterity". A change can be made outside this PR about how that is handled, but it's not worth denying the -1 second climb speed because of a weird design choice 
author_association: NONE