From dd631d4806441d884ce35289d557ef17d6ef019a Mon Sep 17 00:00:00 2001 From: DZD Date: Tue, 2 Dec 2014 18:00:24 -0500 Subject: [PATCH] Migrating Bay pAI Drone Form Port --- code/__HELPERS/unsorted.dm | 7 +- code/datums/browser.dm | 8 +- code/game/gamemodes/events/ninja_equipment.dm | 21 +- code/game/machinery/doors/windowdoor.dm | 6 +- code/game/objects/items/devices/PDA/PDA.dm | 20 +- code/game/verbs/suicide.dm | 2 + code/modules/mob/living/silicon/pai/death.dm | 9 +- code/modules/mob/living/silicon/pai/life.dm | 2 + code/modules/mob/living/silicon/pai/pai.dm | 230 +++++++++++++++++- .../mob/living/silicon/pai/software.dm | 27 +- .../modules/mob/living/silicon/robot/robot.dm | 19 +- code/modules/mob/living/silicon/say.dm | 10 +- code/modules/mob/living/silicon/silicon.dm | 19 ++ code/modules/mob/mob.dm | 3 +- icons/mob/pai.dmi | Bin 0 -> 9739 bytes 15 files changed, 315 insertions(+), 68 deletions(-) create mode 100644 icons/mob/pai.dmi diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index e641d8c773f..fa4247b0db6 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -331,8 +331,8 @@ Turf and target are seperate in case you want to teleport some distance from a t if(oldname != real_name) R.notify_ai(3, oldname, newname) if(R.camera) - R.camera.c_tag = real_name - + R.camera.c_tag = real_name + if(oldname) //update the datacore records! This is goig to be a bit costly. for(var/list/L in list(data_core.general,data_core.medical,data_core.security,data_core.locked)) @@ -639,6 +639,7 @@ var/list/sortMobsOrder = list( "/mob/living/silicon/ai", // if a valid atom reference is supplied, call the atom's Topic() with "close=1" // otherwise, just reset the client mob's machine var. // +/* /client/verb/windowclose(var/atomref as text) set hidden = 1 // hide this verb from the user's panel set name = ".windowclose" // no autocomplete on cmd line @@ -658,7 +659,7 @@ var/list/sortMobsOrder = list( "/mob/living/silicon/ai", if(src && src.mob) //world << "[src] was [src.mob.machine], setting to null" src.mob.unset_machine() - return + return */ //Will return the location of the turf an atom is ultimatly sitting on /proc/get_turf_loc(var/atom/movable/M) //gets the location of the turf that the atom is on, or what the atom is in is on, etc diff --git a/code/datums/browser.dm b/code/datums/browser.dm index da5a80443ba..01a287a46db 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -152,7 +152,7 @@ winset(user, windowid, "on-close=\".windowclose [param]\"") - //world << "OnClose [user]: [windowid] : ["on-close=\".windowclose [param]\""]" + world << "OnClose [user]: [windowid] : ["on-close=\".windowclose [param]\""]" // the on-close client verb @@ -160,11 +160,11 @@ // if a valid atom reference is supplied, call the atom's Topic() with "close=1" // otherwise, just reset the client mob's machine var. // -/client/verb/windowclosed(var/atomref as text) +/client/verb/windowclose(var/atomref as text) set hidden = 1 // hide this verb from the user's panel set name = ".windowclose" // no autocomplete on cmd line - //world << "windowclose: [atomref]" + world << "windowclose: [atomref]" if(atomref!="null") // if passed a real atomref var/hsrc = locate(atomref) // find the reffed atom if(hsrc) @@ -176,6 +176,6 @@ // no atomref specified (or not found) // so just reset the user mob's machine var if(src && src.mob) - //world << "[src] was [src.mob.machine], setting to null" + world << "[src] was [src.mob.machine], setting to null" src.mob.unset_machine() return \ No newline at end of file diff --git a/code/game/gamemodes/events/ninja_equipment.dm b/code/game/gamemodes/events/ninja_equipment.dm index ae6d6892050..d17e839fb37 100644 --- a/code/game/gamemodes/events/ninja_equipment.dm +++ b/code/game/gamemodes/events/ninja_equipment.dm @@ -682,17 +682,24 @@ ________________________________________________________________________________ pai.attack_self(U) if("Eject pAI") - var/turf/T = get_turf(loc) - if(!U.get_active_hand()) - U.put_in_hands(pai) - pai.add_fingerprint(U) + if("Eject pAI") + if(pai) pai = null else - if(T) - pai.loc = T + if(pai.loc != src) pai = null else - U << "\red ERROR: \black Could not eject pAI card." + var/turf/T = get_turf(loc) + if(!U.get_active_hand()) + U.put_in_hands(pai) + pai.add_fingerprint(U) + pai = null + else + if(T) + pai.loc = T + pai = null + else + U << "\red ERROR: \black Could not eject pAI card." if("Override AI Laws") var/law_zero = A.laws.zeroth//Remembers law zero, if there is one. diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index d86e7d96b0a..592cfdc6ef9 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -17,7 +17,7 @@ if (src.req_access && src.req_access.len) src.icon_state = "[src.icon_state]" src.base_state = src.icon_state - + color = color_windows(src) return @@ -56,7 +56,7 @@ if (!( ticker )) return var/mob/M = AM - if(!M.restrained()) + if(!M.restrained() && !M.small) bumpopen(M) return @@ -187,7 +187,7 @@ visible_message("[src] has been hit by [M.name].") take_damage(M.force) return - + /obj/machinery/door/window/attack_ai(mob/user as mob) return src.attack_hand(user) diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index fb960fa6021..415f80e5cff 100755 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -768,14 +768,18 @@ var/global/list/obj/item/device/pda/PDAs = list() //pAI FUNCTIONS=================================== if("pai") - switch(href_list["option"]) - if("1") // Configure pAI device - pai.attack_self(U) - if("2") // Eject pAI device - var/turf/T = get_turf_or_move(src.loc) - if(T) - pai.loc = T - pai = null + if(pai) + if(pai.loc != src) + pai = null + else + switch(href_list["option"]) + if("1") // Configure pAI device + pai.attack_self(U) + if("2") // Eject pAI device + var/turf/T = get_turf_or_move(src.loc) + if(T) + pai.loc = T + pai = null else mode = text2num(href_list["choice"]) diff --git a/code/game/verbs/suicide.dm b/code/game/verbs/suicide.dm index 7f963bb4e1c..0b351b7619a 100644 --- a/code/game/verbs/suicide.dm +++ b/code/game/verbs/suicide.dm @@ -170,6 +170,8 @@ set name = "pAI Suicide" var/answer = input("REALLY kill yourself? This action can't be undone.", "Suicide", "No") in list ("Yes", "No") if(answer == "Yes") + if(canmove && !resting) + close_up() var/obj/item/device/paicard/card = loc card.removePersonality() var/turf/T = get_turf_or_move(card.loc) diff --git a/code/modules/mob/living/silicon/pai/death.dm b/code/modules/mob/living/silicon/pai/death.dm index 127091a854c..b945315ac2b 100644 --- a/code/modules/mob/living/silicon/pai/death.dm +++ b/code/modules/mob/living/silicon/pai/death.dm @@ -1,5 +1,13 @@ /mob/living/silicon/pai/death(gibbed) if(stat == DEAD) return + var/turf/T = get_turf_or_move(loc) + for (var/mob/M in viewers(T)) + M.show_message("\red A shower of sparks spray from [src]'s inner workings.", 3, "\red You hear and smell the ozone hiss of electrical sparks being expelled violently.", 2) + var/obj/effect/decal/cleanable/deadpai = new /obj/effect/decal/cleanable/robot_debris(loc) + if(canmove || resting) + deadpai.icon = 'icons/mob/pai.dmi' + deadpai.icon_state = "[chassis]_dead" + del(src) stat = DEAD canmove = 0 if(blind) blind.layer = 0 @@ -15,4 +23,3 @@ if(mind) del(mind) living_mob_list -= src ghostize() - del(src) \ No newline at end of file diff --git a/code/modules/mob/living/silicon/pai/life.dm b/code/modules/mob/living/silicon/pai/life.dm index cebdf2a5359..0054b32f57f 100644 --- a/code/modules/mob/living/silicon/pai/life.dm +++ b/code/modules/mob/living/silicon/pai/life.dm @@ -25,4 +25,6 @@ stat = CONSCIOUS else health = 100 - getBruteLoss() - getFireLoss() + if(health <= 0) + death(0) diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 02c8ea04223..ab04798293b 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -1,10 +1,13 @@ /mob/living/silicon/pai name = "pAI" - icon = 'icons/mob/mob.dmi'// - icon_state = "shadow" + icon = 'icons/mob/pai.dmi'// + icon_state = "repairbot" robot_talk_understand = 0 emote_type = 2 // pAIs emotes are heard, not seen, so they can be seen through a container (eg. person) + small = 1 + pass_flags = 1 + density = 0 var/network = "SS13" var/obj/machinery/camera/current = null @@ -15,9 +18,22 @@ var/obj/item/device/paicard/card // The card we inhabit var/obj/item/device/radio/radio // Our primary radio - var/speakStatement = "states" - var/speakExclamation = "declares" - var/speakQuery = "queries" + var/chassis = "repairbot" // A record of your chosen chassis. + var/global/list/possible_chassis = list( + "Drone" = "repairbot", + "Cat" = "cat", + "Mouse" = "mouse", + "Monkey" = "monkey" + ) + + var/global/list/possible_say_verbs = list( + "Robotic" = list("states","declares","queries"), + "Natural" = list("says","yells","asks"), + "Beep" = list("beeps","beeps loudly","boops"), + "Chirp" = list("chirps","chirrups","cheeps"), + "Feline" = list("purrs","yowls","meows") + ) + var/obj/item/weapon/pai_cable/cable // The cable we produce and use when door or camera jacking @@ -63,6 +79,10 @@ card.radio = new /obj/item/device/radio(src.card) radio = card.radio + //Verbs for pAI mobile form, chassis and Say flavor text + verbs += /mob/living/silicon/pai/proc/choose_chassis + verbs += /mob/living/silicon/pai/proc/choose_verbs + //PDA pda = new(src) spawn(5) @@ -76,20 +96,20 @@ ..() usr << browse_rsc('html/paigrid.png') // Go ahead and cache the interface resources as early as possible - + // this function shows the information about being silenced as a pAI in the Status panel /mob/living/silicon/pai/proc/show_silenced() if(src.silence_time) var/timeleft = round((silence_time - world.timeofday)/10 ,1) stat(null, "Communications system reboot in -[(timeleft / 60) % 60]:[add_zero(num2text(timeleft % 60), 2)]") - - + + /mob/living/silicon/pai/Stat() ..() statpanel("Status") if (src.client.statpanel == "Status") show_silenced() - + if (proc_holder_list.len)//Generic list for proc_holder objects. for(var/obj/effect/proc_holder/P in proc_holder_list) statpanel("[P.panel]","",P) @@ -108,7 +128,12 @@ return 0 /mob/living/silicon/pai/restrained() - return 0 + if(istype(src.loc,/obj/item/device/paicard)) + return 0 + ..() + +/mob/living/silicon/pai/MouseDrop(atom/over_object) + return /mob/living/silicon/pai/emp_act(severity) // Silence for 2 minutes @@ -270,3 +295,188 @@ card.setPersonality(pai) */ + +// Procs/code after this point is used to convert the stationary pai item into a +// mobile pai mob. This also includes handling some of the general shit that can occur +// to it. Really this deserves its own file, but for the moment it can sit here. ~ Z + +/mob/living/silicon/pai/verb/fold_out() + set category = "pAI Commands" + set name = "Unfold Chassis" + + if(stat || sleeping || paralysis || weakened) + return + + if(src.loc != card) + src << "\red You are already in your mobile form!" + return + + if(world.time <= last_special) + src << "\red You must wait before folding your chassis out again!" + return + + last_special = world.time + 200 + + canmove = 1 + + //I'm not sure how much of this is necessary, but I would rather avoid issues. + if(istype(card.loc,/mob)) + var/mob/holder = card.loc + holder.drop_from_inventory(card) + else if(istype(card.loc,/obj/item/clothing/suit/space/space_ninja)) + var/obj/item/clothing/suit/space/space_ninja/holder = card.loc + holder.pai = null + else if(istype(card.loc,/obj/item/device/pda)) + var/obj/item/device/pda/holder = card.loc + holder.pai = null + + + src.client.perspective = EYE_PERSPECTIVE + src.client.eye = src + src.forceMove(get_turf(card)) + + card.forceMove(src) + card.screen_loc = null + + var/turf/T = get_turf(src) + if(istype(T)) T.visible_message("[src] folds outwards, expanding into a mobile form.") + +/mob/living/silicon/pai/verb/fold_up() + set category = "pAI Commands" + set name = "Collapse Chassis" + + if(stat || sleeping || paralysis || weakened) + return + + if(src.loc == card) + src << "\red You are already in your card form!" + return + + if(world.time <= last_special) + src << "\red You must wait before returning to your card form!" + return + + close_up() + +/mob/living/silicon/pai/proc/choose_chassis() + set category = "pAI Commands" + set name = "Choose Chassis" + + var/choice + var/finalized = "No" + while(finalized == "No" && src.client) + + choice = input(usr,"What would you like to use for your mobile chassis icon? This decision can only be made once.") as null|anything in possible_chassis + if(!choice) return + + icon_state = possible_chassis[choice] + finalized = alert("Look at your sprite. Is this what you wish to use?",,"No","Yes") + + chassis = possible_chassis[choice] + verbs -= /mob/living/silicon/pai/proc/choose_chassis + +/mob/living/silicon/pai/proc/choose_verbs() + set category = "pAI Commands" + set name = "Choose Speech Verbs" + + var/choice = input(usr,"What theme would you like to use for your speech verbs? This decision can only be made once.") as null|anything in possible_say_verbs + if(!choice) return + + var/list/sayverbs = possible_say_verbs[choice] + speak_statement = sayverbs[1] + speak_exclamation = sayverbs[(sayverbs.len>1 ? 2 : sayverbs.len)] + speak_query = sayverbs[(sayverbs.len>2 ? 3 : sayverbs.len)] + + verbs -= /mob/living/silicon/pai/proc/choose_verbs + +/mob/living/silicon/pai/lay_down() + set name = "Rest" + set category = "IC" + + if(istype(src.loc,/obj/item/device/paicard)) + resting = 0 + else + resting = !resting + icon_state = resting ? "[chassis]_rest" : "[chassis]" + src << "\blue You are now [resting ? "resting" : "getting up"]" + + canmove = !resting + +//Overriding this will stop a number of headaches down the track. +/mob/living/silicon/pai/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(W.force) + visible_message("[user.name] attacks [src] with [W]!") + src.adjustBruteLoss(W.force) + src.updatehealth() + else + visible_message("[user.name] bonks [src] harmlessly with [W].") + if(stat != 2) close_up() + return + +/mob/living/silicon/pai/attack_hand(mob/user as mob) + if(stat == 2) return + visible_message("[user.name] boops [src] on the head.") + close_up() + +//I'm not sure how much of this is necessary, but I would rather avoid issues. +/mob/living/silicon/pai/proc/close_up() + + last_special = world.time + 200 + + if(src.loc == card) + return + + var/turf/T = get_turf(src) + if(istype(T)) T.visible_message("[src] neatly folds inwards, compacting down to a rectangular card.") + + src.stop_pulling() + src.client.perspective = EYE_PERSPECTIVE + src.client.eye = card + + //This seems redundant but not including the forced loc setting messes the behavior up. + src.loc = card + card.loc = get_turf(card) + src.forceMove(card) + card.forceMove(card.loc) + canmove = 0 + icon_state = "[chassis]" + +/mob/living/silicon/pai/start_pulling(var/atom/movable/AM) + + if(istype(AM,/obj/item)) + src << "You are far too small to pull anything!" + +/mob/living/silicon/pai/examine() + + set src in oview() + + if(!usr || !src) return + if( (usr.sdisabilities & BLIND || usr.blinded || usr.stat) && !istype(usr,/mob/dead/observer) ) + usr << "Something is there but you can't see it." + return + + var/msg = "*---------*\nThis is \icon[src][name], a personal AI!" + + switch(src.stat) + if(CONSCIOUS) + if(!src.client) msg += "\nIt appears to be in stand-by mode." //afk + if(UNCONSCIOUS) msg += "\nIt doesn't seem to be responding." + if(DEAD) msg += "\nIt looks completely unsalvageable." + msg += "\n*---------*" + + if(print_flavor_text()) msg += "\n[print_flavor_text()]\n" + + if (pose) + if( findtext(pose,".",lentext(pose)) == 0 && findtext(pose,"!",lentext(pose)) == 0 && findtext(pose,"?",lentext(pose)) == 0 ) + pose = addtext(pose,".") //Makes sure all emotes end with a period. + msg += "\nIt is [pose]" + + usr << msg + +/mob/living/silicon/pai/bullet_act(var/obj/item/projectile/Proj) + ..(Proj) + updatehealth() + if (stat != 2) + close_up() + return 2 + diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm index 6cf3bf2761f..4f7bd1a1c61 100644 --- a/code/modules/mob/living/silicon/pai/software.dm +++ b/code/modules/mob/living/silicon/pai/software.dm @@ -122,6 +122,8 @@ return var/soft = href_list["software"] var/sub = href_list["sub"] + if(!soft && !sub) + return if(soft) src.screen = soft if(sub) @@ -528,14 +530,21 @@ dat += {"

