diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 106eb891bd6..51a8e941990 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -61,7 +61,8 @@ var/datum/language_holder/language_holder var/unconvertable = FALSE var/late_joiner = FALSE - + ///has this mind ever been an AI + var/has_ever_been_ai = FALSE var/last_death = 0 var/force_escaped = FALSE // Set by Into The Sunset command of the shuttle manipulator diff --git a/code/game/objects/items/robot/ai_upgrades.dm b/code/game/objects/items/robot/ai_upgrades.dm index c098ca15f34..59e685692e7 100644 --- a/code/game/objects/items/robot/ai_upgrades.dm +++ b/code/game/objects/items/robot/ai_upgrades.dm @@ -25,7 +25,7 @@ AI.hack_software = TRUE log_silicon("[key_name(user)] has upgraded [key_name(AI)] with a [src].") message_admins("[ADMIN_LOOKUPFLW(user)] has upgraded [ADMIN_LOOKUPFLW(AI)] with a [src].") - to_chat(user, span_notice("You upgrade [AI]. [src] is consumed in the process.")) + to_chat(user, span_notice("You install [src], upgrading [AI].")) qdel(src) return TRUE diff --git a/code/game/objects/structures/ai_core.dm b/code/game/objects/structures/ai_core.dm index b647537c370..39d95b29bfb 100644 --- a/code/game/objects/structures/ai_core.dm +++ b/code/game/objects/structures/ai_core.dm @@ -1,3 +1,5 @@ +#define AI_CORE_BRAIN(X) X.braintype == "Android" ? "brain" : "MMI" + /obj/structure/ai_core density = TRUE anchored = FALSE @@ -9,45 +11,76 @@ var/state = EMPTY_CORE var/datum/ai_laws/laws var/obj/item/circuitboard/aicore/circuit - var/obj/item/mmi/brain - var/can_deconstruct = TRUE + var/obj/item/mmi/core_mmi /obj/structure/ai_core/Initialize(mapload) . = ..() laws = new laws.set_laws_config() +/obj/structure/ai_core/examine(mob/user) + . = ..() + if(!anchored) + if(state != EMPTY_CORE) + . += span_notice("It has some bolts that could be tightened.") + else + . += span_notice("It has some bolts that could be tightened. The frame can be melted down.") + else + switch(state) + if(EMPTY_CORE) + . += span_notice("There is a slot for a circuit board, its bolts can be loosened.") + if(CIRCUIT_CORE) + . += span_notice("The circuit board can be screwed into place or pried out.") + if(SCREWED_CORE) + . += span_notice("The frame can be wired, the circuit board can be unfastened.") + if(CABLED_CORE) + if(!core_mmi) + . += span_notice("There are wires which could be hooked up to an MMI or positronic brain, or cut.") + else + var/accept_laws = TRUE + if(core_mmi.laws.id != DEFAULT_AI_LAWID || !core_mmi.brainmob || !core_mmi.brainmob?.mind) + accept_laws = FALSE + . += span_notice("There is a slot for a reinforced glass panel, the [AI_CORE_BRAIN(core_mmi)] could be pried out.[accept_laws ? " A law module can be swiped across." : ""]") + if(GLASS_CORE) + . += span_notice("The monitor [core_mmi?.brainmob?.mind && !core_mmi?.brainmob?.suiciding ? "and neural interface " : ""]can be screwed in, the panel can be pried out.") + if(AI_READY_CORE) + . += span_notice("The monitor's connection can be cut[core_mmi?.brainmob?.mind && !core_mmi?.brainmob?.suiciding ? " the neural interface can be screwed in." : "."]") + /obj/structure/ai_core/handle_atom_del(atom/A) if(A == circuit) circuit = null if((state != GLASS_CORE) && (state != AI_READY_CORE)) state = EMPTY_CORE update_appearance() - if(A == brain) - brain = null + if(A == core_mmi) + core_mmi = null return ..() /obj/structure/ai_core/Destroy() QDEL_NULL(circuit) - QDEL_NULL(brain) + QDEL_NULL(core_mmi) QDEL_NULL(laws) return ..() /obj/structure/ai_core/deactivated - name = "inactive AI" icon_state = "ai-empty" anchored = TRUE state = AI_READY_CORE -/obj/structure/ai_core/deactivated/Initialize(mapload) +/obj/structure/ai_core/deactivated/Initialize(mapload, posibrain = FALSE) . = ..() circuit = new(src) + if(posibrain) + core_mmi = new/obj/item/mmi/posibrain(src) + else + core_mmi = new(src) + core_mmi.brain = new(core_mmi) + core_mmi.update_appearance() /obj/structure/ai_core/latejoin_inactive name = "networked AI core" desc = "This AI core is connected by bluespace transmitters to NTNet, allowing for an AI personality to be downloaded to it on the fly mid-shift." - can_deconstruct = FALSE icon_state = "ai-empty" anchored = TRUE state = AI_READY_CORE @@ -58,6 +91,9 @@ /obj/structure/ai_core/latejoin_inactive/Initialize(mapload) . = ..() circuit = new(src) + core_mmi = new(src) + core_mmi.brain = new(core_mmi) + core_mmi.update_appearance() GLOB.latejoin_ai_cores += src /obj/structure/ai_core/latejoin_inactive/Destroy() @@ -100,21 +136,45 @@ default_unfasten_wrench(user, tool) return TOOL_ACT_TOOLTYPE_SUCCESS -/obj/structure/ai_core/attackby(obj/item/P, mob/user, params) +/obj/structure/ai_core/screwdriver_act(mob/living/user, obj/item/tool) + . = ..() + if(state == AI_READY_CORE) + if(!core_mmi) + balloon_alert(user, "no brain installed!") + return TOOL_ACT_TOOLTYPE_SUCCESS + else if(!core_mmi.brainmob?.mind || core_mmi.brainmob?.suiciding) + balloon_alert(user, "brain is inactive!") + return TOOL_ACT_TOOLTYPE_SUCCESS + else + balloon_alert(user, "connecting neural network...") + if(!tool.use_tool(src, user, 10 SECONDS)) + return TOOL_ACT_TOOLTYPE_SUCCESS + if(!ai_structure_to_mob()) + return TOOL_ACT_TOOLTYPE_SUCCESS + balloon_alert(user, "connected neural network") + return TOOL_ACT_TOOLTYPE_SUCCESS + +/obj/structure/ai_core/attackby(obj/item/P, mob/living/user, params) if(!anchored) - if(P.tool_behaviour == TOOL_WELDER && can_deconstruct) + if(P.tool_behaviour == TOOL_WELDER) if(state != EMPTY_CORE) - to_chat(user, span_warning("The core must be empty to deconstruct it!")) + balloon_alert(user, "core must be empty to deconstruct it!") return if(!P.tool_start_check(user, amount=0)) return - to_chat(user, span_notice("You start to deconstruct the frame...")) + balloon_alert(user, "deconstructing frame...") if(P.use_tool(src, user, 20, volume=50) && state == EMPTY_CORE) - to_chat(user, span_notice("You deconstruct the frame.")) + balloon_alert(user, "deconstructed frame") deconstruct(TRUE) return + else + if(!user.combat_mode) + balloon_alert(user, "bolt it down first!") + return + else + return ..() else switch(state) if(EMPTY_CORE) @@ -122,7 +182,7 @@ if(!user.transferItemToLoc(P, src)) return playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, span_notice("You place the circuit board inside the frame.")) + balloon_alert(user, "circuit board inserted") update_appearance() state = CIRCUIT_CORE circuit = P @@ -130,13 +190,13 @@ if(CIRCUIT_CORE) if(P.tool_behaviour == TOOL_SCREWDRIVER) P.play_tool_sound(src) - to_chat(user, span_notice("You screw the circuit board into place.")) + balloon_alert(user, "board screwed into place") state = SCREWED_CORE update_appearance() return if(P.tool_behaviour == TOOL_CROWBAR) P.play_tool_sound(src) - to_chat(user, span_notice("You remove the circuit board.")) + balloon_alert(user, "circuit board removed") state = EMPTY_CORE update_appearance() circuit.forceMove(loc) @@ -145,7 +205,7 @@ if(SCREWED_CORE) if(P.tool_behaviour == TOOL_SCREWDRIVER && circuit) P.play_tool_sound(src) - to_chat(user, span_notice("You unfasten the circuit board.")) + balloon_alert(user, "circuit board unfastened") state = CIRCUIT_CORE update_appearance() return @@ -153,50 +213,70 @@ var/obj/item/stack/cable_coil/C = P if(C.get_amount() >= 5) playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, span_notice("You start to add cables to the frame...")) + balloon_alert(user, "adding cables to frame...") if(do_after(user, 20, target = src) && state == SCREWED_CORE && C.use(5)) - to_chat(user, span_notice("You add cables to the frame.")) + balloon_alert(user, "added cables to frame.") state = CABLED_CORE update_appearance() else - to_chat(user, span_warning("You need five lengths of cable to wire the AI core!")) + balloon_alert(user, "need five lengths of cable!") return if(CABLED_CORE) if(P.tool_behaviour == TOOL_WIRECUTTER) - if(brain) - to_chat(user, span_warning("Get that [brain.name] out of there first!")) + if(core_mmi) + balloon_alert(user, "remove the [AI_CORE_BRAIN(core_mmi)] first!") else P.play_tool_sound(src) - to_chat(user, span_notice("You remove the cables.")) + balloon_alert(user, "cables removed") state = SCREWED_CORE update_appearance() new /obj/item/stack/cable_coil(drop_location(), 5) return if(istype(P, /obj/item/stack/sheet/rglass)) + if(!core_mmi) + balloon_alert(user, "add a brain first!") + return var/obj/item/stack/sheet/rglass/G = P if(G.get_amount() >= 2) playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - to_chat(user, span_notice("You start to put in the glass panel...")) + balloon_alert(user, "adding glass panel...") if(do_after(user, 20, target = src) && state == CABLED_CORE && G.use(2)) - to_chat(user, span_notice("You put in the glass panel.")) + balloon_alert(user, "added glass panel") state = GLASS_CORE update_appearance() else - to_chat(user, span_warning("You need two sheets of reinforced glass to insert them into the AI core!")) + balloon_alert(user, "need two sheets of reinforced glass!") return if(istype(P, /obj/item/ai_module)) - if(brain && brain.laws.id != DEFAULT_AI_LAWID) - to_chat(user, span_warning("The installed [brain.name] already has set laws!")) + if(!core_mmi) + balloon_alert(user, "no brain installed!") + return + if(!core_mmi.brainmob || !core_mmi.brainmob?.mind || core_mmi.brainmob?.suiciding) + balloon_alert(user, "[AI_CORE_BRAIN(core_mmi)] is inactive!") + return + if(core_mmi.laws.id != DEFAULT_AI_LAWID) + balloon_alert(user, "[AI_CORE_BRAIN(core_mmi)] already has set laws!") return var/obj/item/ai_module/module = P module.install(laws, user) return - if(istype(P, /obj/item/mmi) && !brain) + if(istype(P, /obj/item/mmi) && !core_mmi) var/obj/item/mmi/M = P if(!M.brain_check(user)) + var/install = tgui_alert(user, "This [AI_CORE_BRAIN(M)] is inactive, would you like to make an inactive AI?", "Installing AI [AI_CORE_BRAIN(M)]", list("Yes", "No")) + if(install != "Yes") + return + if(M.brainmob?.suiciding) + to_chat(user, span_warning("[M.name] is completely useless!")) + return + if(!user.transferItemToLoc(M, src)) + return + core_mmi = M + balloon_alert(user, "added [AI_CORE_BRAIN(core_mmi)] to frame") + update_appearance() return var/mob/living/brain/B = M.brainmob @@ -207,49 +287,36 @@ if(!user.transferItemToLoc(M,src)) return - brain = M - to_chat(user, span_notice("You add [M.name] to the frame.")) + core_mmi = M + balloon_alert(user, "added [AI_CORE_BRAIN(core_mmi)] to frame") update_appearance() return - if(P.tool_behaviour == TOOL_CROWBAR && brain) + if(P.tool_behaviour == TOOL_CROWBAR && core_mmi) P.play_tool_sound(src) - to_chat(user, span_notice("You remove the brain.")) - brain.forceMove(loc) - brain = null + balloon_alert(user, "removed [AI_CORE_BRAIN(core_mmi)]") + core_mmi.forceMove(loc) + core_mmi = null update_appearance() return if(GLASS_CORE) if(P.tool_behaviour == TOOL_CROWBAR) P.play_tool_sound(src) - to_chat(user, span_notice("You remove the glass panel.")) + balloon_alert(user, "removed glass panel") state = CABLED_CORE update_appearance() new /obj/item/stack/sheet/rglass(loc, 2) return if(P.tool_behaviour == TOOL_SCREWDRIVER) + if(core_mmi?.brainmob?.suiciding) + to_chat(user, span_warning("The brain installed is completely useless.")) + return P.play_tool_sound(src) - to_chat(user, span_notice("You connect the monitor.")) - if(brain) - var/mob/living/brain/B = brain.brainmob - B.mind?.remove_antags_for_borging() - - var/mob/living/silicon/ai/A = null - - if (brain.overrides_aicore_laws) - A = new /mob/living/silicon/ai(loc, brain.laws, B) - brain.laws = null //Brain's law datum is being donated, so we need the brain to let it go or the GC will eat it - else - A = new /mob/living/silicon/ai(loc, laws, B) - laws = null //we're giving the new AI this datum, so let's not delete it when we qdel(src) 5 lines from now - - if(brain.force_replace_ai_name) - A.fully_replace_character_name(A.name, brain.replacement_ai_name()) - SSblackbox.record_feedback("amount", "ais_created", 1) - deadchat_broadcast(" has been brought online at [get_area_name(A, TRUE)].", span_name("[A]"), follow_target=A, message_type=DEADCHAT_ANNOUNCEMENT) - qdel(src) + balloon_alert(user, "connected monitor[core_mmi?.brainmob?.mind ? " and neural network" : ""]") + if(core_mmi.brainmob?.mind) + ai_structure_to_mob() else state = AI_READY_CORE update_appearance() @@ -259,14 +326,38 @@ if(istype(P, /obj/item/aicard)) return //handled by /obj/structure/ai_core/transfer_ai() - if(P.tool_behaviour == TOOL_SCREWDRIVER) + if(P.tool_behaviour == TOOL_WIRECUTTER) P.play_tool_sound(src) - to_chat(user, span_notice("You disconnect the monitor.")) + balloon_alert(user, "disconnected monitor") state = GLASS_CORE update_appearance() return return ..() +/obj/structure/ai_core/proc/ai_structure_to_mob() + var/mob/living/brain/the_brainmob = core_mmi.brainmob + if(!the_brainmob.mind || the_brainmob.suiciding) + return FALSE + the_brainmob.mind.remove_antags_for_borging() + if(!the_brainmob.mind.has_ever_been_ai) + SSblackbox.record_feedback("amount", "ais_created", 1) + var/mob/living/silicon/ai/ai_mob = null + + if(core_mmi.overrides_aicore_laws) + ai_mob = new /mob/living/silicon/ai(loc, core_mmi.laws, the_brainmob) + core_mmi.laws = null //MMI's law datum is being donated, so we need the MMI to let it go or the GC will eat it + else + ai_mob = new /mob/living/silicon/ai(loc, laws, the_brainmob) + laws = null //we're giving the new AI this datum, so let's not delete it when we qdel(src) 5 lines from now + + if(core_mmi.force_replace_ai_name) + ai_mob.fully_replace_character_name(ai_mob.name, core_mmi.replacement_ai_name()) + if(core_mmi.braintype == "Android") + ai_mob.posibrain_inside = TRUE + deadchat_broadcast(" has been brought online at [get_area_name(ai_mob, format_text = TRUE)].", span_name("[ai_mob]"), follow_target = ai_mob, message_type = DEADCHAT_ANNOUNCEMENT) + qdel(src) + return TRUE + /obj/structure/ai_core/update_icon_state() switch(state) if(EMPTY_CORE) @@ -276,7 +367,7 @@ if(SCREWED_CORE) icon_state = "2" if(CABLED_CORE) - if(brain) + if(core_mmi) icon_state = "3b" else icon_state = "3" @@ -287,7 +378,7 @@ return ..() /obj/structure/ai_core/deconstruct(disassembled = TRUE) - if(state == GLASS_CORE) + if(state >= GLASS_CORE) new /obj/item/stack/sheet/rglass(loc, 2) if(state >= CABLED_CORE) new /obj/item/stack/cable_coil(loc, 5) @@ -315,6 +406,13 @@ That prevents a few funky behaviors. /obj/structure/ai_core/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) if(state != AI_READY_CORE || !..()) return + if(core_mmi && core_mmi.brainmob) + if(core_mmi.brainmob.mind) + to_chat(user, span_warning("[src] already contains an active mind!")) + return + else if(core_mmi.brainmob.suiciding) + to_chat(user, span_warning("[AI_CORE_BRAIN(core_mmi)] installed in [src] is completely useless!")) + return //Transferring a carded AI to a core. if(interaction == AI_TRANS_FROM_CARD) AI.control_disabled = FALSE @@ -324,6 +422,10 @@ That prevents a few funky behaviors. to_chat(user, "[span_boldnotice("Transfer successful")]: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") card.AI = null AI.battery = circuit.battery + if(core_mmi.braintype == "Android") + AI.posibrain_inside = TRUE + else + AI.posibrain_inside = FALSE qdel(src) else //If for some reason you use an empty card on an empty AI terminal. to_chat(user, span_alert("There is no AI loaded on this terminal.")) @@ -331,3 +433,5 @@ That prevents a few funky behaviors. /obj/item/circuitboard/aicore name = "AI core (AI Core Board)" //Well, duh, but best to be consistent var/battery = 200 //backup battery for when the AI loses power. Copied to/from AI mobs when carding, and placed here to avoid recharge via deconning the core + +#undef AI_CORE_BRAIN diff --git a/code/modules/mob/living/brain/MMI.dm b/code/modules/mob/living/brain/MMI.dm index 17e3e31749a..993cbe9942c 100644 --- a/code/modules/mob/living/brain/MMI.dm +++ b/code/modules/mob/living/brain/MMI.dm @@ -57,8 +57,20 @@ if(brain) to_chat(user, span_warning("There's already a brain in the MMI!")) return - if(!newbrain.brainmob) - to_chat(user, span_warning("You aren't sure where this brain came from, but you're pretty sure it's a useless brain!")) + if(newbrain.suicided) + to_chat(user, span_warning("[newbrain] is completely useless.")) + return + if(!newbrain.brainmob?.mind || !newbrain.brainmob) + var/install = tgui_alert(user, "[newbrain] is inactive, slot it in anyway?", "Installing Brain", list("Yes", "No")) + if(install != "Yes") + return + if(!user.transferItemToLoc(newbrain, src)) + return + user.visible_message(span_notice("[user] sticks [newbrain] into [src]."), span_notice("[src]'s indicator light turns red as you insert [newbrain]. Its brainwave activity alarm buzzes.")) + brain = newbrain + brain.organ_flags |= ORGAN_FROZEN + name = "[initial(name)]: [copytext(newbrain.name, 1, -8)]" + update_appearance() return if(!user.transferItemToLoc(O, src)) @@ -113,14 +125,15 @@ to_chat(user, span_notice("You unlock and upend [src], spilling the brain onto the floor.")) /obj/item/mmi/proc/eject_brain(mob/user) - brainmob.container = null //Reset brainmob mmi var. - brainmob.forceMove(brain) //Throw mob into brain. - brainmob.set_stat(DEAD) - brainmob.emp_damage = 0 - brainmob.reset_perspective() //so the brainmob follows the brain organ instead of the mmi. And to update our vision - brain.brainmob = brainmob //Set the brain to use the brainmob - log_game("[key_name(user)] has ejected the brain of [key_name(brainmob)] from an MMI at [AREACOORD(src)]") - brainmob = null //Set mmi brainmob var to null + if(brain.brainmob) + brainmob.container = null //Reset brainmob mmi var. + brainmob.forceMove(brain) //Throw mob into brain. + brainmob.set_stat(DEAD) + brainmob.emp_damage = 0 + brainmob.reset_perspective() //so the brainmob follows the brain organ instead of the mmi. And to update our vision + brain.brainmob = brainmob //Set the brain to use the brainmob + log_game("[key_name(user)] has ejected the brain of [key_name(brainmob)] from an MMI at [AREACOORD(src)]") + brainmob = null //Set mmi brainmob var to null brain.forceMove(drop_location()) if(Adjacent(user)) user.put_in_hands(brain) @@ -252,7 +265,7 @@ var/mob/living/brain/B = brainmob if(!B) if(user) - to_chat(user, span_warning("\The [src] indicates that there is no brain present!")) + to_chat(user, span_warning("\The [src] indicates that there is no mind present!")) return FALSE if(!B.key || !B.mind) if(user) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 80b12659a01..58fac2ac7c1 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -108,6 +108,12 @@ interaction_range = null var/atom/movable/screen/ai/modpc/interfaceButton + ///whether its mmi is a posibrain or regular mmi when going ai mob to ai core structure + var/posibrain_inside = FALSE + ///whether its cover is opened, so you can wirecut it for deconstruction + var/opened = FALSE + ///whether AI is anchored or not, used for checks + var/is_anchored = TRUE /mob/living/silicon/ai/Initialize(mapload, datum/ai_laws/L, mob/target_ai) . = ..() @@ -133,6 +139,8 @@ if(mind.special_role) to_chat(src, span_userdanger("You have been installed as an AI! ")) to_chat(src, span_danger("You must obey your silicon laws above all else. Your objectives will consider you to be dead.")) + if(!mind.has_ever_been_ai) + mind.has_ever_been_ai = TRUE to_chat(src, "You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras).") to_chat(src, "To look at other parts of the station, click on yourself to get a camera menu.") @@ -363,20 +371,61 @@ return battery = battery - 50 to_chat(src, span_notice("You route power from your backup battery to move the bolts.")) - var/is_anchored = FALSE - if(move_resist == MOVE_FORCE_OVERPOWERING) + flip_anchored() + to_chat(src, "You are now [is_anchored ? "" : "un"]anchored.") + +/mob/living/silicon/ai/proc/flip_anchored() + if(is_anchored) + is_anchored = !is_anchored move_resist = MOVE_FORCE_NORMAL status_flags |= CANPUSH //we want the core to be push-able when un-anchored REMOVE_TRAIT(src, TRAIT_NO_TELEPORT, AI_ANCHOR_TRAIT) else - is_anchored = TRUE + is_anchored = !is_anchored move_resist = MOVE_FORCE_OVERPOWERING status_flags &= ~CANPUSH //we dont want the core to be push-able when anchored ADD_TRAIT(src, TRAIT_NO_TELEPORT, AI_ANCHOR_TRAIT) - to_chat(src, "You are now [is_anchored ? "" : "un"]anchored.") - // the message in the [] will change depending whether or not the AI is anchored +/mob/living/silicon/ai/proc/ai_mob_to_structure() + disconnect_shell() + ShutOffDoomsdayDevice() + var/obj/structure/ai_core/deactivated/ai_core = new(get_turf(src)) + if(!make_mmi_drop_and_transfer(ai_core.core_mmi, the_core = ai_core)) + return FALSE + qdel(src) + return TRUE +/mob/living/silicon/ai/proc/make_mmi_drop_and_transfer(obj/item/mmi/the_mmi, the_core) + var/mmi_type + if(posibrain_inside) + mmi_type = new/obj/item/mmi/posibrain(src) + else + mmi_type = new/obj/item/mmi(src) + if(hack_software) + new/obj/item/malf_upgrade(get_turf(src)) + the_mmi = mmi_type + the_mmi.brain = new /obj/item/organ/internal/brain(the_mmi) + the_mmi.brain.organ_flags |= ORGAN_FROZEN + the_mmi.brain.name = "[real_name]'s brain" + the_mmi.name = "[initial(the_mmi.name)]: [real_name]" + the_mmi.set_brainmob(new /mob/living/brain(the_mmi)) + the_mmi.brainmob.name = src.real_name + the_mmi.brainmob.real_name = src.real_name + the_mmi.brainmob.container = the_mmi + the_mmi.brainmob.set_suicide(suiciding) + the_mmi.brain.suicided = suiciding + if(the_core) + var/obj/structure/ai_core/core = the_core + core.core_mmi = the_mmi + the_mmi.forceMove(the_core) + else + the_mmi.forceMove(get_turf(src)) + if(the_mmi.brainmob.stat == DEAD && !suiciding) + the_mmi.brainmob.set_stat(CONSCIOUS) + if(mind) + mind.transfer_to(the_mmi.brainmob) + the_mmi.update_appearance() + return TRUE /mob/living/silicon/ai/Topic(href, href_list) ..() @@ -774,7 +823,7 @@ to_chat(user, span_warning("No intelligence patterns detected.")) return ShutOffDoomsdayDevice() - var/obj/structure/ai_core/new_core = new /obj/structure/ai_core/deactivated(loc)//Spawns a deactivated terminal at AI location. + var/obj/structure/ai_core/new_core = new /obj/structure/ai_core/deactivated(loc, posibrain_inside)//Spawns a deactivated terminal at AI location. new_core.circuit.battery = battery ai_restore_power()//So the AI initially has power. control_disabled = TRUE //Can't control things remotely if you're stuck in a card! diff --git a/code/modules/mob/living/silicon/ai/ai_defense.dm b/code/modules/mob/living/silicon/ai/ai_defense.dm index 54c63bba2e4..65962dbba91 100644 --- a/code/modules/mob/living/silicon/ai/ai_defense.dm +++ b/code/modules/mob/living/silicon/ai/ai_defense.dm @@ -7,7 +7,7 @@ return MOD.install(laws, user) //Proc includes a success mesage so we don't need another one return - if(W.force && W.damtype != STAMINA && stat != DEAD) //only sparks if real damage is dealt. + if(W.force && W.damtype != STAMINA && stat != DEAD && !QDELETED(src)) //only sparks if real damage is dealt. spark_system.start() return ..() @@ -59,3 +59,90 @@ /mob/living/silicon/ai/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash, length = 25) return // no eyes, no flashing + +/mob/living/silicon/ai/emag_act(mob/user, obj/item/card/emag/emag_card)///emags access panel lock, so you can crowbar it without robotics access or consent + . = ..() + if(emagged) + balloon_alert(user, "access panel lock already shorted!") + return + balloon_alert(user, "access panel lock shorted") + to_chat(src, span_warning("[user] shorts out your access panel lock!")) + emagged = TRUE + +/mob/living/silicon/ai/crowbar_act(mob/living/user, obj/item/tool) + . = ..() + if(user.combat_mode) + return + if(!is_anchored) + balloon_alert(user, "bolt it down first!") + return TOOL_ACT_TOOLTYPE_SUCCESS + if(opened) + if(emagged) + balloon_alert(user, "access panel lock damaged!") + return TOOL_ACT_TOOLTYPE_SUCCESS + balloon_alert(user, "closing access panel...") + balloon_alert(src, "access panel being closed...") + if(!tool.use_tool(src, user, 5 SECONDS)) + return TOOL_ACT_TOOLTYPE_SUCCESS + balloon_alert(src, "access panel closed") + balloon_alert(user, "access panel closed") + opened = FALSE + return TOOL_ACT_TOOLTYPE_SUCCESS + if(stat == DEAD) + to_chat(user, span_warning("The access panel looks damaged, you try dislodging the cover.")) + else + var/consent + var/consent_override = FALSE + if(ishuman(user)) + var/mob/living/carbon/human/human_user = user + if(human_user.wear_id) + var/list/access = human_user.wear_id.GetAccess() + if(ACCESS_ROBOTICS in access) + consent_override = TRUE + if(mind) + consent = tgui_alert(src, "[user] is attempting to open your access panel, unlock the cover?", "AI Access Panel", list("Yes", "No")) + if(consent == "No" && !consent_override && !emagged) + to_chat(user, span_notice("[src] refuses to unlock its access panel.")) + return TOOL_ACT_TOOLTYPE_SUCCESS + if(consent != "Yes" && (consent_override || emagged)) + to_chat(user, span_warning("[src] refuses to unlock its access panel...so you[!emagged ? " swipe your ID and " : " "]open it anyway!")) + else + if(!consent_override && !emagged) + to_chat(user, span_notice("[src] did not respond to your request to unlock its access panel cover lock.")) + return TOOL_ACT_TOOLTYPE_SUCCESS + else + to_chat(user, span_notice("[src] did not respond to your request to unlock its access panel cover lock. You[!emagged ? " swipe your ID and " : " "]open it anyway.")) + + balloon_alert(user, "prying open access panel...") + balloon_alert(src, "access panel being pried open...") + if(!tool.use_tool(src, user, (stat == DEAD ? 40 SECONDS : 5 SECONDS))) + return TOOL_ACT_TOOLTYPE_SUCCESS + balloon_alert(src, "access panel opened") + balloon_alert(user, "access panel opened") + opened = TRUE + return TOOL_ACT_TOOLTYPE_SUCCESS + +/mob/living/silicon/ai/wirecutter_act(mob/living/user, obj/item/tool) + . = ..() + if(user.combat_mode) + return + if(!is_anchored) + balloon_alert(user, "bolt it down first!") + return TOOL_ACT_TOOLTYPE_SUCCESS + if(!opened) + balloon_alert(user, "open the access panel first!") + return TOOL_ACT_TOOLTYPE_SUCCESS + balloon_alert(src, "neural network being disconnected...") + balloon_alert(user, "disconnecting neural network...") + if(!tool.use_tool(src, user, (stat == DEAD ? 40 SECONDS : 5 SECONDS))) + return TOOL_ACT_TOOLTYPE_SUCCESS + if(IS_MALF_AI(src)) + to_chat(user, span_userdanger("The voltage inside the wires rises dramatically!")) + user.electrocute_act(120, src) + opened = FALSE + return TOOL_ACT_TOOLTYPE_SUCCESS + balloon_alert(user, "disconnected neural network") + to_chat(src, span_danger("You feel incredibly confused and disorientated.")) + if(!ai_mob_to_structure()) + return TOOL_ACT_TOOLTYPE_SUCCESS + return TOOL_ACT_TOOLTYPE_SUCCESS diff --git a/code/modules/mob/living/silicon/ai/death.dm b/code/modules/mob/living/silicon/ai/death.dm index ce2fa69e814..e3412a29dfb 100644 --- a/code/modules/mob/living/silicon/ai/death.dm +++ b/code/modules/mob/living/silicon/ai/death.dm @@ -23,6 +23,7 @@ status_flags |= CANPUSH //we want it to be pushable when unanchored on death REMOVE_TRAIT(src, TRAIT_NO_TELEPORT, AI_ANCHOR_TRAIT) //removes the anchor trait, because its not anchored anymore move_resist = MOVE_FORCE_NORMAL + is_anchored = FALSE if(eyeobj) eyeobj.setLoc(get_turf(src)) @@ -34,6 +35,9 @@ ShutOffDoomsdayDevice() + if(gibbed) + make_mmi_drop_and_transfer() + if(explosive) addtimer(CALLBACK(GLOBAL_PROC, .proc/explosion, loc, 3, 6, 12, null, 15), 1 SECONDS) diff --git a/code/modules/mob/living/silicon/ai/examine.dm b/code/modules/mob/living/silicon/ai/examine.dm index 265ad8144b9..e838f202af4 100644 --- a/code/modules/mob/living/silicon/ai/examine.dm +++ b/code/modules/mob/living/silicon/ai/examine.dm @@ -1,8 +1,17 @@ /mob/living/silicon/ai/examine(mob/user) . = list("This is [icon2html(src, user)] [src]!") - if (stat == DEAD) + if(stat == DEAD) . += span_deadsay("It appears to be powered-down.") - else + . += span_notice("Its floor bolts are [is_anchored ? "tightened" : "loose"].") + if(is_anchored) + if(!opened) + if(!emagged) + . += span_notice("Its access panel is [stat == DEAD ? "damaged" : "closed and locked"], but could be pried open.") + else + . += span_warning("Its access panel lock is sparking, the cover can be pried open.") + else + . += span_notice("Its neural network connection could be cut, its access panel cover can be pried back into place.") + if(stat != DEAD) if (getBruteLoss()) if (getBruteLoss() < 30) . += span_warning("It looks slightly dented.") diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index fac7d5a45f7..cee912a2a55 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -32,9 +32,6 @@ var/list/software = list() /// The strength of the internal flashlight var/brightness_power = 5 - /// Changes the display to syndi if true - var/emagged = FALSE - /// Name of the one who commands us var/master /// DNA string for owner verification @@ -277,7 +274,7 @@ return ..() -/obj/item/paicard/emag_act(mob/user) // Emag to wipe the master DNA and supplemental directive +/obj/item/paicard/emag_act(mob/user) // Emag to wipe the master DNA and supplemental directive, changes the display to syndi if(!pai) return to_chat(user, span_notice("You override [pai]'s directive system, clearing its master string and supplied directive.")) diff --git a/code/modules/mob/living/silicon/robot/robot_defines.dm b/code/modules/mob/living/silicon/robot/robot_defines.dm index e6c9b01744e..bebdcfe84d2 100644 --- a/code/modules/mob/living/silicon/robot/robot_defines.dm +++ b/code/modules/mob/living/silicon/robot/robot_defines.dm @@ -84,7 +84,6 @@ var/mob/living/silicon/ai/connected_ai = null var/opened = FALSE - var/emagged = FALSE var/emag_cooldown = 0 var/wiresexposed = FALSE diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index c754b3836c4..f4b85d40f04 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -44,7 +44,8 @@ var/law_change_counter = 0 var/obj/machinery/camera/builtInCamera = null var/updating = FALSE //portable camera camerachunk update - + ///Whether we have been emagged + var/emagged = FALSE var/hack_software = FALSE //Will be able to use hacking actions interaction_range = 7 //wireless control range