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."