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