Medical Analysis Suite


Host Bioscan

"} - var/mob/living/M = src.loc - if(!istype(M, /mob/living)) - while (!istype(M, /mob/living)) - M = M.loc - if(istype(M, /turf)) - src.temp = "Error: No biological host found.
" - src.subscreen = 0 - return dat + var/mob/living/M + //If we are not deployed, check the holder of the card. + if(src.loc == card) + M = card.loc + + //If we are deployed or the card is not held, check the first living mob in our turf. + if(!M || !istype(M)) + var/turf/T = get_turf(src) + M = locate(/mob/living/) in T.contents + + if(!M || !istype(M)) + src.temp = "Error: No biological host found.
" + src.subscreen = 0 + return dat + dat += {"Bioscan Results for [M]:
Overall Status: [M.stat > 1 ? "dead" : "[M.health]% healthy"]

@@ -664,7 +673,7 @@ dat += "
    " if(!pda.toff) for (var/obj/item/device/pda/P in sortAtom(PDAs)) - if (!P.owner||P.toff||P == src.pda) continue + if (!P.owner||P.toff||P == src.pda||P.hidden) continue dat += "
  • [P]" dat += "
  • " dat += "
" diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 41eef6dddf1..03d56e895ea 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -69,7 +69,6 @@ var/speed = 0 //Cause sec borgs gotta go fast //No they dont! var/scrambledcodes = 0 // Used to determine if a borg shows up on the robotics console. Setting to one hides them. var/braintype = "Cyborg" - var/pose var/base_icon = "" var/crisis = 0 var/hiddenborg = 0 @@ -1355,20 +1354,6 @@ lockcharge = state update_canmove() -/mob/living/silicon/robot/verb/pose() - set name = "Set Pose" - set desc = "Sets a description which will be shown when someone examines you." - set category = "IC" - - pose = copytext(sanitize(input(usr, "This is [src]. It is...", "Pose", null) as text), 1, MAX_MESSAGE_LEN) - -/mob/living/silicon/robot/verb/set_flavor() - set name = "Set Flavour Text" - set desc = "Sets an extended description of your character's features." - set category = "IC" - - flavor_text = copytext(sanitize(input(usr, "Please enter your new flavour text.", "Flavour text", null) as text), 1) - /mob/living/silicon/robot/proc/choose_icon(var/triesleft, var/list/module_sprites) if(triesleft<1 || !module_sprites.len) @@ -1425,7 +1410,7 @@ radio = new /obj/item/device/radio/borg/deathsquad(src) module = new /obj/item/weapon/robot_module/deathsquad(src) laws = new /datum/ai_laws/deathsquad() - + Namepick() /mob/living/silicon/robot/deathsquad/attack_hand(mob/user) @@ -1436,7 +1421,7 @@ ghosts += G get_borg_occupant(user, ghosts) return - + /mob/living/silicon/robot/deathsquad/proc/get_borg_occupant(mob/user as mob, var/list/possiblecandidates = list()) var/time_passed = world.time searching_for_ckey = 1 diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm index 04d55d5bd9b..ef2b067835b 100644 --- a/code/modules/mob/living/silicon/say.dm +++ b/code/modules/mob/living/silicon/say.dm @@ -2,11 +2,11 @@ var/ending = copytext(text, length(text)) if (ending == "?") - return "queries, \"[text]\""; + return "[speak_query], \"[text]\""; else if (ending == "!") - return "declares, \"[text]\""; + return "[speak_exclamation], \"[text]\""; - return "states, \"[text]\""; + return "[speak_statement], \"[text]\""; /mob/living/silicon/say(var/message) if (!message) @@ -115,12 +115,12 @@ if(isrobot(src)) var/mob/living/silicon/S = src if(S.designation) - desig = trim_left(S.designation) + desig = trim_left(S.designation) else desig = "Default" else if(isAI(src)) desig = "AI" - + message = trim(message) if (!message) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index d2e067f8cc8..bf980fe365e 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -12,6 +12,11 @@ var/list/alarm_types_clear = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0) var/designation = "" var/obj/item/device/camera/siliconcam/aiCamera = null //photography +//Used in say.dm, allows for pAIs to have different say flavor text, as well as silicons, although the latter is not implemented. + var/speak_statement = "states" + var/speak_exclamation = "declares" + var/speak_query = "queries" + var/pose //Yes, now AIs can pose too. /mob/living/silicon/proc/cancelAlarm() return @@ -235,3 +240,17 @@ /mob/living/silicon/assess_threat() //Secbots won't hunt silicon units return -10 + +/mob/living/silicon/verb/pose() + set name = "Set Pose" + set desc = "Sets a description which will be shown when someone examines you." + set category = "IC" + + pose = copytext(sanitize(input(usr, "This is [src]. It is...", "Pose", null) as text), 1, MAX_MESSAGE_LEN) + +/mob/living/silicon/verb/set_flavor() + set name = "Set Flavour Text" + set desc = "Sets an extended description of your character's features." + set category = "IC" + + flavor_text = copytext(sanitize(input(usr, "Please enter your new flavour text.", "Flavour text", null) as text), 1) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 7842bc402f6..a78d7995122 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -761,6 +761,7 @@ var/list/slot_equipment_priority = list( \ /mob/MouseDrop(mob/M as mob) ..() if(M != usr) return + if(M.small) return // Stops pAI drones and small mobs (borers, parrots, crabs) from stripping people. --DZD if(usr == src) return if(!Adjacent(usr)) return if(istype(M,/mob/living/silicon/ai)) return @@ -1309,6 +1310,6 @@ mob/proc/yank_out_object() if(paralysis) AdjustParalysis(-1) return paralysis - + /mob/proc/assess_threat() //For sec bot threat assessment return \ No newline at end of file diff --git a/icons/mob/pai.dmi b/icons/mob/pai.dmi new file mode 100644 index 0000000000000000000000000000000000000000..f286389bffc1f05454d7370cb0c2f7a04b5ff831 GIT binary patch literal 9739 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