[MDB IGNORE] Cleans up AI core structure, un-spaghettis some AI code (#93873)

## About The Pull Request

The AI core structure now fully integrates item/tool interactions and no
longer uses attackby/take_damage overrides. Construction behaviour has
been isolated into its own file.

All handling relating to the AI core's status when shunted has been
turned into signals. For this reason, helper procs for managing core
link state have been made and integrated into the relevant users (APC
shunting, mech takeover). The AI core no longer references the remote AI
mob.

/obj/structure/ai_core/deactivated has been removed from code since it
had no function after refactoring. There was only a single instance of
it being used in a map, which has been replaced with a var edit.

There may be more things I changed, but I'm h
This commit is contained in:
tonty
2025-12-02 12:38:51 -06:00
committed by GitHub
parent 17b13d0aa8
commit 8c45d4466c
18 changed files with 657 additions and 572 deletions
+3 -1
View File
@@ -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" = (
+6 -6
View File
@@ -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
@@ -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"
+247
View File
@@ -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 <b>bolts</b> that look [anchored ? "tightened" : "loosened"].")
switch(state)
if(CORE_STATE_EMPTY)
. += span_notice("There is a <b>slot</b> for a circuit board, the frame can be <b>melted</b> down.")
if(CORE_STATE_CIRCUIT)
. += span_notice("The circuit board can be <b>screwed</b> into place or <b>pried</b> out.")
if(CORE_STATE_SCREWED)
. += span_notice("The frame can be <b>wired</b>, the circuit board can be <b>unfastened</b>.")
if(CORE_STATE_CABLED)
if(!core_mmi)
. += span_notice("There are wires which could be hooked up to an <b>MMI or positronic brain</b>, or <b>cut</b>.")
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 <b>slot</b> for a reinforced glass panel, the [AI_CORE_BRAIN(core_mmi)] could be <b>pried</b> out.[accept_laws ? " A law module can be <b>swiped</b> across." : ""]")
if(CORE_STATE_GLASSED)
. += span_notice("The monitor [core_mmi?.brainmob?.mind && !suicide_check() ? "and neural interface " : ""]can be <b>screwed</b> in, the panel can be <b>pried</b> out.")
if(CORE_STATE_FINISHED)
. += span_notice("The monitor's connection can be <b>cut</b>[core_mmi?.brainmob?.mind && !suicide_check() ? " the neural interface can be <b>screwed</b> 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 <b>[active? "on" : "off"]</b>."
. += 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 <b>[get_area_name(ai_mob, format_text = TRUE)]</b>.", 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
@@ -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
@@ -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
-484
View File
@@ -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 <b>bolts</b> that could be tightened.")
else
. += span_notice("It has some <b>bolts</b> that could be tightened. The frame can be <b>melted</b> down.")
else
switch(state)
if(EMPTY_CORE)
. += span_notice("There is a <b>slot</b> for a circuit board, its <b>bolts</b> can be loosened.")
if(CIRCUIT_CORE)
. += span_notice("The circuit board can be <b>screwed</b> into place or <b>pried</b> out.")
if(SCREWED_CORE)
. += span_notice("The frame can be <b>wired</b>, the circuit board can be <b>unfastened</b>.")
if(CABLED_CORE)
if(!core_mmi)
. += span_notice("There are wires which could be hooked up to an <b>MMI or positronic brain</b>, or <b>cut</b>.")
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 <b>slot</b> for a reinforced glass panel, the [AI_CORE_BRAIN(core_mmi)] could be <b>pried</b> out.[accept_laws ? " A law module can be <b>swiped</b> across." : ""]")
if(GLASS_CORE)
. += span_notice("The monitor [core_mmi?.brainmob?.mind && !suicide_check() ? "and neural interface " : ""]can be <b>screwed</b> in, the panel can be <b>pried</b> out.")
if(AI_READY_CORE)
. += span_notice("The monitor's connection can be <b>cut</b>[core_mmi?.brainmob?.mind && !suicide_check() ? " the neural interface can be <b>screwed</b> 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 <b>[active? "on" : "off"]</b>."
. += 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 <b>[get_area_name(ai_mob, format_text = TRUE)]</b>.", 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
@@ -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"
+131 -46
View File
@@ -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
@@ -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
+4 -5
View File
@@ -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)
+2 -5
View File
@@ -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.")
+1
View File
@@ -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
@@ -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!"))
@@ -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)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 KiB

After

Width:  |  Height:  |  Size: 410 KiB

+2 -1
View File
@@ -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"
@@ -0,0 +1 @@
/obj/structure/ai_core/deactivated/@SUBTYPES : /obj/structure/ai_core/@SUBTYPES{@OLD;state=5}