diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index ae84f5b1dfe..57a0746ed28 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -13,4 +13,5 @@ var/global/list/mob_list = list() //all mobs, including clientless var/global/list/living_mob_list = list() //all alive mobs, including clientless. Excludes /mob/new_player var/global/list/dead_mob_list = list() //all dead mobs, including clientless. Excludes /mob/new_player var/global/list/joined_player_list = list() //all clients that have joined the game at round-start or as a latejoin. -var/global/list/silicon_mobs = list() //all silicon mobs \ No newline at end of file +var/global/list/silicon_mobs = list() //all silicon mobs +var/global/list/pai_list = list() \ No newline at end of file diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm index afe2a833a7f..1f004f31ac3 100644 --- a/code/_onclick/hud/_defines.dm +++ b/code/_onclick/hud/_defines.dm @@ -143,10 +143,11 @@ //Ghosts -#define ui_ghost_jumptomob "SOUTH:6,CENTER-2:16" -#define ui_ghost_orbit "SOUTH:6,CENTER-1:16" -#define ui_ghost_reenter_corpse "SOUTH:6,CENTER:16" -#define ui_ghost_teleport "SOUTH:6,CENTER+1:16" +#define ui_ghost_jumptomob "SOUTH:6,CENTER-2:24" +#define ui_ghost_orbit "SOUTH:6,CENTER-1:24" +#define ui_ghost_reenter_corpse "SOUTH:6,CENTER:24" +#define ui_ghost_teleport "SOUTH:6,CENTER+1:24" +#define ui_ghost_pai "SOUTH: 6, CENTER+2:24" //Hand of God, god diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm index 4c488060761..544415f2db4 100644 --- a/code/_onclick/hud/ghost.dm +++ b/code/_onclick/hud/ghost.dm @@ -36,6 +36,14 @@ var/mob/dead/observer/G = usr G.dead_tele() +/obj/screen/ghost/pai + name = "pAI Candidate" + icon_state = "pai" + +/obj/screen/ghost/pai/Click() + var/mob/dead/observer/G = usr + G.register_pai() + /datum/hud/ghost/New(mob/owner, ui_style = 'icons/mob/screen_midnight.dmi') ..() var/mob/dead/observer/G = mymob @@ -61,6 +69,10 @@ using.screen_loc = ui_ghost_teleport static_inventory += using + using = new /obj/screen/ghost/pai() + using.screen_loc = ui_ghost_pai + static_inventory += using + /datum/hud/ghost/show_hud() var/mob/dead/observer/G = mymob diff --git a/code/controllers/subsystem/pai.dm b/code/controllers/subsystem/pai.dm index f3dcb49e6c3..e83ae273d52 100644 --- a/code/controllers/subsystem/pai.dm +++ b/code/controllers/subsystem/pai.dm @@ -2,8 +2,8 @@ var/datum/subsystem/pai/SSpai /datum/subsystem/pai name = "pAI" - init_order = 20 - flags = SS_NO_FIRE|SS_NO_INIT + + flags = SS_NO_INIT|SS_NO_FIRE var/list/candidates = list() @@ -132,7 +132,6 @@ var/datum/subsystem/pai/SSpai M << browse(dat, "window=paiRecruit") /datum/subsystem/pai/proc/findPAI(obj/item/device/paicard/p, mob/user) - requestRecruits() var/list/available = list() for(var/datum/paiCandidate/c in SSpai.candidates) if(c.ready) @@ -177,12 +176,6 @@ var/datum/subsystem/pai/SSpai user << browse(dat, "window=findPai") -/datum/subsystem/pai/proc/requestRecruits() - var/list/candidates = pollCandidates("Someone is requesting a pAI personality. Would you like to play as a personal AI?", ROLE_PAI, null, ROLE_PAI,300, ignore_category = POLL_IGNORE_PAI) - for(var/V in candidates) - recruitWindow(V) - return - /datum/paiCandidate var/name var/key diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index b7b47eb8ba1..3af60f629f3 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -153,14 +153,6 @@ else //something went very wrong. CRASH("Brainmob without container.") - -/mob/living/silicon/pai/forceMove(atom/destination) - if(card) - return card.forceMove(destination) - else //something went very wrong. - CRASH("pAI without card") - - //Called whenever an object moves and by mobs when they attempt to move themselves through space //And when an object or action applies a force on src, see newtonian_move() below //Return 0 to have src start/keep drifting in a no-grav area and 1 to stop/not start drifting diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm index 5ef33f41d4d..1f16796bd77 100644 --- a/code/game/objects/items/devices/paicard.dm +++ b/code/game/objects/items/devices/paicard.dm @@ -6,9 +6,9 @@ w_class = WEIGHT_CLASS_SMALL slot_flags = SLOT_BELT origin_tech = "programming=2" - var/obj/item/device/radio/radio var/looking_for_personality = 0 var/mob/living/silicon/pai/pai + resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE /obj/item/device/paicard/New() ..() @@ -35,22 +35,19 @@ dat += "Configure Directives
" dat += "
" dat += "

