diff --git a/_maps/RandomRuins/SpaceRuins/oldstation.dmm b/_maps/RandomRuins/SpaceRuins/oldstation.dmm index a857a39a40a..180a0eb6d41 100644 --- a/_maps/RandomRuins/SpaceRuins/oldstation.dmm +++ b/_maps/RandomRuins/SpaceRuins/oldstation.dmm @@ -288,7 +288,9 @@ /turf/open/floor/iron, /area/ruin/space/ancientstation/charlie/bridge) "bA" = ( -/obj/structure/ai_core/deactivated, +/obj/structure/ai_core{ + state = 5 + }, /turf/open/floor/iron/dark, /area/ruin/space/ancientstation/delta/ai) "bC" = ( diff --git a/code/__DEFINES/construction/structures.dm b/code/__DEFINES/construction/structures.dm index e48dc078ca4..874978a8cc9 100644 --- a/code/__DEFINES/construction/structures.dm +++ b/code/__DEFINES/construction/structures.dm @@ -1,12 +1,12 @@ //Defines for construction states //ai core defines -#define EMPTY_CORE 0 -#define CIRCUIT_CORE 1 -#define SCREWED_CORE 2 -#define CABLED_CORE 3 -#define GLASS_CORE 4 -#define AI_READY_CORE 5 +#define CORE_STATE_EMPTY 0 +#define CORE_STATE_CIRCUIT 1 +#define CORE_STATE_SCREWED 2 +#define CORE_STATE_CABLED 3 +#define CORE_STATE_GLASSED 4 +#define CORE_STATE_FINISHED 5 //girder construction states #define GIRDER_NORMAL 0 diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_silicon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_silicon.dm index 564465dfcf2..f68b0840205 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_silicon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_silicon.dm @@ -8,10 +8,6 @@ #define COMSIG_BORG_HUG_HANDLED 1 ///called from /mob/living/silicon/attack_hand proc #define COMSIG_MOB_PAT_BORG "mob_pat_borg" -///called when someone is inquiring about an AI's linked core -#define COMSIG_SILICON_AI_CORE_STATUS "AI_core_status" - #define COMPONENT_CORE_ALL_GOOD (1<<0) - #define COMPONENT_CORE_DISCONNECTED (1<<1) ///called when an AI (malf or perhaps combat upgraded or some other circumstance that has them inhabit ///an APC) enters an APC #define COMSIG_SILICON_AI_OCCUPY_APC "AI_occupy_apc" diff --git a/code/game/machinery/ai_core/_core.dm b/code/game/machinery/ai_core/_core.dm new file mode 100644 index 00000000000..50fb2a9c732 --- /dev/null +++ b/code/game/machinery/ai_core/_core.dm @@ -0,0 +1,247 @@ +#define AI_CORE_BRAIN(X) X.braintype == "Android" ? "brain" : "MMI" + +/obj/structure/ai_core + name = "\improper AI core" + desc = "The framework for an artificial intelligence core." + icon = 'icons/mob/silicon/ai.dmi' + icon_state = "build_0" + base_icon_state = "build_" + density = TRUE + anchored = FALSE + max_integrity = 500 + var/state = CORE_STATE_EMPTY + var/datum/ai_laws/laws + var/obj/item/circuitboard/aicore/circuit + var/obj/item/mmi/core_mmi + +/obj/structure/ai_core/Initialize(mapload, state = src.state, obj/item/mmi/core_mmi = null) + . = ..() + laws = new + laws.set_laws_config() + + if(core_mmi && state < CORE_STATE_CABLED) + stack_trace("supplied a core_mmi as constructor argument, but core state wouldn't have accepted it!") + state = CORE_STATE_FINISHED // just in case... + src.state = state + if(state >= CORE_STATE_CIRCUIT) + circuit = new(src) + if(state >= CORE_STATE_CABLED) + if(!core_mmi) + core_mmi = new /obj/item/mmi(src) + core_mmi.brain = new(core_mmi) + core_mmi.brain.organ_flags |= ORGAN_FROZEN + core_mmi.set_brainmob(new /mob/living/brain()) + core_mmi.brainmob.container = core_mmi + core_mmi.update_appearance() + core_mmi.forceMove(src) + src.core_mmi = core_mmi + set_anchored(TRUE) + + update_appearance(UPDATE_ICON_STATE) + +/obj/structure/ai_core/update_icon_state() + if(state != CORE_STATE_FINISHED) + icon_state = "[base_icon_state][state]" + if(state == CORE_STATE_CABLED && core_mmi) + icon_state += "b" + else + icon_state = "ai-empty" + return ..() + +/obj/structure/ai_core/Exited(atom/movable/gone, direction) + . = ..() + if(gone == circuit) + circuit = null + if((state != CORE_STATE_GLASSED) && (state != CORE_STATE_FINISHED)) + state = CORE_STATE_EMPTY + update_appearance() + if(gone == core_mmi) + core_mmi = null + update_appearance() + +/obj/structure/ai_core/atom_deconstruct(disassembled = TRUE) + if(state >= CORE_STATE_GLASSED) + new /obj/item/stack/sheet/rglass(drop_location(), 2) + if(state >= CORE_STATE_CABLED) + new /obj/item/stack/cable_coil(drop_location(), 5) + core_mmi?.forceMove(drop_location()) + circuit?.forceMove(drop_location()) + new /obj/item/stack/sheet/plasteel(drop_location(), 4) + +/obj/structure/ai_core/Destroy() + QDEL_NULL(circuit) + QDEL_NULL(core_mmi) + QDEL_NULL(laws) + return ..() + +/obj/structure/ai_core/examine(mob/user) + . = ..() + . += span_notice("It has some bolts that look [anchored ? "tightened" : "loosened"].") + + switch(state) + if(CORE_STATE_EMPTY) + . += span_notice("There is a slot for a circuit board, the frame can be melted down.") + if(CORE_STATE_CIRCUIT) + . += span_notice("The circuit board can be screwed into place or pried out.") + if(CORE_STATE_SCREWED) + . += span_notice("The frame can be wired, the circuit board can be unfastened.") + if(CORE_STATE_CABLED) + 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(CORE_STATE_GLASSED) + . += span_notice("The monitor [core_mmi?.brainmob?.mind && !suicide_check() ? "and neural interface " : ""]can be screwed in, the panel can be pried out.") + if(CORE_STATE_FINISHED) + . += span_notice("The monitor's connection can be cut[core_mmi?.brainmob?.mind && !suicide_check() ? " the neural interface can be screwed in." : "."]") + +/obj/structure/ai_core/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(state < CORE_STATE_FINISHED) + return construction_item_interaction(user, tool, modifiers) + + return NONE + +/// Exists to be used for callbacks. +/obj/structure/ai_core/proc/check_state(state_to_check) + return (state == state_to_check) + +/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." + anchored = TRUE + state = CORE_STATE_FINISHED + var/available = TRUE + var/safety_checks = TRUE + var/active = TRUE + +/obj/structure/ai_core/latejoin_inactive/Initialize(mapload, state, posibrain) + . = ..() + GLOB.latejoin_ai_cores += src + +/obj/structure/ai_core/latejoin_inactive/Destroy() + GLOB.latejoin_ai_cores -= src + return ..() + +/obj/structure/ai_core/latejoin_inactive/examine(mob/user) + . = ..() + . += "Its transmitter seems to be [active? "on" : "off"]." + . += span_notice("You could [active? "deactivate" : "activate"] it with a multitool.") + +/obj/structure/ai_core/latejoin_inactive/proc/is_available() //If people still manage to use this feature to spawn-kill AI latejoins ahelp them. + if(!available) + return FALSE + if(!safety_checks) + return TRUE + if(!active) + return FALSE + var/turf/T = get_turf(src) + var/area/A = get_area(src) + if(!(A.area_flags & BLOBS_ALLOWED)) + return FALSE + if(!A.power_equip) + return FALSE + if(!SSmapping.level_trait(T.z,ZTRAIT_STATION)) + return FALSE + if(!isfloorturf(T)) + return FALSE + return TRUE + +/obj/structure/ai_core/latejoin_inactive/multitool_act(mob/living/user, obj/item/tool) + if(user.combat_mode) + return NONE + + if(!tool.use_tool(src, user, 0 SECONDS, 0, 50)) + return ITEM_INTERACT_BLOCKING + + active = !active + balloon_alert(user, "[active ? "activated" : "deactivated"] transmitters") + return ITEM_INTERACT_SUCCESS + +/obj/structure/ai_core/proc/ai_structure_to_mob() + var/mob/living/brain/the_brainmob = core_mmi.brainmob + if(!the_brainmob.mind || suicide_check()) + 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 + + var/datum/antagonist/malf_ai/malf_datum = IS_MALF_AI(ai_mob) + if(malf_datum) + malf_datum.add_law_zero() + + if(!isnull(the_brainmob.client)) + ai_mob.set_gender(the_brainmob.client) + if(core_mmi.force_replace_ai_name) + ai_mob.fully_replace_character_name(ai_mob.name, core_mmi.replacement_ai_name()) + ai_mob.posibrain_inside = core_mmi.braintype == "Android" + 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 ai_mob + +/// Quick proc to call to see if the brainmob inside of us has suicided. Returns TRUE if we have, FALSE in any other scenario. +/obj/structure/ai_core/proc/suicide_check() + if(isnull(core_mmi) || isnull(core_mmi.brainmob)) + return FALSE + return HAS_TRAIT(core_mmi.brainmob, TRAIT_SUICIDED) + +/* +This is a good place for AI-related object verbs so I'm sticking it here. +If adding stuff to this, don't forget that an AI need to cancel_camera() whenever it physically moves to a different location. +That prevents a few funky behaviors. +*/ +//The type of interaction, the player performing the operation, the AI itself, and the card object, if any. + + +/atom/proc/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) + SHOULD_CALL_PARENT(TRUE) + if(istype(card)) + if(card.flush) + to_chat(user, span_alert("ERROR: AI flush is in progress, cannot execute transfer protocol.")) + return FALSE + return TRUE + +/obj/structure/ai_core/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) + if(state != CORE_STATE_FINISHED || !..()) + 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(suicide_check()) + 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.set_control_disabled(FALSE) + AI.radio_enabled = TRUE + AI.forceMove(loc) // to replace the terminal. + to_chat(AI, span_notice("You have been uploaded to a stationary terminal. Remote device connection restored.")) + 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 + AI.posibrain_inside = isnull(core_mmi) || core_mmi.braintype == "Android" + 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.")) + +/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 + +/obj/item/circuitboard/aicore/Initialize(mapload) + . = ..() + if(mapload && HAS_TRAIT(SSstation, STATION_TRAIT_HUMAN_AI)) + return INITIALIZE_HINT_QDEL + +#undef AI_CORE_BRAIN diff --git a/code/game/machinery/ai_core/core_construction.dm b/code/game/machinery/ai_core/core_construction.dm new file mode 100644 index 00000000000..df1a36782da --- /dev/null +++ b/code/game/machinery/ai_core/core_construction.dm @@ -0,0 +1,253 @@ +#define AI_CORE_BRAIN(X) X.braintype == "Android" ? "brain" : "MMI" +#define UPDATE_STATE(new_state) state = new_state; update_appearance(UPDATE_ICON_STATE) +#define CHECK_STATE_CALLBACK(maintained_state) CALLBACK(src, PROC_REF(check_state), maintained_state) + +/obj/structure/ai_core/welder_act(mob/living/user, obj/item/tool) + if(state != CORE_STATE_EMPTY) + balloon_alert(user, "frame has to be empty!") + return ITEM_INTERACT_BLOCKING + + if(!tool.tool_start_check(user, 1)) + return ITEM_INTERACT_BLOCKING + + if(!tool.use_tool(src, user, 2 SECONDS, 1, 50, CHECK_STATE_CALLBACK(CORE_STATE_EMPTY))) + return ITEM_INTERACT_BLOCKING + + deconstruct(TRUE) + return ITEM_INTERACT_SUCCESS + +/obj/structure/ai_core/wrench_act(mob/living/user, obj/item/tool) + if(state >= CORE_STATE_FINISHED) + set_anchored(TRUE) //teehee + balloon_alert(user, "can't unanchor!") + return ITEM_INTERACT_BLOCKING + + default_unfasten_wrench(user, tool) + return ITEM_INTERACT_SUCCESS + +/obj/structure/ai_core/screwdriver_act(mob/living/user, obj/item/tool) + switch(state) + if(CORE_STATE_EMPTY) + balloon_alert(user, "nothing to screw in there!") + return ITEM_INTERACT_BLOCKING + if(CORE_STATE_CIRCUIT) + if(!tool.use_tool(src, user, 0 SECONDS, 0, 50, CHECK_STATE_CALLBACK(CORE_STATE_CIRCUIT))) + return ITEM_INTERACT_BLOCKING + balloon_alert(user, "board secured") + UPDATE_STATE(CORE_STATE_SCREWED) + return ITEM_INTERACT_SUCCESS + if(CORE_STATE_SCREWED) + if(!tool.use_tool(src, user, 0 SECONDS, 0, 50, CHECK_STATE_CALLBACK(CORE_STATE_SCREWED))) + return ITEM_INTERACT_BLOCKING + balloon_alert(user, "board unsecured") + UPDATE_STATE(CORE_STATE_CIRCUIT) + return ITEM_INTERACT_SUCCESS + if(CORE_STATE_CABLED) + balloon_alert(user, "can't reach the board!") + return ITEM_INTERACT_BLOCKING + if(CORE_STATE_GLASSED) + if(!anchored) + balloon_alert(user, "isn't anchored!") + return ITEM_INTERACT_BLOCKING + if(!tool.use_tool(src, user, 0 SECONDS, 0, 50, CHECK_STATE_CALLBACK(CORE_STATE_GLASSED))) + return ITEM_INTERACT_BLOCKING + if(suicide_check()) + balloon_alert(user, "processor is completely useless!") + return ITEM_INTERACT_BLOCKING + + var/atom/movable/alert_source = src + if(core_mmi.brainmob?.mind) + alert_source = ai_structure_to_mob() || alert_source + else + UPDATE_STATE(CORE_STATE_FINISHED) + alert_source.balloon_alert(user, "connected monitor[core_mmi?.brainmob?.mind ? " and neural network" : ""]") + return ITEM_INTERACT_SUCCESS + if(CORE_STATE_FINISHED) + if(!core_mmi?.brainmob?.mind || suicide_check()) + balloon_alert(user, "processor is inactive!") + return ITEM_INTERACT_BLOCKING + + if(!anchored) + balloon_alert(user, "anchor it first!") + return ITEM_INTERACT_BLOCKING + + balloon_alert(user, "connecting neural network...") + if(!tool.use_tool(src, user, 10 SECONDS, 0, 50, CHECK_STATE_CALLBACK(CORE_STATE_FINISHED))) + return ITEM_INTERACT_BLOCKING + + var/atom/movable/alert_source = ai_structure_to_mob() + if(!alert_source) + balloon_alert(user, "processor is inactive!") + return ITEM_INTERACT_BLOCKING + + alert_source.balloon_alert(user, "connected neural network") + return ITEM_INTERACT_SUCCESS + +/obj/structure/ai_core/crowbar_act(mob/living/user, obj/item/tool) + switch(state) + if(CORE_STATE_EMPTY) + balloon_alert(user, "nothing to pry out!") + return ITEM_INTERACT_BLOCKING + if(CORE_STATE_CIRCUIT) + if(!tool.use_tool(src, user, 0 SECONDS, 0, 50, CHECK_STATE_CALLBACK(CORE_STATE_CIRCUIT))) + return ITEM_INTERACT_BLOCKING + + circuit.forceMove(drop_location()) + UPDATE_STATE(CORE_STATE_EMPTY) + return ITEM_INTERACT_SUCCESS + if(CORE_STATE_SCREWED) + balloon_alert(user, "won't budge!") + return ITEM_INTERACT_BLOCKING + if(CORE_STATE_CABLED) + if(!core_mmi) + balloon_alert(user, "nothing to pry out!") + return ITEM_INTERACT_BLOCKING + if(!tool.use_tool(src, user, 0 SECONDS, 0, 50, CHECK_STATE_CALLBACK(CORE_STATE_CABLED)) || !core_mmi) + return ITEM_INTERACT_BLOCKING + + core_mmi.forceMove(drop_location()) + UPDATE_STATE(CORE_STATE_CABLED) + return ITEM_INTERACT_SUCCESS + if(CORE_STATE_GLASSED) + if(!tool.use_tool(src, user, 0 SECONDS, 0, 50, CHECK_STATE_CALLBACK(CORE_STATE_GLASSED))) + return ITEM_INTERACT_BLOCKING + + new /obj/item/stack/sheet/rglass(drop_location(), 2) + UPDATE_STATE(CORE_STATE_CABLED) + return ITEM_INTERACT_SUCCESS + if(CORE_STATE_FINISHED) + balloon_alert(user, "display is on!") + return ITEM_INTERACT_SUCCESS + +/obj/structure/ai_core/wirecutter_act(mob/living/user, obj/item/tool) + switch(state) + if(CORE_STATE_EMPTY to CORE_STATE_CIRCUIT) + balloon_alert(user, "nothing to cut!") + return ITEM_INTERACT_BLOCKING + if(CORE_STATE_CABLED) + if(core_mmi) + balloon_alert(user, "[AI_CORE_BRAIN(core_mmi)] in the way!") + return ITEM_INTERACT_BLOCKING + + if(!tool.use_tool(src, user, 0 SECONDS, 0, 50, CHECK_STATE_CALLBACK(CORE_STATE_CABLED)) || core_mmi) + return ITEM_INTERACT_BLOCKING + + new /obj/item/stack/cable_coil(drop_location(), 5) + UPDATE_STATE(CORE_STATE_SCREWED) + return ITEM_INTERACT_SUCCESS + if(CORE_STATE_GLASSED) + balloon_alert(user, "nothing left to cut!") + return ITEM_INTERACT_BLOCKING + if(CORE_STATE_FINISHED) + if(!tool.use_tool(src, user, 0 SECONDS, 0, 50, CHECK_STATE_CALLBACK(CORE_STATE_FINISHED))) + return ITEM_INTERACT_BLOCKING + + UPDATE_STATE(CORE_STATE_GLASSED) + return ITEM_INTERACT_SUCCESS + +/// Handles the interaction chain the same as item_interaction. Exists to isolate construction behaviour from other item behaviour. +/obj/structure/ai_core/proc/construction_item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(istype(tool, /obj/item/circuitboard/aicore)) + return install_board(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING + + if(istype(tool, /obj/item/stack/cable_coil)) + return add_cabling(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING + + if(istype(tool, /obj/item/mmi)) + return install_mmi(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING + if(istype(tool, /obj/item/ai_module)) + return update_laws(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING + if(istype(tool, /obj/item/stack/sheet/rglass)) + return install_glass(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING + + return NONE + +/obj/structure/ai_core/proc/install_board(mob/living/user, obj/item/circuitboard/aicore/circuit) + if(state != CORE_STATE_EMPTY) + return FALSE + if(!user.transferItemToLoc(circuit, src)) + return FALSE + + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) + src.circuit = circuit + UPDATE_STATE(CORE_STATE_CIRCUIT) + return TRUE + +/obj/structure/ai_core/proc/add_cabling(mob/living/user, obj/item/stack/cable_coil/cable) + if(state != CORE_STATE_SCREWED) + return FALSE + + if(cable.get_amount() < 5) + balloon_alert(user, "not enough [cable::name]!") + return FALSE + + balloon_alert(user, "adding cable...") + if(!cable.use_tool(src, user, 2 SECONDS, 5, 50, CHECK_STATE_CALLBACK(CORE_STATE_SCREWED))) + return FALSE + + UPDATE_STATE(CORE_STATE_CABLED) + return TRUE + +/obj/structure/ai_core/proc/install_mmi(mob/living/user, obj/item/mmi/mmi) + if(state != CORE_STATE_CABLED) + return FALSE + + if(!mmi.brain_check(user)) + var/wants_install = (tgui_alert(user, "This [AI_CORE_BRAIN(mmi)] is inactive, would you like to make an inactive AI?", "Installing AI [AI_CORE_BRAIN(mmi)]", list("Yes", "No")) == "Yes") + if(!wants_install) + return FALSE + if(QDELETED(src) || QDELETED(user) || QDELETED(mmi) || !user.is_holding(mmi) || !Adjacent(user)) + return FALSE + if(mmi.brainmob && HAS_TRAIT(mmi.brainmob, TRAIT_SUICIDED)) + balloon_alert(user, "[AI_CORE_BRAIN(mmi)] is useless!") + return FALSE + else + var/mob/living/brain/mmi_brainmob = mmi.brainmob + if(!CONFIG_GET(flag/allow_ai) || (mmi_brainmob && is_banned_from(mmi_brainmob.ckey, JOB_AI))) + if(!QDELETED(src) && !QDELETED(user) && !QDELETED(mmi) && user.is_holding(mmi) && Adjacent(user)) + balloon_alert(user, "[mmi] won't fit!") + return FALSE + + if(state != CORE_STATE_CABLED) + return FALSE + if(!user.transferItemToLoc(mmi, src)) + return FALSE + + core_mmi = mmi + UPDATE_STATE(CORE_STATE_CABLED) + return TRUE + +/obj/structure/ai_core/proc/update_laws(mob/living/user, obj/item/ai_module/module) + if(!core_mmi) + balloon_alert(user, "no brain installed!") + return FALSE + if(!core_mmi.brainmob || !core_mmi.brainmob?.mind || suicide_check()) + balloon_alert(user, "[AI_CORE_BRAIN(core_mmi)] is inactive!") + return FALSE + if(core_mmi.laws.id != DEFAULT_AI_LAWID) + balloon_alert(user, "[AI_CORE_BRAIN(core_mmi)] already has set laws!") + return FALSE + + module.install(laws, user) + return TRUE + +/obj/structure/ai_core/proc/install_glass(mob/living/user, obj/item/stack/sheet/rglass/glass) + if(state != CORE_STATE_CABLED) + return FALSE + + if(!core_mmi) + balloon_alert(user, "needs a processor!") + return FALSE + if(glass.get_amount() < 2) + balloon_alert(user, "not enough [glass::name]!") + return FALSE + + if(!glass.use_tool(src, user, 2 SECONDS, 2, 50, CHECK_STATE_CALLBACK(CORE_STATE_CABLED)) || !core_mmi) + return FALSE + + UPDATE_STATE(CORE_STATE_GLASSED) + return TRUE + +#undef CHECK_STATE_CALLBACK +#undef UPDATE_STATE +#undef AI_CORE_BRAIN diff --git a/code/game/objects/items/devices/aicard_evil.dm b/code/game/objects/items/devices/aicard_evil.dm index b63ecf25870..0c9f0c6f9c4 100644 --- a/code/game/objects/items/devices/aicard_evil.dm +++ b/code/game/objects/items/devices/aicard_evil.dm @@ -69,7 +69,7 @@ // Transfer the AI from the core we created into the card, then delete the core capture_ai(new_ai, user) - var/obj/structure/ai_core/deactivated/detritus = locate() in get_turf(src) + var/obj/structure/ai_core/detritus = locate() in get_turf(src) qdel(detritus) AI.set_control_disabled(FALSE) AI.radio_enabled = TRUE diff --git a/code/game/objects/structures/ai_core.dm b/code/game/objects/structures/ai_core.dm deleted file mode 100644 index 6eee27ce24e..00000000000 --- a/code/game/objects/structures/ai_core.dm +++ /dev/null @@ -1,484 +0,0 @@ -#define AI_CORE_BRAIN(X) X.braintype == "Android" ? "brain" : "MMI" - -/obj/structure/ai_core - density = TRUE - anchored = FALSE - name = "\improper AI core" - icon = 'icons/mob/silicon/ai.dmi' - icon_state = "0" - desc = "The framework for an artificial intelligence core." - max_integrity = 500 - var/state = EMPTY_CORE - var/datum/ai_laws/laws - var/obj/item/circuitboard/aicore/circuit - var/obj/item/mmi/core_mmi - /// only used in cases of AIs piloting mechs or shunted malf AIs, possible later use cases - var/mob/living/silicon/ai/remote_ai = null - -/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 && !suicide_check() ? "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 && !suicide_check() ? " the neural interface can be screwed in." : "."]") - -/obj/structure/ai_core/Exited(atom/movable/gone, direction) - . = ..() - if(gone == circuit) - circuit = null - if((state != GLASS_CORE) && (state != AI_READY_CORE)) - state = EMPTY_CORE - update_appearance() - if(gone == core_mmi) - core_mmi = null - update_appearance() - -/obj/structure/ai_core/Destroy() - if(istype(remote_ai)) - remote_ai = null - QDEL_NULL(circuit) - QDEL_NULL(core_mmi) - QDEL_NULL(laws) - return ..() - -/obj/structure/ai_core/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration) - . = ..() - if(. > 0 && istype(remote_ai)) - to_chat(remote_ai, span_danger("Your core is under attack!")) - - -/obj/structure/ai_core/deactivated - icon_state = "ai-empty" - anchored = TRUE - state = AI_READY_CORE - var/mob/living/silicon/ai/attached_ai - -/obj/structure/ai_core/deactivated/Initialize(mapload, skip_mmi_creation = FALSE, posibrain = FALSE, linked_ai) - . = ..() - circuit = new(src) - if(linked_ai) - attached_ai = linked_ai - if(skip_mmi_creation) - return - if(posibrain) - core_mmi = new/obj/item/mmi/posibrain(src, /* autoping = */ FALSE) - else - core_mmi = new(src) - core_mmi.brain = new(core_mmi) - core_mmi.update_appearance() - -/obj/structure/ai_core/deactivated/Destroy() - if(attached_ai) - attached_ai.linked_core = null - attached_ai = null - . = ..() - -/obj/structure/ai_core/deactivated/proc/disable_doomsday(datum/source) - SIGNAL_HANDLER - attached_ai.ShutOffDoomsdayDevice() - -/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." - icon_state = "ai-empty" - anchored = TRUE - state = AI_READY_CORE - var/available = TRUE - var/safety_checks = TRUE - var/active = TRUE - -/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() - GLOB.latejoin_ai_cores -= src - return ..() - -/obj/structure/ai_core/latejoin_inactive/examine(mob/user) - . = ..() - . += "Its transmitter seems to be [active? "on" : "off"]." - . += span_notice("You could [active? "deactivate" : "activate"] it with a multitool.") - -/obj/structure/ai_core/latejoin_inactive/proc/is_available() //If people still manage to use this feature to spawn-kill AI latejoins ahelp them. - if(!available) - return FALSE - if(!safety_checks) - return TRUE - if(!active) - return FALSE - var/turf/T = get_turf(src) - var/area/A = get_area(src) - if(!(A.area_flags & BLOBS_ALLOWED)) - return FALSE - if(!A.power_equip) - return FALSE - if(!SSmapping.level_trait(T.z,ZTRAIT_STATION)) - return FALSE - if(!isfloorturf(T)) - return FALSE - return TRUE - -/obj/structure/ai_core/latejoin_inactive/attackby(obj/item/tool, mob/user, list/modifiers, list/attack_modifiers) - if(tool.tool_behaviour == TOOL_MULTITOOL) - active = !active - to_chat(user, span_notice("You [active? "activate" : "deactivate"] \the [src]'s transmitters.")) - return - return ..() - -/obj/structure/ai_core/wrench_act(mob/living/user, obj/item/tool) - . = ..() - default_unfasten_wrench(user, tool) - return ITEM_INTERACT_SUCCESS - -/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 ITEM_INTERACT_SUCCESS - else if(!core_mmi.brainmob?.mind || suicide_check()) - balloon_alert(user, "brain is inactive!") - return ITEM_INTERACT_SUCCESS - else - balloon_alert(user, "connecting neural network...") - if(!tool.use_tool(src, user, 10 SECONDS)) - return ITEM_INTERACT_SUCCESS - if(!ai_structure_to_mob()) - return ITEM_INTERACT_SUCCESS - balloon_alert(user, "connected neural network") - return ITEM_INTERACT_SUCCESS - -/obj/structure/ai_core/attackby(obj/item/tool, mob/living/user, list/modifiers, list/attack_modifiers) - if(remote_ai) - to_chat(remote_ai, span_danger("CORE TAMPERING DETECTED!")) - if(!anchored) - if(tool.tool_behaviour == TOOL_WELDER) - if(state != EMPTY_CORE) - balloon_alert(user, "core must be empty to deconstruct it!") - return - - if(!tool.tool_start_check(user, amount=1)) - return - - balloon_alert(user, "deconstructing frame...") - if(tool.use_tool(src, user, 20, volume=50) && state == EMPTY_CORE) - 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) - if(istype(tool, /obj/item/circuitboard/aicore)) - if(!user.transferItemToLoc(tool, src)) - return - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - balloon_alert(user, "circuit board inserted") - update_appearance() - state = CIRCUIT_CORE - circuit = tool - return - if(CIRCUIT_CORE) - if(tool.tool_behaviour == TOOL_SCREWDRIVER) - tool.play_tool_sound(src) - balloon_alert(user, "board screwed into place") - state = SCREWED_CORE - update_appearance() - return - if(tool.tool_behaviour == TOOL_CROWBAR) - tool.play_tool_sound(src) - balloon_alert(user, "circuit board removed") - state = EMPTY_CORE - circuit.forceMove(loc) - return - if(SCREWED_CORE) - if(tool.tool_behaviour == TOOL_SCREWDRIVER && circuit) - tool.play_tool_sound(src) - balloon_alert(user, "circuit board unfastened") - state = CIRCUIT_CORE - update_appearance() - return - if(istype(tool, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = tool - if(C.get_amount() >= 5) - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - balloon_alert(user, "adding cables to frame...") - if(do_after(user, 2 SECONDS, target = src) && state == SCREWED_CORE && C.use(5)) - balloon_alert(user, "added cables to frame.") - state = CABLED_CORE - update_appearance() - else - balloon_alert(user, "need five lengths of cable!") - return - if(CABLED_CORE) - if(tool.tool_behaviour == TOOL_WIRECUTTER) - if(core_mmi) - balloon_alert(user, "remove the [AI_CORE_BRAIN(core_mmi)] first!") - else - tool.play_tool_sound(src) - balloon_alert(user, "cables removed") - state = SCREWED_CORE - update_appearance() - new /obj/item/stack/cable_coil(drop_location(), 5) - return - - if(istype(tool, /obj/item/stack/sheet/rglass)) - if(!core_mmi) - balloon_alert(user, "add a brain first!") - return - var/obj/item/stack/sheet/rglass/G = tool - if(G.get_amount() >= 2) - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - balloon_alert(user, "adding glass panel...") - if(do_after(user, 2 SECONDS, target = src) && state == CABLED_CORE && G.use(2)) - balloon_alert(user, "added glass panel") - state = GLASS_CORE - update_appearance() - else - balloon_alert(user, "need two sheets of reinforced glass!") - return - - if(istype(tool, /obj/item/ai_module)) - if(!core_mmi) - balloon_alert(user, "no brain installed!") - return - if(!core_mmi.brainmob || !core_mmi.brainmob?.mind || suicide_check()) - 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 = tool - module.install(laws, user) - return - - if(istype(tool, /obj/item/mmi) && !core_mmi) - var/obj/item/mmi/M = tool - 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 && HAS_TRAIT(M.brainmob, TRAIT_SUICIDED)) - 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 - if(!CONFIG_GET(flag/allow_ai) || (is_banned_from(B.ckey, JOB_AI) && !QDELETED(src) && !QDELETED(user) && !QDELETED(M) && !QDELETED(user) && Adjacent(user))) - if(!QDELETED(M)) - to_chat(user, span_warning("This [M.name] does not seem to fit!")) - return - if(!user.transferItemToLoc(M,src)) - return - - core_mmi = M - balloon_alert(user, "added [AI_CORE_BRAIN(core_mmi)] to frame") - update_appearance() - return - - if(tool.tool_behaviour == TOOL_CROWBAR && core_mmi) - tool.play_tool_sound(src) - balloon_alert(user, "removed [AI_CORE_BRAIN(core_mmi)]") - if(remote_ai) - var/mob/living/silicon/ai/remoted_ai = remote_ai - remoted_ai.break_core_link() - if(!IS_MALF_AI(remoted_ai)) - //don't pull back shunted malf AIs - remoted_ai.death(gibbed = TRUE, drop_mmi = FALSE) - ///the drop_mmi param determines whether the MMI is dropped at their current location - ///which in this case would be somewhere else, so we drop their MMI at the core instead - remoted_ai.make_mmi_drop_and_transfer(core_mmi, src) - core_mmi.forceMove(loc) //if they're malf, just drops a blank MMI, or if it's an incomplete shell - return //it drops the mmi that was put in before it was finished - - if(GLASS_CORE) - if(tool.tool_behaviour == TOOL_CROWBAR) - tool.play_tool_sound(src) - balloon_alert(user, "removed glass panel") - state = CABLED_CORE - update_appearance() - new /obj/item/stack/sheet/rglass(loc, 2) - return - - if(tool.tool_behaviour == TOOL_SCREWDRIVER) - if(suicide_check()) - to_chat(user, span_warning("The brain installed is completely useless.")) - return - tool.play_tool_sound(src) - - var/atom/alert_source = src - if(core_mmi.brainmob?.mind) - alert_source = ai_structure_to_mob() || alert_source - else - state = AI_READY_CORE - update_appearance() - alert_source.balloon_alert(user, "connected monitor[core_mmi?.brainmob?.mind ? " and neural network" : ""]") - return - - if(AI_READY_CORE) - if(istype(tool, /obj/item/aicard)) - return //handled by /obj/structure/ai_core/transfer_ai() - - if(tool.tool_behaviour == TOOL_WIRECUTTER) - tool.play_tool_sound(src) - 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 || suicide_check()) - 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 - - var/datum/antagonist/malf_ai/malf_datum = IS_MALF_AI(ai_mob) - if(malf_datum) - malf_datum.add_law_zero() - - if(!isnull(the_brainmob.client)) - ai_mob.set_gender(the_brainmob.client) - if(core_mmi.force_replace_ai_name) - ai_mob.fully_replace_character_name(ai_mob.name, core_mmi.replacement_ai_name()) - ai_mob.posibrain_inside = core_mmi.braintype == "Android" - 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 ai_mob - -/obj/structure/ai_core/update_icon_state() - switch(state) - if(EMPTY_CORE) - icon_state = "0" - if(CIRCUIT_CORE) - icon_state = "1" - if(SCREWED_CORE) - icon_state = "2" - if(CABLED_CORE) - if(core_mmi) - icon_state = "3b" - else - icon_state = "3" - if(GLASS_CORE) - icon_state = "4" - if(AI_READY_CORE) - icon_state = "ai-empty" - return ..() - -/obj/structure/ai_core/atom_deconstruct(disassembled = TRUE) - if(state >= GLASS_CORE) - new /obj/item/stack/sheet/rglass(loc, 2) - if(state >= CABLED_CORE) - new /obj/item/stack/cable_coil(loc, 5) - if(circuit) - circuit.forceMove(loc) - circuit = null - new /obj/item/stack/sheet/plasteel(loc, 4) - -/// Quick proc to call to see if the brainmob inside of us has suicided. Returns TRUE if we have, FALSE in any other scenario. -/obj/structure/ai_core/proc/suicide_check() - if(isnull(core_mmi) || isnull(core_mmi.brainmob)) - return FALSE - return HAS_TRAIT(core_mmi.brainmob, TRAIT_SUICIDED) - -/* -This is a good place for AI-related object verbs so I'm sticking it here. -If adding stuff to this, don't forget that an AI need to cancel_camera() whenever it physically moves to a different location. -That prevents a few funky behaviors. -*/ -//The type of interaction, the player performing the operation, the AI itself, and the card object, if any. - - -/atom/proc/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) - SHOULD_CALL_PARENT(TRUE) - if(istype(card)) - if(card.flush) - to_chat(user, span_alert("ERROR: AI flush is in progress, cannot execute transfer protocol.")) - return FALSE - return TRUE - -/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(suicide_check()) - 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.set_control_disabled(FALSE) - AI.radio_enabled = TRUE - AI.forceMove(loc) // to replace the terminal. - to_chat(AI, span_notice("You have been uploaded to a stationary terminal. Remote device connection restored.")) - 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 - AI.posibrain_inside = isnull(core_mmi) || core_mmi.braintype == "Android" - 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.")) - -/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 - -/obj/item/circuitboard/aicore/Initialize(mapload) - . = ..() - if(mapload && HAS_TRAIT(SSstation, STATION_TRAIT_HUMAN_AI)) - return INITIALIZE_HINT_QDEL - -#undef AI_CORE_BRAIN diff --git a/code/modules/antagonists/malf_ai/malf_ai.dm b/code/modules/antagonists/malf_ai/malf_ai.dm index f448670eeda..d35a00f8b7c 100644 --- a/code/modules/antagonists/malf_ai/malf_ai.dm +++ b/code/modules/antagonists/malf_ai/malf_ai.dm @@ -40,7 +40,6 @@ malfunction_flavor = strings(MALFUNCTION_FLAVOR_FILE, employer) add_law_zero() - RegisterSignal(owner.current, COMSIG_SILICON_AI_CORE_STATUS, PROC_REF(core_status)) if(malf_sound) owner.current.playsound_local(get_turf(owner.current), malf_sound, 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) owner.current.grant_language(/datum/language/codespeak, source = LANGUAGE_MALF) @@ -57,7 +56,6 @@ malf_ai.remove_malf_abilities() QDEL_NULL(malf_ai.malf_picker) - UnregisterSignal(owner, COMSIG_SILICON_AI_CORE_STATUS) return ..() /// Generates a complete set of malf AI objectives up to the traitor objective limit. @@ -271,14 +269,6 @@ return malf_ai_icon -/datum/antagonist/malf_ai/proc/core_status(datum/source) - SIGNAL_HANDLER - - var/mob/living/silicon/ai/malf_owner = owner.current - if(malf_owner?.linked_core) - return COMPONENT_CORE_ALL_GOOD - return COMPONENT_CORE_DISCONNECTED - //Subtype of Malf AI datum, used for one of the traitor final objectives /datum/antagonist/malf_ai/infected name = "Infected AI" diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 329a5f29823..67aa473c47e 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -5,7 +5,7 @@ /mob/living/silicon/ai/Initialize(mapload, datum/ai_laws/L, mob/target_ai) . = ..() if(!target_ai) //If there is no player/brain inside. - new/obj/structure/ai_core/deactivated(loc) //New empty terminal. + new/obj/structure/ai_core(loc, CORE_STATE_FINISHED) //New empty terminal. return INITIALIZE_HINT_QDEL //Delete AI. ADD_TRAIT(src, TRAIT_NO_TELEPORT, AI_ANCHOR_TRAIT) @@ -152,7 +152,7 @@ current = null bot_ref = null controlled_equipment = null - linked_core = null + break_core_link() apc_override = null if(ai_voicechanger) ai_voicechanger.owner = null @@ -363,53 +363,45 @@ status_flags &= ~CANPUSH //we dont want the core to be push-able when anchored ADD_TRAIT(src, TRAIT_NO_TELEPORT, AI_ANCHOR_TRAIT) +/// Creates an MMI of the AI based on its configuration. +/mob/living/silicon/ai/proc/make_mmi(atom/destination) as /obj/item/mmi + RETURN_TYPE(/obj/item/mmi) + //FIXME: this code is really bad, we shouldn't be doing most of this ourselves. MMI code needs a good refactoring.... + var/obj/item/mmi/copied_mmi + if(posibrain_inside) + copied_mmi = new /obj/item/mmi/posibrain(destination, FALSE) + copied_mmi.name = "[initial(copied_mmi.name)] ([real_name])" + else + copied_mmi = new /obj/item/mmi(destination) + copied_mmi.name = "[initial(copied_mmi.name)]: [real_name]" + copied_mmi.brain = new /obj/item/organ/brain(copied_mmi) + copied_mmi.brain.organ_flags |= ORGAN_FROZEN + copied_mmi.brain.name = "[real_name]'s brain" + copied_mmi.set_brainmob(new /mob/living/brain(copied_mmi)) + copied_mmi.brainmob.container = copied_mmi + + copied_mmi.brainmob.name = real_name + copied_mmi.brainmob.real_name = real_name + copied_mmi.brainmob.gender = gender + + var/suicided = HAS_TRAIT(src, TRAIT_SUICIDED) + copied_mmi.brainmob.set_suicide(suicided) + copied_mmi.brain?.suicided = suicided // we can't guarantee that the MMI has a brain... sigh + + if(copied_mmi.brainmob.stat == DEAD && !suicided) + copied_mmi.brainmob.set_stat(CONSCIOUS) + + copied_mmi.update_appearance() + return copied_mmi + /mob/living/silicon/ai/proc/ai_mob_to_structure() disconnect_shell() ShutOffDoomsdayDevice() - var/obj/structure/ai_core/deactivated/ai_core = new(get_turf(src), /* skip_mmi_creation = */ TRUE) - if(make_mmi_drop_and_transfer(ai_core.core_mmi, the_core = ai_core)) - qdel(src) + var/obj/structure/ai_core/ai_core = new(get_turf(src), CORE_STATE_FINISHED, make_mmi()) + mind?.transfer_to(ai_core.core_mmi.brainmob) + qdel(src) return ai_core -/mob/living/silicon/ai/proc/break_core_link() - to_chat(src, span_danger("Your core has been destroyed!")) - linked_core = null - -/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, /* autoping = */ FALSE) - 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/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.gender = src.gender - - var/has_suicided_trait = HAS_TRAIT(src, TRAIT_SUICIDED) - the_mmi.brainmob.set_suicide(has_suicided_trait) - the_mmi.brain.suicided = has_suicided_trait - 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 && !has_suicided_trait) - 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) ..() if(usr != src) @@ -702,7 +694,7 @@ if(!istype(apc)) to_chat(owner, span_notice("You are already in your Main Core.")) return - if(SEND_SIGNAL(owner, COMSIG_SILICON_AI_CORE_STATUS) & COMPONENT_CORE_ALL_GOOD) + if(astype(owner, /mob/living/silicon/ai)?.linked_core) apc.malfvacate() else to_chat(owner, span_danger("Linked core not detected!")) @@ -786,7 +778,7 @@ balloon_alert(user, "no intelligence detected!") // average tg coder am i right return ShutOffDoomsdayDevice() - var/obj/structure/ai_core/new_core = new /obj/structure/ai_core/deactivated(loc, posibrain_inside)//Spawns a deactivated terminal at AI location. + var/obj/structure/ai_core/new_core = new /obj/structure/ai_core(loc, CORE_STATE_FINISHED, make_mmi()) new_core.circuit.battery = battery ai_restore_power()//So the AI initially has power. set_control_disabled(TRUE) //Can't control things remotely if you're stuck in a card! @@ -1109,6 +1101,99 @@ SEND_SIGNAL(src, COMSIG_SILICON_AI_SET_CONTROL_DISABLED, control_disabled) src.control_disabled = control_disabled + +/// Establishes a "core link" with a supplied core structure. +/// This will register multiple signals and give the AI a strong reference to it. +/// See [proc/resolve_core_link] or [proc/break_core_link] for ways to end the connection. +/mob/living/silicon/ai/proc/create_core_link(obj/structure/ai_core/core) + if(linked_core) //uh oh + break_core_link(linked_core) + linked_core = core + + //this block is kind of sketchy, but I don't think this should cause any problems + qdel(core.core_mmi) + core.core_mmi = make_mmi(core) + + RegisterSignals(linked_core, list(COMSIG_ATOM_DESTRUCTION, COMSIG_QDELETING), PROC_REF(on_core_destroyed)) + RegisterSignals(linked_core, list( + COMSIG_ATOM_ITEM_INTERACTION, + COMSIG_ATOM_TOOL_ACT(TOOL_CROWBAR), + COMSIG_ATOM_TOOL_ACT(TOOL_WRENCH), + COMSIG_ATOM_TOOL_ACT(TOOL_WELDER), + COMSIG_ATOM_TOOL_ACT(TOOL_WIRECUTTER), + COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), + ), PROC_REF(on_core_item_interaction)) + RegisterSignal(linked_core, COMSIG_ATOM_TAKE_DAMAGE, PROC_REF(on_core_take_damage)) + RegisterSignal(linked_core, COMSIG_ATOM_EXITED, PROC_REF(on_core_exited)) + +/// Elegantly closes the AI's link to a core structure, +/// moving them to its location and cleaning it up. This is generally what you want to call. +/// Prefer calling [proc/break_core_link] directly if the connection is meant to be suddenly severed. +/mob/living/silicon/ai/proc/resolve_core_link() + if(!linked_core) //oh no bro + CRASH("tried to resolve a core link with no core!!!!") + + forceMove(linked_core.loc) + var/obj/structure/ai_core/unlinked_core = linked_core + break_core_link() + qdel(unlinked_core) + cancel_camera() + +/// Handles unregistering the AI from its core. The core itself will not be cleaned up. +/// Prefer calling [proc/resolve_core_link] if the connection is being closed elegantly. +/mob/living/silicon/ai/proc/break_core_link() + if(!linked_core) + return + + UnregisterSignal(linked_core, list( + COMSIG_QDELETING, COMSIG_ATOM_DESTRUCTION, //on_core_destroyed + //on_core_item_interaction + COMSIG_ATOM_ITEM_INTERACTION, + COMSIG_ATOM_TOOL_ACT(TOOL_CROWBAR), + COMSIG_ATOM_TOOL_ACT(TOOL_WRENCH), + COMSIG_ATOM_TOOL_ACT(TOOL_WELDER), + COMSIG_ATOM_TOOL_ACT(TOOL_WIRECUTTER), + COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), + COMSIG_ATOM_TAKE_DAMAGE, //on_core_take_damage + COMSIG_ATOM_EXITED, //on_core_exited + )) + linked_core = null + +/mob/living/silicon/ai/proc/on_core_item_interaction(datum/source, mob/living/user, obj/item/tool, list/processing_recipes) + SIGNAL_HANDLER + if(user.combat_mode) + return NONE + + to_chat(src, span_danger("CORE TAMPERING DETECTED!")) + return NONE + +/mob/living/silicon/ai/proc/on_core_take_damage(datum/source, damage_taken, ...) + SIGNAL_HANDLER + + if(damage_taken > 0) + to_chat(src, span_danger("CORE DAMAGE DETECTED!")) + return NONE + +/mob/living/silicon/ai/proc/on_core_destroyed(datum/source, damage_flag) + SIGNAL_HANDLER + + to_chat(src, span_danger("Your core has been destroyed!")) + ShutOffDoomsdayDevice() + break_core_link() + +/mob/living/silicon/ai/proc/on_core_exited(datum/source, atom/movable/gone, direction) + SIGNAL_HANDLER + + if(istype(gone, /obj/item/mmi)) + var/obj/item/mmi/mmi_gone = gone + on_core_destroyed(source, NONE) + if(!IS_MALF_AI(src)) //don't pull back shunted malf AIs + death(gibbed = TRUE, drop_mmi = FALSE) + ///the drop_mmi param determines whether the MMI is dropped at their current location + ///which in this case would be somewhere else, so we drop their MMI at the core instead + mind?.transfer_to(mmi_gone.brainmob) + qdel(src) + #undef HOLOGRAM_CHOICE_CHARACTER #undef CHARACTER_TYPE_SELF #undef CHARACTER_TYPE_CREWMEMBER diff --git a/code/modules/mob/living/silicon/ai/ai_defines.dm b/code/modules/mob/living/silicon/ai/ai_defines.dm index de631e7b867..30201c9e703 100644 --- a/code/modules/mob/living/silicon/ai/ai_defines.dm +++ b/code/modules/mob/living/silicon/ai/ai_defines.dm @@ -122,8 +122,8 @@ /* REMOTE CONTROL */ /// Equipment that the AI is controlling remotely, to determine whether to relaymove or use the AI eye VAR_FINAL/obj/controlled_equipment - /// AI core that this AI is linked to, used when put into an exosuit - VAR_FINAL/obj/structure/ai_core/deactivated/linked_core + /// AI core that this AI is linked to. See [proc/create_core_link] [proc/resolve_core_link] [proc/break_core_link] + VAR_FINAL/obj/structure/ai_core/linked_core /// Robot that this AI is currently using VAR_FINAL/mob/living/silicon/robot/deployed_shell /// Action to deploy to a shell from a list of options diff --git a/code/modules/mob/living/silicon/ai/death.dm b/code/modules/mob/living/silicon/ai/death.dm index aad3df39d80..6f76f3294a9 100644 --- a/code/modules/mob/living/silicon/ai/death.dm +++ b/code/modules/mob/living/silicon/ai/death.dm @@ -34,7 +34,8 @@ ShutOffDoomsdayDevice() if(gibbed && drop_mmi) - make_mmi_drop_and_transfer() + var/obj/item/mmi/loose_cpu = make_mmi(get_turf(src)) + mind?.transfer_to(loose_cpu.brainmob) if(explodes_on_death) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(explosion), loc, 3, 6, 12, null, 15), 1 SECONDS) @@ -42,7 +43,5 @@ SSblackbox.ReportDeath(src) /mob/living/silicon/ai/proc/ShutOffDoomsdayDevice() - if(nuking) - nuking = FALSE - if(doomsday_device) - qdel(doomsday_device) + nuking = FALSE + QDEL_NULL(doomsday_device) diff --git a/code/modules/power/apc/apc_malf.dm b/code/modules/power/apc/apc_malf.dm index c4cafb9d5e7..eb0526e32ca 100644 --- a/code/modules/power/apc/apc_malf.dm +++ b/code/modules/power/apc/apc_malf.dm @@ -45,8 +45,7 @@ malf.ShutOffDoomsdayDevice() occupier = malf if (isturf(malf.loc)) // create a deactivated AI core if the AI isn't coming from an emergency mech shunt - malf.linked_core = new /obj/structure/ai_core/deactivated(malf.loc) - malf.linked_core.remote_ai = malf // note that we do not set the deactivated core's core_mmi.brainmob + malf.create_core_link(new /obj/structure/ai_core(malf.loc, CORE_STATE_FINISHED, malf.make_mmi())) malf.forceMove(src) // move INTO the APC, not to its tile if(!findtext(occupier.name, "APC Copy")) occupier.name = "[malf.name] APC Copy" @@ -74,9 +73,7 @@ return if(occupier.linked_core) occupier.shunted = FALSE - occupier.forceMove(occupier.linked_core.loc) - qdel(occupier.linked_core) - occupier.cancel_camera() + occupier.resolve_core_link() occupier = null else stack_trace("An AI: [occupier] has vacated an APC with no linked core and without being gibbed.") diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm index b666a013479..44690288175 100644 --- a/code/modules/vehicles/mecha/_mecha.dm +++ b/code/modules/vehicles/mecha/_mecha.dm @@ -332,6 +332,7 @@ var/mob/living/silicon/ai/unlucky_ai for(var/mob/living/occupant as anything in occupants) if(isAI(occupant)) + //FIXME: Nothiing about this block works var/mob/living/silicon/ai/ai = occupant if(!ai.linked_core && !ai.can_shunt) // we probably shouldnt gib AIs with a core or shunting abilities unlucky_ai = occupant diff --git a/code/modules/vehicles/mecha/mecha_ai_interaction.dm b/code/modules/vehicles/mecha/mecha_ai_interaction.dm index 02bc2c89953..7a13e423bb4 100644 --- a/code/modules/vehicles/mecha/mecha_ai_interaction.dm +++ b/code/modules/vehicles/mecha/mecha_ai_interaction.dm @@ -78,10 +78,7 @@ return if(AI_MECH_HACK) //Called by AIs on the mech - var/obj/structure/ai_core/deactivated/deactivated_core = new(AI.loc, FALSE, FALSE, AI) - AI.linked_core = deactivated_core - AI.linked_core.RegisterSignal(deactivated_core, COMSIG_ATOM_DESTRUCTION, TYPE_PROC_REF(/obj/structure/ai_core/deactivated, disable_doomsday)) //Protect that core! The structure goes bye-bye when we re-shunt back in so no need for cleanup. - AI.linked_core.remote_ai = AI + AI.create_core_link(new /obj/structure/ai_core(AI.loc, CORE_STATE_FINISHED, AI.make_mmi())) if(AI.can_dominate_mechs && LAZYLEN(occupants)) //Oh, I am sorry, were you using that? to_chat(AI, span_warning("Occupants detected! Forced ejection initiated!")) to_chat(occupants, span_danger("You have been forcibly ejected!")) diff --git a/code/modules/vehicles/mecha/mecha_mob_interaction.dm b/code/modules/vehicles/mecha/mecha_mob_interaction.dm index f5bf2fed81c..57250ecb437 100644 --- a/code/modules/vehicles/mecha/mecha_mob_interaction.dm +++ b/code/modules/vehicles/mecha/mecha_mob_interaction.dm @@ -111,7 +111,9 @@ brain_mob.log_message("was put into [src] by [key_name(user)]", LOG_GAME, log_globally = FALSE) return TRUE + /obj/vehicle/sealed/mecha/mob_exit(mob/M, silent = FALSE, randomstep = FALSE, forced = FALSE) + // FIXME: this code is really bad (shocker). Needs a refactor var/atom/movable/mob_container var/turf/newloc = get_turf(src) if(ishuman(M)) @@ -147,9 +149,7 @@ if(!forced && !silent) to_chat(AI, span_notice("Returning to core...")) mecha_flags &= ~SILICON_PILOT - newloc = get_turf(AI.linked_core) - qdel(AI.linked_core) - AI.forceMove(newloc) + AI.resolve_core_link() if(forced) to_chat(AI, span_danger("ZZUZULU.ERR--ERRR-NEUROLOG-- PERCEP--- DIST-B**@")) for(var/count in 1 to 5) diff --git a/icons/mob/silicon/ai.dmi b/icons/mob/silicon/ai.dmi index df9059fb09e..be9060ecd55 100644 Binary files a/icons/mob/silicon/ai.dmi and b/icons/mob/silicon/ai.dmi differ diff --git a/tgstation.dme b/tgstation.dme index f4204593f57..e14e3913392 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2251,6 +2251,8 @@ #include "code\game\machinery\wall_healer.dm" #include "code\game\machinery\washing_machine.dm" #include "code\game\machinery\wishgranter.dm" +#include "code\game\machinery\ai_core\_core.dm" +#include "code\game\machinery\ai_core\core_construction.dm" #include "code\game\machinery\camera\camera.dm" #include "code\game\machinery\camera\camera_construction.dm" #include "code\game\machinery\camera\motion.dm" @@ -2869,7 +2871,6 @@ #include "code\game\objects\items\tools\painter\airlock_painter.dm" #include "code\game\objects\items\tools\painter\decal_painter.dm" #include "code\game\objects\items\tools\painter\paintable_decals.dm" -#include "code\game\objects\structures\ai_core.dm" #include "code\game\objects\structures\aliens.dm" #include "code\game\objects\structures\bedsheet_bin.dm" #include "code\game\objects\structures\billboard.dm" diff --git a/tools/UpdatePaths/Scripts/93873_aicore_refactor.txt b/tools/UpdatePaths/Scripts/93873_aicore_refactor.txt new file mode 100644 index 00000000000..aa15c612a0f --- /dev/null +++ b/tools/UpdatePaths/Scripts/93873_aicore_refactor.txt @@ -0,0 +1 @@ +/obj/structure/ai_core/deactivated/@SUBTYPES : /obj/structure/ai_core/@SUBTYPES{@OLD;state=5}