diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm
index 61713724291..46450071afb 100644
--- a/code/game/objects/items/devices/PDA/PDA.dm
+++ b/code/game/objects/items/devices/PDA/PDA.dm
@@ -1198,6 +1198,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
user.drop_item()
C.loc = src
pai = C
+ pai.update_location()//This notifies the pAI that they've been slotted into a PDA
user << "You slot \the [C] into [src]."
nanomanager.update_uis(src) // update all UIs attached to src
else if(istype(C, /obj/item/weapon/pen))
diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index 7edf72e9a50..3e3910ef0b0 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -323,4 +323,24 @@
if(pai && pai.client && !pai.canmove)
var/rendered = "[text]"
pai.show_message(rendered, 2)
- ..()
\ No newline at end of file
+ ..()
+
+/obj/item/device/paicard/dropped(mob/user)
+
+ ///When an object is put into a container, drop fires twice.
+ //once with it on the floor, and then once in the container
+ //We only care about the second one
+ if (istype(loc, /obj/item/weapon/storage)) //The second drop reads the container its placed into as the location
+ update_location()
+
+
+/obj/item/device/paicard/equipped(var/mob/user, var/slot)
+ ..()
+ update_location(slot)
+
+/obj/item/device/paicard/proc/update_location(var/slotnumber = null)
+ if (!slotnumber)
+ if (istype(loc, /mob))
+ slotnumber = get_equip_slot()
+
+ report_onmob_location(1, slotnumber, pai)
\ No newline at end of file
diff --git a/code/modules/mob/holder.dm b/code/modules/mob/holder.dm
index a4db72fa9cb..a4525bfb3d1 100644
--- a/code/modules/mob/holder.dm
+++ b/code/modules/mob/holder.dm
@@ -12,6 +12,10 @@
var/name_dead
var/isalive
+ var/last_loc_general//This stores a general location of the object. Ie, a container or a mob
+ var/last_loc_specific//This stores specific extra information about the location, pocket, hand, worn on head, etc. Only relevant to mobs
+ var/checkverb
+
/obj/item/weapon/holder/New()
if (!item_state)
item_state = icon_state
@@ -25,17 +29,18 @@
/obj/item/weapon/holder/process()
- if(!get_holding_mob())
+ if (!(istype(loc,/obj/item/weapon/storage)))//Mobs bug out if placed directly into a container
+ if(!get_holding_mob())
- for(var/mob/M in contents)
+ for(var/mob/M in contents)
- var/atom/movable/mob_container
- mob_container = M
- mob_container.loc = src.loc//if the holder was placed into a disposal, this should place the animal in the disposal
- //mob_container.forceMove(get_turf(src))
- M.reset_view()
-
- qdel(src)
+ var/atom/movable/mob_container
+ mob_container = M
+ mob_container.loc = src.loc//if the holder was placed into a disposal, this should place the animal in the disposal
+ M.reset_view()
+ M.verbs -= /mob/living/proc/get_holder_location
+ qdel(src)
+ return
if (isalive && contained.stat == DEAD)
held_death(1)//If we get here, it means the mob died sometime after we picked it up. We pass in 1 so that we can play its deathmessage
@@ -43,22 +48,31 @@
for(var/mob/M in src.contents)
M.attackby(W,user)
-/obj/item/weapon/holder/proc/get_holding_mob()
- //This function will return the mob which is holding this holder, or null if it's not held
- //It recurses up the hierarchy out of containers until it reaches a mob, or aturf, or hits the limit
- var/x = 0//As a safety, we'll crawl up a maximum of five layers
- var/atom/a = src
- while (x < 5)
- x++
- a = a.loc
- if (istype(a, /turf))
- return null//We must be on a table or a floor, or maybe in a wall. Either way we're not held.
+/obj/item/weapon/holder/dropped(mob/user)
+
+ ///When an object is put into a container, drop fires twice.
+ //once with it on the floor, and then once in the container
+ //This conditional allows us to ignore that first one. Handling of mobs dropped on the floor is done in process
+ if (istype(loc, /turf))
+ return
+
+ if (istype(loc, /obj/item/weapon/storage)) //The second drop reads the container its placed into as the location
+ update_location()
+
+
+/obj/item/weapon/holder/equipped(var/mob/user, var/slot)
+ ..()
+ update_location(slot)
+
+/obj/item/weapon/holder/proc/update_location(var/slotnumber = null)
+ if (!slotnumber)
+ if (istype(loc, /mob))
+ slotnumber = get_equip_slot()
+
+ report_onmob_location(1, slotnumber, contained)
+
- if (istype(a, /mob))
- return a
- //If none of the above are true, we must be inside a box or backpack or something. Keep recursing up.
- return null//If we get here, the holder must be buried many layers deep in nested containers. Shouldn't happen
/obj/item/weapon/holder/attack_self(mob/M as mob)
@@ -115,22 +129,43 @@
grabber << "Your hand is full!"
return
+ src.verbs += /mob/living/proc/get_holder_location//This has to be before we move the mob into the holder
- var/obj/item/weapon/holder/H = new holder_type(loc)
- src.loc = H
- H.name = loc.name
- H.attack_hand(grabber)
- H.contained = src
- if (src.stat == DEAD)
- H.held_death()//We've scooped up an animal that's already dead. use the proper dead icons
- else
- H.isalive = 1//We note that the mob is alive when picked up. If it dies later, we can know that its death happened while held, and play its deathmessage for it
+ spawn(2)
+ var/obj/item/weapon/holder/H = new holder_type(loc)
+ src.loc = H
+ H.name = loc.name
- grabber << "You scoop up [src]."
- src << "[grabber] scoops you up."
- grabber.status_flags |= PASSEMOTES
- return
+ H.contained = src
+
+
+
+ if (src.stat == DEAD)
+ H.held_death()//We've scooped up an animal that's already dead. use the proper dead icons
+ else
+ H.isalive = 1//We note that the mob is alive when picked up. If it dies later, we can know that its death happened while held, and play its deathmessage for it
+
+ grabber << "You scoop up [src]."
+ src << "[grabber] scoops you up."
+ grabber.status_flags |= PASSEMOTES
+
+ H.attack_hand(grabber)//We put this last to prevent some race conditions
+ return
+
+
+/mob/living/proc/get_holder_location()
+ set category = "Abilities"
+ set name = "Check held location"
+ set desc = "Find out where on their person, someone is holding you."
+
+ if (!usr.get_holding_mob())
+ src << "Nobody is holding you!"
+ return
+
+ if (istype(usr.loc, /obj/item/weapon/holder))
+ var/obj/item/weapon/holder/H = usr.loc
+ H.report_onmob_location(0, H.get_equip_slot(), src)
//Mob specific holders.
//w_class mainly determines whether they can fit in trashbags. <=2 can, >=3 cannot
@@ -227,7 +262,6 @@
origin_tech = "biotech=2"
w_class = 1
-
/obj/item/weapon/holder/mouse/white
icon_state = "mouse_white"
icon_state_dead = "mouse_white_dead"
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index cb94fbf498f..1209d81cae8 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -72,7 +72,15 @@
var/current_pda_messaging = null
-/mob/living/silicon/pai/New(var/obj/item/device/paicard)
+/mob/living/silicon/pai/New(var/obj/item/device/paicard/newlocation)
+ var/obj/item/device/paicard/paicard
+ if (istype(newlocation, /obj/item/device/paicard))
+ paicard = newlocation
+ else
+ //If we get here, then we must have been created by adminspawning.
+ //so lets assist with debugging by creating our own card and adding ourself to it
+ paicard = new/obj/item/device/paicard(newlocation)
+ paicard.pai = src
canmove = 0
src.loc = paicard
@@ -333,6 +341,17 @@
verbs -= /mob/living/silicon/pai/proc/choose_chassis
verbs += /mob/living/proc/hide
+/mob/living/silicon/pai/verb/get_onmob_location()
+ set category = "pAI Commands"
+ set name = "Check location"
+ set desc = "Find out where on their person, someone is holding you."
+
+ if (!get_holding_mob())
+ src << "Nobody is holding you!"
+ return
+
+ card.report_onmob_location(0, card.get_equip_slot(), src)
+
/mob/living/silicon/pai/proc/choose_verbs()
set category = "pAI Commands"
set name = "Choose Speech Verbs"
@@ -389,7 +408,8 @@
src.stop_pulling()
src.client.perspective = EYE_PERSPECTIVE
- src.client.eye = card
+ src.client.eye = src
+//Changed the client eye to follow the mob itself instead of the card that contains it. This makes examining work, and the camera still follows wherever the card goes
//stop resting
resting = 0
@@ -417,3 +437,5 @@
// No binary for pAIs.
/mob/living/silicon/pai/binarycheck()
return 0
+
+
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 16d26c05b7a..8888c2934a7 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -94,6 +94,29 @@
else
M.show_message(message, 1, blind_message, 2)
+
+// Designed for mobs contained inside things, where a normal visible message wont actually be visible
+// Useful for visible actions by pAIs, and held mobs
+// Broadcaster is the place the action will be seen/heard from, mobs in sight of THAT will see the message. This is generally the object or mob that src is contained in
+// message is the message output to anyone who can see e.g. "[src] does something!"
+// self_message (optional) is what the src mob sees e.g. "You do something!"
+// blind_message (optional) is what blind people will hear e.g. "You hear something!"
+/mob/proc/contained_visible_message(var/atom/broadcaster, var/message, var/self_message, var/blind_message)
+ var/self_served = 0
+ for(var/mob/M in viewers(broadcaster))
+ if(self_message && M==src)
+ M.show_message(self_message, 1, blind_message, 2)
+ self_served = 1
+ else if(M.see_invisible < invisibility) // Cannot view the invisible, but you can hear it.
+ if(blind_message)
+ M.show_message(blind_message, 2)
+ else
+ M.show_message(message, 1, blind_message, 2)
+
+ if (!self_served)
+ src.show_message(self_message, 1, blind_message, 2)
+
+
// Show a message to all mobs in sight of this atom
// Use for objects performing visible actions
// message is output to anyone who can see, e.g. "The [src] does something!"
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 65ba49ac352..117809af028 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -720,4 +720,218 @@ proc/is_blind(A)
src.visible_message("[src] retches, attempting to vomit!","You gag and collapse as you feel the urge to vomit, but there's nothing in your stomach!")
Weaken(4)
+/obj/proc/get_equip_slot()
+ //This function is called by an object which is somewhere on a humanoid mob
+ //It will return the number of the equipment slot its in
+
+ if (!istype(loc, /mob/living/carbon/human))//This function is for finding where we are on a human. not valid otherwise
+ return null
+
+ var/mob/living/carbon/human/H = loc
+
+
+ //Now we check various slots on the mob, the order of these is optimised based on how likely we are to be in that slot
+ if (H.l_hand == src)
+ return slot_l_hand
+ else if (H.r_hand == src)
+ return slot_r_hand
+ else if (H.l_store == src)
+ return slot_l_store
+ else if (H.r_store == src)
+ return slot_r_store
+ else if (H.head == src)
+ return slot_head
+ else if (H.wear_suit == src)
+ return slot_wear_suit
+ else if (H.s_store == src)
+ return slot_s_store
+ else if (H.wear_mask == src)
+ return slot_wear_mask
+ else if (H.wear_id == src)
+ return slot_wear_id
+ else if (H.w_uniform == src)
+ return slot_w_uniform
+ else if (H.gloves == src)
+ return slot_gloves
+ else if (H.belt == src)
+ return slot_belt
+ else if (H.back == src)
+ return slot_back
+ else if (H.r_ear == src)
+ return slot_r_ear
+ else if (H.l_ear == src)
+ return slot_l_ear
+ else if (H.shoes == src)
+ return slot_shoes
+ else
+ return null//We failed to find the slot
+
+ /* Variables to check
+ l_hand
+ r_hand
+ head
+ l_store //Left and right pockets
+ r_store
+ s_store //Suit storage?
+
+ wear_mask,
+ wear_id
+ w_uniform //the uniform
+ wear_suit
+ gloves
+ belt
+ back
+ r_ear
+ l_ear
+
+ shoes
+
+ */
+
+/obj/proc/report_onmob_location(var/justmoved, var/slot = null, var/mob/reportto)
+ var/mob/living/carbon/human/H//The person who the item is on
+ var/newlocation
+ var/preposition= ""
+ var/action = ""
+ var/action3 = ""
+ if (istype(loc, /mob/living/carbon/human))//This function is for finding where we are on a human. not valid otherwise
+ H = loc
+
+ else
+ H = get_holding_mob()
+
+
+ if (slot != null)
+
+ if (slot_l_hand == slot)
+ if (justmoved)
+ action += "now "
+ preposition = "in"
+ action += "being held"
+ action3 = "holds"
+ newlocation = "left hand"
+ else if (slot_r_hand == slot)
+ if (justmoved)
+ action += "now "
+ preposition = "in"
+ action += "being held"
+ action3 = "holds"
+ newlocation = "right hand"
+ else if (slot_l_store == slot)
+ if (justmoved)
+ preposition = "into"
+ action = "placed"
+ action3 = "places"
+ else
+ preposition = "inside"
+ newlocation = "left pocket"
+ else if (slot_r_store == slot)
+ if (justmoved)
+ preposition = "into"
+ action = "placed"
+ action3 = "places"
+ else
+ preposition = "inside"
+ newlocation = "right pocket"
+ else if (slot_s_store == slot)
+ if (justmoved)
+ preposition = "into"
+ action = "placed"
+ action3 = "places"
+ else
+ preposition = "inside"
+ newlocation = "suit storage"
+ else
+ if (justmoved)
+ action += "now "
+ action += "being worn"
+
+ if (slot_head == slot)
+ preposition = "as"
+ action3 = "wears"
+ newlocation = "hat"
+ else if (slot_wear_suit == slot)
+ preposition = "over"
+ action3 = "wears"
+ newlocation = "uniform"
+ else if (slot_wear_mask == slot)
+ preposition = "on"
+ action3 = "wears"
+ newlocation = "face"
+ else if (slot_wear_id == slot)
+ preposition = "as"
+ action3 = "wears"
+ newlocation = "ID"
+ else if (slot_w_uniform == slot)
+ preposition = "on"
+ action3 = "wears"
+ newlocation = "body"
+ else if (slot_gloves == slot)
+ preposition = "on"
+ action3 = "wears"
+ newlocation = "hands"
+ else if (slot_belt == slot)
+ preposition = "around"
+ action3 = "wears"
+ newlocation = "waist"
+ else if (slot_back == slot)
+ preposition = "on"
+ action3 = "wears"
+ newlocation = "back"
+ else if (slot_r_ear == slot)
+ preposition = "on"
+ action3 = "wears"
+ newlocation = "right shoulder"//Ill use ear slots for wearing mobs on the shoulder in future
+ else if (slot_l_ear == slot)
+ preposition = "on"
+ action3 = "wears"
+ newlocation = "left shoulder"
+ else if (slot_shoes == slot)
+ preposition = "on"
+ action3 = "wears"
+ newlocation = "feet"
+ else if (istype(loc,/obj/item/device/pda))
+ var/obj/item/device/pda/S = loc
+ newlocation = S.name
+ if (justmoved)
+ preposition = "into"
+ action = "slotted"
+ action3 = "slots"
+ else
+ action = "installed"
+ preposition = "in"
+ else if (istype(loc,/obj/item/weapon/storage))
+ var/obj/item/weapon/storage/S = loc
+ newlocation = S.name
+ if (justmoved)
+ preposition = "into"
+ action = "placed"
+ action3 = "places"
+ else
+ action = "tucked"
+ preposition = "inside"
+
+ if (justmoved)
+ reportto.contained_visible_message(H, "[H] [action3] [reportto] [preposition] their [newlocation]", "You are [action] [preposition] [H]'s [newlocation]", "", 1)
+ else
+ reportto << "You are [action] [preposition] [H]'s [newlocation]"
+
+/atom/proc/get_holding_mob()
+ //This function will return the mob which is holding this holder, or null if it's not held
+ //It recurses up the hierarchy out of containers until it reaches a mob, or aturf, or hits the limit
+ var/x = 0//As a safety, we'll crawl up a maximum of five layers
+ var/atom/a = src
+ while (x < 5)
+ x++
+ a = a.loc
+ if (istype(a, /turf))
+ return null//We must be on a table or a floor, or maybe in a wall. Either way we're not held.
+
+ if (istype(a, /mob))
+ return a
+ //If none of the above are true, we must be inside a box or backpack or something. Keep recursing up.
+
+ return null//If we get here, the holder must be buried many layers deep in nested containers. Shouldn't happen
+
+
#undef SAFE_PERP
diff --git a/html/changelogs/Nanako-pAILocation.yml b/html/changelogs/Nanako-pAILocation.yml
new file mode 100644
index 00000000000..e681d6b42be
--- /dev/null
+++ b/html/changelogs/Nanako-pAILocation.yml
@@ -0,0 +1,39 @@
+################################
+# Example Changelog File
+#
+# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
+#
+# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
+# When it is, any changes listed below will disappear.
+#
+# Valid Prefixes:
+# bugfix
+# wip (For works in progress)
+# tweak
+# soundadd
+# sounddel
+# rscadd (general adding of nice things)
+# rscdel (general deleting of nice things)
+# imageadd
+# imagedel
+# maptweak
+# spellcheck (typo fixes)
+# experiment
+#################################
+
+# Your name.
+author: Nanako
+
+# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
+delete-after: True
+
+# Any changes you've made. See valid prefix list above.
+# INDENT WITH TWO SPACES. NOT TABS. SPACES.
+# SCREW THIS UP AND IT WON'T WORK.
+# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
+# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
+changes:
+ - bugfix: "Fixed a bug where placing held mobs into containers would make them vanish"
+ - tweak: "pAIs can now examine objects while in card form"
+ - rscadd: "Moving pAIs and held mobs around on your person is now a visible action, and the mob or pAI is notified of where its moved to"
+ - rscadd: "Added a verb for pAIs and held mobs, to check where on the holder they are."