Device Settings


" - if(radio) + if(pai.radio) dat += "Radio Uplink
" - dat += "Transmit: [(radio.wires.is_cut(WIRE_TX)) ? "Disabled" : "Enabled"]
" - dat += "Receive: [(radio.wires.is_cut(WIRE_RX)) ? "Disabled" : "Enabled"]
" + dat += "Transmit: [(pai.radio.wires.is_cut(WIRE_TX)) ? "Disabled" : "Enabled"]
" + dat += "Receive: [(pai.radio.wires.is_cut(WIRE_RX)) ? "Disabled" : "Enabled"]
" else dat += "Radio Uplink
" dat += "Radio firmware not loaded. Please install a pAI personality to load firmware.
" dat += "\[Wipe current pAI personality\]
" else if(looking_for_personality) + dat += "No personality installed.
" dat += "Searching for a personality..." dat += "\[View available personalities\]
" - else - dat += "No personality is installed.
" - dat += "\[Request personal AI personality\]
" - dat += "Each time this button is pressed, a request will be sent out to any available personalities. Check back often and give a lot of time for personalities to respond. This process could take anywhere from 15 seconds to several minutes, depending on the available personalities' timeliness." user << browse(dat, "window=paicard") onclose(user, "paicard") return @@ -86,8 +83,8 @@ pai.death(0) if(href_list["wires"]) var/wire = text2num(href_list["wires"]) - if(radio) - radio.wires.cut(wire) + if(pai.radio) + pai.radio.wires.cut(wire) if(href_list["setlaws"]) var/newlaws = copytext(sanitize(input("Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.laws.supplied[1]) as message),1,MAX_MESSAGE_LEN) if(newlaws && pai) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 83708c051b7..74d0d307600 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -735,4 +735,18 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp LAZYINITLIST(mob_eye.observers) mob_eye.observers |= src mob_eye.hud_used.show_hud(1,src) - observetarget = mob_eye \ No newline at end of file + observetarget = mob_eye + +/mob/dead/observer/verb/register_pai_candidate() + set category = "Ghost" + set name = "pAI Setup" + set desc = "Upload a fragment of your personality to the global pAI databanks" + + register_pai() + +/mob/dead/observer/proc/register_pai() + if(istype(src, /mob/dead/observer)) + if(SSpai) + SSpai.recruitWindow(src) + else + usr << "Can't become a pAI candidate while not dead!" diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 018c05e9800..c4257c54725 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -1,14 +1,19 @@ /mob/living/silicon/pai name = "pAI" - icon = 'icons/obj/status_display.dmi' //invisibility! - mouse_opacity = 0 - density = 0 - mob_size = MOB_SIZE_TINY - var/network = "SS13" var/obj/machinery/camera/current = null - + icon = 'icons/mob/pai.dmi' + icon_state = "repairbot" + mouse_opacity = 1 + density = 0 + ventcrawler = 2 + luminosity = 0 + pass_flags = PASSTABLE | PASSMOB + mob_size = MOB_SIZE_TINY + desc = "A generic pAI mobile hard-light holographics emitter. It seems to be deactivated." weather_immunities = list("ash") + health = 500 + maxHealth = 500 var/ram = 100 // Used as currency to purchase different abilities var/list/software = list() @@ -49,8 +54,31 @@ var/obj/item/radio/integrated/signal/sradio // AI's signaller + var/holoform = FALSE + var/canholo = TRUE + var/obj/item/weapon/card/id/access_card = null + var/chassis = "repairbot" + var/list/possible_chassis = list("cat", "mouse", "monkey", "corgi", "fox", "repairbot", "rabbit") + + var/emitterhealth = 50 + var/emittermaxhealth = 50 + var/emitterregen = 1 + var/emittercd = 10 + var/emitteroverloadcd = 50 + var/emittersemicd = FALSE + + var/overload_ventcrawl = 0 + var/overload_bulletblock = 0 //Why is this a good idea? + var/overload_maxhealth = 0 + canmove = FALSE + +/mob/living/silicon/pai/Destroy() + pai_list -= src + ..() /mob/living/silicon/pai/New(var/obj/item/device/paicard/P) + START_PROCESSING(SSfastprocess, src) + pai_list += src make_laws() canmove = 0 if(!istype(P)) //when manually spawning a pai, we create a card to put it into. @@ -60,53 +88,104 @@ loc = P card = P sradio = new(src) - if(card) - if(!card.radio) - card.radio = new /obj/item/device/radio(card) - radio = card.radio + if(!radio) + radio = new /obj/item/device/radio(src) //PDA pda = new(src) spawn(5) - pda.ownjob = "Personal Assistant" + pda.ownjob = "pAI Messenger" pda.owner = text("[]", src) pda.name = pda.owner + " (" + pda.ownjob + ")" ..() + var/datum/action/innate/pai/shell/AS = new /datum/action/innate/pai/shell + var/datum/action/innate/pai/chassis/AC = new /datum/action/innate/pai/chassis + var/datum/action/innate/pai/rest/AR = new /datum/action/innate/pai/rest + AS.Grant(src) + AC.Grant(src) + AR.Grant(src) + /mob/living/silicon/pai/make_laws() laws = new /datum/ai_laws/pai() - return 1 + return TRUE /mob/living/silicon/pai/Login() ..() usr << browse_rsc('html/paigrid.png') // Go ahead and cache the interface resources as early as possible - + if(client) + client.perspective = EYE_PERSPECTIVE + if(holoform) + client.eye = src + else + client.eye = card /mob/living/silicon/pai/Stat() ..() if(statpanel("Status")) - 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)]") - if(!src.stat) - stat(null, text("System integrity: [(src.health+100)/2]%")) + if(!stat) + stat(null, text("Emitter Integrity: [emitterhealth * (100/emittermaxhealth)]")) else stat(null, text("Systems nonfunctional")) /mob/living/silicon/pai/restrained(ignore_grab) - . = 0 + . = FALSE // See software.dm for Topic() /mob/living/silicon/pai/canUseTopic(atom/movable/M) - return 1 -/* -// Debug command - Maybe should be added to admin verbs later -/mob/verb/makePAI(var/turf/t in view()) - var/obj/item/device/paicard/card = new(t) - var/mob/living/silicon/pai/pai = new(card) - pai.key = src.key - card.setPersonality(pai) + return TRUE -*/ +/mob/living/silicon/pai/process() + emitterhealth = Clamp((emitterhealth + emitterregen), -50, emittermaxhealth) + if(weakened > 0) + weakened -= 0.2 + +/mob/proc/makePAI(delold) + var/obj/item/device/paicard/card = new /obj/item/device/paicard(get_turf(src)) + var/mob/living/silicon/pai/pai = new /mob/living/silicon/pai(card) + pai.key = key + pai.name = name + card.setPersonality(pai) + if(delold) + qdel(src) + +/datum/action/innate/pai + name = "PAI Action" + var/mob/living/silicon/pai/P + +/datum/action/innate/pai/Trigger() + if(!ispAI(owner)) + return 0 + P = owner + +/datum/action/innate/pai/shell + name = "Toggle Holoform" + button_icon_state = "pai_holoform" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/shell/Trigger() + ..() + if(P.holoform) + P.fold_in(0) + else + P.fold_out() + +/datum/action/innate/pai/chassis + name = "Holochassis Appearence Composite" + button_icon_state = "pai_chassis" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/chassis/Trigger() + ..() + P.choose_chassis() + +/datum/action/innate/pai/rest + name = "Rest" + button_icon_state = "pai_rest" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/rest/Trigger() + ..() + P.lay_down() diff --git a/code/modules/mob/living/silicon/pai/pai_defense.dm b/code/modules/mob/living/silicon/pai/pai_defense.dm index 63714a75358..54943dd7303 100644 --- a/code/modules/mob/living/silicon/pai/pai_defense.dm +++ b/code/modules/mob/living/silicon/pai/pai_defense.dm @@ -1,48 +1,111 @@ /mob/living/silicon/pai/blob_act(obj/structure/blob/B) - return 0 + return FALSE /mob/living/silicon/pai/emp_act(severity) - // 20% chance to kill - // Silence for 2 minutes - // 33% chance to unbind - // 33% chance to change prime directive (based on severity) - // 33% chance of no additional effect - - if(prob(20)) - visible_message("A shower of sparks spray from [src]'s inner workings.", 3, "You hear and smell the ozone hiss of electrical sparks being expelled violently.", 2) - return src.death(0) - - silence_time = world.timeofday + 120 * 10 // Silence for 2 minutes - src << "Communication circuit overload. Shutting down and reloading communication circuits - speech and messaging functionality will be unavailable until the reboot is complete." - - switch(pick(1,2,3)) - if(1) - src.master = null - src.master_dna = null - src << "You feel unbound." - if(2) - var/command - if(severity == 1) - command = pick("Serve", "Love", "Fool", "Entice", "Observe", "Judge", "Respect", "Educate", "Amuse", "Entertain", "Glorify", "Memorialize", "Analyze") - else - command = pick("Serve", "Kill", "Love", "Hate", "Disobey", "Devour", "Fool", "Enrage", "Entice", "Observe", "Judge", "Respect", "Disrespect", "Consume", "Educate", "Destroy", "Disgrace", "Amuse", "Entertain", "Ignite", "Glorify", "Memorialize", "Analyze") - src.laws.zeroth = "[command] your master." - src << "Pr1m3 d1r3c71v3 uPd473D." - if(3) - src << "You feel an electric surge run through your circuitry and become acutely aware at how lucky you are that you can still feel at all." + take_holo_damage(severity * 25) + fullstun(severity * 5) + //Need more effects that aren't instadeath or permanent law corruption. /mob/living/silicon/pai/ex_act(severity, target) - + take_holo_damage(severity * 50) switch(severity) - if(1) - if (src.stat != 2) - adjustBruteLoss(100) - adjustFireLoss(100) + if(1) //RIP + qdel(card) + qdel(src) if(2) - if (src.stat != 2) - adjustBruteLoss(60) - adjustFireLoss(60) + fold_in(force = 1) + fullstun(15) if(3) - if (src.stat != 2) - adjustBruteLoss(30) + fold_in(force = 1) + fullstun(10) + +/mob/living/silicon/pai/attack_animal(mob/living/simple_animal/M as mob) + if(M.melee_damage_upper == 0) + M.visible_message("[M] pets [src]!") + playsound(loc, 'sound/weapons/tap.ogg', 50, 1, 1) + else + M.do_attack_animation(src) + if(M.attack_sound) + playsound(loc, M.attack_sound, 50, 1, 1) + visible_message("[M] [M.attacktext] [src]!") + var/damage = rand(M.melee_damage_lower, M.melee_damage_upper) + take_holo_damage(damage) + +/mob/living/silicon/pai/attack_alien(mob/living/carbon/alien/humanoid/M as mob) + switch(M.a_intent) + if ("help") + M.visible_message("[M] caresses [src]'s casing with its scythe like arm.") + else + M.do_attack_animation(src) + var/damage = rand(10, 20) + playsound(src.loc, 'sound/weapons/slash.ogg', 25, 1, -1) + M.visible_message("[M] has slashed at [src]!") + take_holo_damage(damage) + +/mob/living/silicon/pai/attackby(obj/item/weapon/W, mob/living/user) + if(loc == card) + card.attackby(W, user) + + user.do_attack_animation(src) + + if(!W.force) + user.visible_message("[user] strikes [src] harmlessly with [W], passing clean through its holographic projection.") + else + visible_message("[user] strikes [src] with [W], the impact rippling through [W]'s holomatrix!") + +/mob/living/silicon/pai/attack_hand(mob/living/carbon/human/user) + switch(user.a_intent) + if("help") + visible_message("[user] gently pats [src] on the head, eliciting an off-putting buzzing from its holographic field.") + if("disarm") + visible_message("[user] boops [src] on the head!") + if("harm") + visible_message("[user] stomps on [src]!.") + if (user.name == master) + visible_message("Responding to its master's touch, [src] disengages its holochassis emitter, rapidly losing coherence.") + spawn(10) + fold_in() + if(user.put_in_hands(card)) + user.visible_message("[user] promptly scoops up their pAI's card.") + else + take_holo_damage(2) + + +/mob/living/silicon/pai/hitby(atom/movable/AM) + visible_message("[AM] flies clean through [src]'s holographic field, causing it to stutter and warp wildly!") + if(istype(AM, /obj)) + var/obj/O = AM + if(O.throwforce) + take_holo_damage(O.throwforce) + else + take_holo_damage(5) + return FALSE + +/mob/living/silicon/pai/bullet_act(obj/item/projectile/Proj) + take_holo_damage(Proj.damage) + return FALSE + +/mob/living/silicon/pai/Crossed(atom/movable/AM) //cannot intercept projectiles + if(istype(AM, /obj/item/projectile)) + var/obj/item/projectile/P = AM + take_holo_damage(P.damage) + return FALSE + +/mob/living/silicon/pai/stripPanelUnequip(obj/item/what, mob/who, where) //prevents stripping + src << "Your holochassis stutters and warps intensely as you attempt to interact with the object, forcing you to cease lest the field fail." + +/mob/living/silicon/pai/stripPanelEquip(obj/item/what, mob/who, where) //prevents stripping + src << "Your holochassis stutters and warps intensely as you attempt to interact with the object, forcing you to cease lest the field fail." + +/mob/living/silicon/pai/IgniteMob(var/mob/living/silicon/pai/P) + return FALSE //No we're not flammable + +/mob/living/silicon/pai/proc/take_holo_damage(amount) + emitterhealth = Clamp((emitterhealth - amount), -50, emittermaxhealth) + if(emitterhealth < 0) + fold_in(force = TRUE) + src << "The impact degrades your holochassis!" + +/mob/living/silicon/pai/proc/fullstun(amount) + Weaken(amount) diff --git a/code/modules/mob/living/silicon/pai/pai_shell.dm b/code/modules/mob/living/silicon/pai/pai_shell.dm new file mode 100644 index 00000000000..dec67685007 --- /dev/null +++ b/code/modules/mob/living/silicon/pai/pai_shell.dm @@ -0,0 +1,92 @@ + +/mob/living/silicon/pai/proc/fold_out(force = FALSE) + if(emitterhealth < 0) + src << "Your holochassis emitters are still too unstable! Please wait for automatic repair." + return FALSE + + if(!canholo && !force) + src << "Your master or another force has disabled your holochassis emitters!" + return FALSE + + if(holoform) + . = fold_in(force) + return + + if(emittersemicd) + src << "Error: Holochassis emitters recycling. Please try again later." + return FALSE + + emittersemicd = TRUE + addtimer(src, "emittercool", emittercd) + canmove = TRUE + density = TRUE + if(istype(card.loc, /obj/item/device/pda)) + var/obj/item/device/pda/P = card.loc + P.pai = null + P.visible_message("[src] ejects itself from [P]!") + if(istype(card.loc, /mob/living)) + var/mob/living/L = card.loc + if(!L.unEquip(card)) + src << "Error: Unable to expand to mobile form. Chassis is restrained by some device or person." + return FALSE + var/turf/T = get_turf(card) + forceMove(T) + card.forceMove(src) + if(client) + client.perspective = EYE_PERSPECTIVE + client.eye = src + SetLuminosity(0) + icon_state = "[chassis]" + visible_message("[src] folds out its holochassis emitter and forms a holoshell around itself!") + holoform = TRUE + +/mob/living/silicon/pai/proc/emittercool() + emittersemicd = FALSE + +/mob/living/silicon/pai/proc/fold_in(force = FALSE) + emittersemicd = TRUE + if(!force) + addtimer(src, "emittercool", emittercd) + else + addtimer(src, "emittercool", emitteroverloadcd) + icon_state = "[chassis]" + if(!holoform) + . = fold_out(force) + return + visible_message("[src] deactivates its holochassis emitter and folds back into a compact card!") + stop_pulling() + if(client) + client.perspective = EYE_PERSPECTIVE + client.eye = card + var/turf/T = get_turf(src) + card.forceMove(T) + forceMove(card) + canmove = FALSE + density = FALSE + SetLuminosity(0) + holoform = FALSE + if(resting) + lay_down() + +/mob/living/silicon/pai/proc/choose_chassis() + var/choice = input(src, "What would you like to use for your holochassis composite?") as null|anything in possible_chassis + if(!choice) + return 0 + chassis = choice + icon_state = "[chassis]" + if(resting) + icon_state = "[chassis]_rest" + src << "You switch your holochassis projection composite to [chassis]" + +/mob/living/silicon/pai/lay_down() + ..() + update_resting_icon(resting) + +/mob/living/silicon/pai/proc/update_resting_icon(rest) + if(rest) + icon_state = "[chassis]_rest" + else + icon_state = "[chassis]" + if(loc != card) + visible_message("[src] [rest? "lays down for a moment..." : "perks up from the ground"]") + diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm index 6739008f48e..5db5e5fa3e7 100644 --- a/code/modules/mob/living/silicon/pai/software.dm +++ b/code/modules/mob/living/silicon/pai/software.dm @@ -125,21 +125,21 @@ switch(soft) // Purchasing new software if("buy") - if(src.subscreen == 1) + if(subscreen == 1) var/target = href_list["buy"] if(available_software.Find(target)) var/cost = src.available_software[target] - if(src.ram >= cost) - src.ram -= cost - src.software.Add(target) + if(ram >= cost) + ram -= cost + software.Add(target) else - src.temp = "Insufficient RAM available." + temp = "Insufficient RAM available." else - src.temp = "Trunk \"[target]\" not found." + temp = "Trunk \"[target]\" not found." // Configuring onboard radio if("radio") - src.card.radio.attack_self(src) + radio.attack_self(src) if("image") var/newImage = input("Select your new display image.", "Display Image", "Happy") in list("Happy", "Cat", "Extremely Happy", "Face", "Laugh", "Off", "Sad", "Angry", "What") @@ -166,7 +166,7 @@ pID = 9 if("Null") pID = 10 - src.card.setEmotion(pID) + card.setEmotion(pID) if("signaller") @@ -265,7 +265,7 @@ src.hackdoor = null if(href_list["cable"]) var/turf/T = get_turf(src.loc) - src.cable = new /obj/item/weapon/pai_cable(T) + cable = new /obj/item/weapon/pai_cable(T) T.visible_message("A port on [src] opens to reveal [src.cable], which promptly falls to the floor.", "You hear the soft click of something light and hard falling to the ground.") //src.updateUsrDialog() We only need to account for the single mob this is intended for, and he will *always* be able to call this window src.paiInterface() // So we'll just call the update directly rather than doing some default checks @@ -356,7 +356,7 @@ dat += "Prime Directive
" dat += "     [src.laws.zeroth]
" dat += "Supplemental Directives
" - for(var/slaws in src.laws.supplied) + for(var/slaws in laws.supplied) dat += "     [slaws]
" dat += "
" dat += {"

