Files
Bubberstation/code/game/objects/structures/ai_core.dm
MrMelbert 5261efb67f Re-refactors batons / Refactors attack chain force modifiers (#90809)
## About The Pull Request

Melee attack chain now has a list passed along with it,
`attack_modifiers`, which you can stick force modifiers to change the
resulting attack

This is basically a soft implementation of damage packets until a more
definitive pr, but one that only applies to item attack chain, and not
unarmed attacks.

This change was done to facilitate a baton refactor - batons no longer
hack together their own attack chain, and are now integrated straight
into the real attack chain. This refactor itself was done because batons
don't send any attack signals, which has been annoying in the past (for
swing combat).

## Changelog

🆑 Melbert
refactor: Batons have been refactored again. Baton stuns now properly
count as an attack, when before it was a nothing. Report any oddities,
particularly in regards to harmbatonning vs normal batonning.
refactor: The method of adjusting item damage mid-attack has been
refactored - some affected items include the Nullblade and knives.
Report any strange happenings with damage numbers.
refactor: A few objects have been moved to the new interaction chain -
records consoles, mawed crucible, alien weeds and space vines, hedges,
restaurant portals, and some mobs - to name a few.
fix: Spears only deal bonus damage against secure lockers, not all
closet types (including crates)
/🆑
2025-05-19 13:32:12 +10:00

485 lines
17 KiB
Plaintext

#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.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