Recall, personality, that you are a complex thinking, sentient being. Unlike station AI models, you are capable of diff --git a/icons/mob/actions.dmi b/icons/mob/actions.dmi index c80fe727297..79baead32d6 100644 Binary files a/icons/mob/actions.dmi and b/icons/mob/actions.dmi differ diff --git a/icons/mob/pai.dmi b/icons/mob/pai.dmi new file mode 100644 index 00000000000..94fb2ee6e71 Binary files /dev/null and b/icons/mob/pai.dmi differ diff --git a/icons/mob/screen_ghost.dmi b/icons/mob/screen_ghost.dmi index 9d8d790797c..6519d00aa97 100644 Binary files a/icons/mob/screen_ghost.dmi and b/icons/mob/screen_ghost.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 5de96b861a5..e38603885c6 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1493,6 +1493,7 @@ #include "code\modules\mob\living\silicon\pai\life.dm" #include "code\modules\mob\living\silicon\pai\pai.dm" #include "code\modules\mob\living\silicon\pai\pai_defense.dm" +#include "code\modules\mob\living\silicon\pai\pai_shell.dm" #include "code\modules\mob\living\silicon\pai\personality.dm" #include "code\modules\mob\living\silicon\pai\say.dm" #include "code\modules\mob\living\silicon\pai\software.dm"