From 5408b91737574a05ce85a252d2b4d0a424a5a15c Mon Sep 17 00:00:00 2001
From: Gw0sty <83688318+Gw0sty@users.noreply.github.com>
Date: Sun, 4 May 2025 11:52:39 -0500
Subject: [PATCH 1/6] AI apc upgrade
---
code/__DEFINES/is_helpers.dm | 2 +
code/game/objects/items/robot/ai_upgrades.dm | 51 +++++++++++++++++
code/modules/antagonists/malf_ai/malf_ai.dm | 4 +-
.../malf_ai/malf_ai_module_picker.dm | 6 +-
.../antagonists/malf_ai/malf_ai_modules.dm | 56 +++++++++----------
code/modules/mob/living/silicon/ai/ai.dm | 6 +-
.../silicon/ai/ai_actions/remote_power.dm | 42 ++++++++++++++
.../research/designs/electronics_designs.dm | 12 ++++
code/modules/research/techweb/robo_nodes.dm | 1 +
.../antagonists/malf_ai/infected_ipc.dm | 2 +-
tgstation.dme | 1 +
11 files changed, 146 insertions(+), 37 deletions(-)
create mode 100644 code/modules/mob/living/silicon/ai/ai_actions/remote_power.dm
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 17dcc0f16495..92e07bce0be8 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -270,6 +270,8 @@ GLOBAL_LIST_INIT(cat_typecache, typecacheof(list(
#define is_reagent_container(O) (istype(O, /obj/item/reagent_containers))
+#define isapc(A) (istype(A, /obj/machinery/power/apc))
+
//MONKESTATION EDIT: used to block cargo teleporters from escaping with syndicate blackbox
#define issyndicateblackbox(O) (istype(O, /obj/item/syndicate_blackbox))
diff --git a/code/game/objects/items/robot/ai_upgrades.dm b/code/game/objects/items/robot/ai_upgrades.dm
index 6873b838ebde..d3346ec48c45 100644
--- a/code/game/objects/items/robot/ai_upgrades.dm
+++ b/code/game/objects/items/robot/ai_upgrades.dm
@@ -1,5 +1,45 @@
///AI Upgrades
+/obj/item/aiupgrade
+ name = "ai upgrade disk"
+ desc = "You really shouldn't be seeing this"
+ icon = 'icons/obj/module.dmi'
+ icon_state = "datadisk3"
+ ///The upgrade that will be applied to the AI when installed
+ var/datum/ai_module/to_gift = /datum/ai_module
+/obj/item/aiupgrade/pre_attack(atom/target, mob/living/user, proximity)
+ if(!proximity)
+ return ..()
+ if(!isAI(target))
+ return ..()
+ var/mob/living/silicon/ai/AI = target
+ var/datum/action/innate/ai/action = locate(to_gift.power_type) in AI.actions
+ var/datum/ai_module/gifted_ability = new to_gift
+ if(!to_gift.upgrade)
+ if(!action)
+ var/ability = to_gift.power_type
+ var/datum/action/gifted_action = new ability
+ gifted_action.Grant(AI)
+ else if(gifted_ability.one_purchase)
+ to_chat(user, "[AI] already has \a [src] installed!")
+ return
+ else
+ action.uses += initial(action.uses)
+ action.desc = "[initial(action.desc)] It has [action.uses] use\s remaining."
+ action.build_all_button_icons()
+ else
+ if(!action)
+ gifted_ability.upgrade(AI)
+ if(gifted_ability.unlock_text)
+ to_chat(AI, gifted_ability.unlock_text)
+ if(gifted_ability.unlock_sound)
+ AI.playsound_local(AI, gifted_ability.unlock_sound, 50, 0)
+ update_static_data(AI)
+ to_chat(user, span_notice("You install [src], upgrading [AI]."))
+ to_chat(AI, span_userdanger("[user] has upgraded you with [src]!"))
+ user.log_message("has upgraded [key_name(AI)] with a [src].", LOG_GAME)
+ qdel(src)
+ return TRUE
//Malf Picker
/obj/item/malf_upgrade
@@ -52,3 +92,14 @@
message_admins("[ADMIN_LOOKUPFLW(user)] has upgraded [ADMIN_LOOKUPFLW(AI)] with a [src].")
qdel(src)
return TRUE
+
+//Lipreading
+/obj/item/aiupgrade/surveillance_upgrade
+ name = "surveillance software upgrade"
+ desc = "An illegal software package that will allow an artificial intelligence to 'hear' from its cameras via lip reading and hidden microphones."
+ to_gift = /datum/ai_module/upgrade/eavesdrop
+
+/obj/item/aiupgrade/power_transfer
+ name = "power transfer upgrade"
+ desc = "A legal upgrade that allows an artificial intelligence to directly provide power to APCs from a distance"
+ to_gift = /datum/ai_module/power_apc
diff --git a/code/modules/antagonists/malf_ai/malf_ai.dm b/code/modules/antagonists/malf_ai/malf_ai.dm
index c892143f4f1c..f94f7d7064a4 100644
--- a/code/modules/antagonists/malf_ai/malf_ai.dm
+++ b/code/modules/antagonists/malf_ai/malf_ai.dm
@@ -189,7 +189,7 @@
"name" = category,
"items" = (category == malf_ai.malf_picker.selected_cat ? list() : null))
for(var/module in malf_ai.malf_picker.possible_modules[category])
- var/datum/ai_module/mod = malf_ai.malf_picker.possible_modules[category][module]
+ var/datum/ai_module/malf/mod = malf_ai.malf_picker.possible_modules[category][module]
// monkestation start: add icons
var/list/item_data = list(
"name" = mod.name,
@@ -221,7 +221,7 @@
for(var/category in malf_ai.malf_picker.possible_modules)
buyable_items += malf_ai.malf_picker.possible_modules[category]
for(var/key in buyable_items)
- var/datum/ai_module/valid_mod = buyable_items[key]
+ var/datum/ai_module/malf/valid_mod = buyable_items[key]
if(valid_mod.name == item_name)
malf_ai.malf_picker.purchase_module(malf_ai, valid_mod)
return TRUE
diff --git a/code/modules/antagonists/malf_ai/malf_ai_module_picker.dm b/code/modules/antagonists/malf_ai/malf_ai_module_picker.dm
index 0ac27c14c97b..af7edaf4e5a1 100644
--- a/code/modules/antagonists/malf_ai/malf_ai_module_picker.dm
+++ b/code/modules/antagonists/malf_ai/malf_ai_module_picker.dm
@@ -16,7 +16,7 @@
var/list/filtered_modules = list()
for(var/path in GLOB.malf_modules)
- var/datum/ai_module/AM = new path
+ var/datum/ai_module/malf/AM = new path
if((AM.power_type == /datum/action/innate/ai) && !AM.upgrade)
continue
if(!filtered_modules[AM.category])
@@ -52,7 +52,7 @@
"name" = category,
"items" = (category == selected_cat ? list() : null))
for(var/module in possible_modules[category])
- var/datum/ai_module/AM = possible_modules[category][module]
+ var/datum/ai_module/malf/AM = possible_modules[category][module]
cat["items"] += list(list(
"name" = AM.name,
"cost" = AM.cost,
@@ -75,7 +75,7 @@
for(var/category in possible_modules)
buyable_items += possible_modules[category]
for(var/key in buyable_items)
- var/datum/ai_module/AM = buyable_items[key]
+ var/datum/ai_module/malf/AM = buyable_items[key]
if(AM.name == item_name)
purchase_module(usr, AM)
return TRUE
diff --git a/code/modules/antagonists/malf_ai/malf_ai_modules.dm b/code/modules/antagonists/malf_ai/malf_ai_modules.dm
index 67d14c6dca14..aa3bc298bb07 100644
--- a/code/modules/antagonists/malf_ai/malf_ai_modules.dm
+++ b/code/modules/antagonists/malf_ai/malf_ai_modules.dm
@@ -49,7 +49,7 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
/obj/machinery/portable_atmospherics/canister,
)))
-GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
+GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module/malf))
/// The malf AI action subtype. All malf actions are subtypes of this.
/datum/action/innate/ai
@@ -138,15 +138,15 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
category = "Destructive Modules"
/// Modules with stealthy and utility uses
-/datum/ai_module/utility
+/datum/ai_module/malf/utility
category = "Utility Modules"
/// Modules that are improving AI abilities and assets
-/datum/ai_module/upgrade
+/datum/ai_module/malf/upgrade
category = "Upgrade Modules"
/// Doomsday Device: Starts the self-destruct timer. It can only be stopped by killing the AI completely.
-/datum/ai_module/destructive/nuke_station
+/datum/ai_module/malf/destructive/nuke_station
name = "Doomsday Device"
description = "Activate a weapon that will disintegrate all organic life on the station after a 450 second delay. \
Can only be used while on the station, will fail if your core is moved off station or destroyed. \
@@ -369,7 +369,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
return TRUE
/// Hostile Station Lockdown: Locks, bolts, and electrifies every airlock on the station. After 90 seconds, the doors reset.
-/datum/ai_module/destructive/lockdown
+/datum/ai_module/malf/destructive/lockdown
name = "Hostile Station Lockdown"
description = "Overload the airlock, blast door and fire control networks, locking them down. \
Caution! This command also electrifies all airlocks. The networks will automatically reset after 90 seconds, briefly \
@@ -421,7 +421,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
CHECK_TICK
/// Override Machine: Allows the AI to override a machine, animating it into an angry, living version of itself.
-/datum/ai_module/destructive/override_machine
+/datum/ai_module/malf/destructive/override_machine
name = "Machine Override"
description = "Overrides a machine's programming, causing it to rise up and attack everyone except other machines. Four uses per purchase."
cost = 30
@@ -473,7 +473,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
new /mob/living/basic/mimic/copy/machine(get_turf(to_animate), to_animate, user, TRUE)
/// Destroy RCDs: Detonates all non-cyborg RCDs on the station.
-/datum/ai_module/destructive/destroy_rcd
+/datum/ai_module/malf/destructive/destroy_rcd
name = "Destroy RCDs"
description = "Send a specialised pulse to detonate all hand-held and exosuit Rapid Construction Devices on the station."
cost = 25
@@ -498,7 +498,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
owner.playsound_local(owner, 'sound/machines/twobeep.ogg', 50, 0)
/// Overload Machine: Allows the AI to overload a machine, detonating it after a delay. Two uses per purchase.
-/datum/ai_module/destructive/overload_machine
+/datum/ai_module/malf/destructive/overload_machine
name = "Machine Overload"
description = "Overheats an electrical machine, causing a small explosion and destroying it. Two uses per purchase."
cost = 20
@@ -554,7 +554,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
return TRUE
/// Blackout: Overloads a random number of lights across the station. Three uses.
-/datum/ai_module/destructive/blackout
+/datum/ai_module/malf/destructive/blackout
name = "Blackout"
description = "Attempts to overload the lighting circuits on the station, destroying some bulbs. Three uses per purchase."
cost = 15
@@ -588,7 +588,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
build_all_button_icons()
/// HIGH IMPACT HONKING
-/datum/ai_module/destructive/megahonk
+/datum/ai_module/malf/destructive/megahonk
name = "Percussive Intercomm Interference"
description = "Emit a debilitatingly percussive auditory blast through the station intercoms. Does not overpower hearing protection. Two uses per purchase."
cost = 20
@@ -627,7 +627,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
to_chat(victim, span_clown("HOOOOONK!"))
/// Robotic Factory: Places a large machine that converts humans that go through it into cyborgs. Unlocking this ability removes shunting.
-/datum/ai_module/utility/place_cyborg_transformer
+/datum/ai_module/malf/utility/place_cyborg_transformer
name = "Robotic Factory (Removes Shunting)"
description = "Build a machine anywhere, using expensive nanomachines, that can convert a living human into a loyal either cyborg or IPC slave when placed inside." // monkestation edit PR #5133
cost = 100
@@ -702,7 +702,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
return success
/// Air Alarm Safety Override: Unlocks the ability to enable flooding on all air alarms.
-/datum/ai_module/utility/break_air_alarms
+/datum/ai_module/malf/utility/break_air_alarms
name = "Air Alarm Safety Override"
description = "Gives you the ability to disable safeties on all air alarms. This will allow you to use the environmental mode Flood, \
which disables scrubbers as well as pressure checks on vents. Anyone can check the air alarm's interface and may be tipped off by their nonfunctionality."
@@ -727,7 +727,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
owner.playsound_local(owner, 'sound/machines/terminal_off.ogg', 50, 0)
/// Thermal Sensor Override: Unlocks the ability to disable all fire alarms from doing their job.
-/datum/ai_module/utility/break_fire_alarms
+/datum/ai_module/malf/utility/break_fire_alarms
name = "Thermal Sensor Override"
description = "Gives you the ability to override the thermal sensors on all fire alarms. \
This will remove their ability to scan for fire and thus their ability to alert."
@@ -758,7 +758,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
owner.playsound_local(owner, 'sound/machines/terminal_off.ogg', 50, 0)
/// Disable Emergency Lights
-/datum/ai_module/utility/emergency_lights
+/datum/ai_module/malf/utility/emergency_lights
name = "Disable Emergency Lights"
description = "Cuts emergency lights across the entire station. If power is lost to light fixtures, \
they will not attempt to fall back on emergency power reserves."
@@ -784,7 +784,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
owner.playsound_local(owner, 'sound/effects/light_flicker.ogg', 50, FALSE)
/// Reactivate Camera Network: Reactivates up to 30 cameras across the station.
-/datum/ai_module/utility/reactivate_cameras
+/datum/ai_module/malf/utility/reactivate_cameras
name = "Reactivate Camera Network"
description = "Runs a network-wide diagnostic on the camera network, resetting focus and re-routing power to failed cameras. \
Can be used to repair up to 30 cameras."
@@ -825,7 +825,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
build_all_button_icons()
/// Upgrade Camera Network: EMP-proofs all cameras, in addition to giving them X-ray vision.
-/datum/ai_module/upgrade/upgrade_cameras
+/datum/ai_module/malf/upgrade/upgrade_cameras
name = "Upgrade Camera Network"
description = "Install broad-spectrum scanning and electrical redundancy firmware to the camera network, enabling EMP-proofing and light-amplified X-ray vision. Upgrade is done immediately upon purchase." //I <3 pointless technobabble
//This used to have motion sensing as well, but testing quickly revealed that giving it to the whole cameranet is PURE HORROR.
@@ -834,7 +834,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
unlock_text = span_notice("OTA firmware distribution complete! Cameras upgraded: CAMSUPGRADED. Light amplification system online.")
unlock_sound = 'sound/items/rped.ogg'
-/datum/ai_module/upgrade/upgrade_cameras/upgrade(mob/living/silicon/ai/AI)
+/datum/ai_module/malf/upgrade/upgrade_cameras/upgrade(mob/living/silicon/ai/AI)
// Sets up nightvision
RegisterSignal(AI, COMSIG_MOB_UPDATE_SIGHT, PROC_REF(on_update_sight))
AI.update_sight()
@@ -861,13 +861,13 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
upgraded_cameras++
unlock_text = replacetext(unlock_text, "CAMSUPGRADED", "[upgraded_cameras]") //This works, since unlock text is called after upgrade()
-/datum/ai_module/upgrade/upgrade_cameras/proc/on_update_sight(mob/source)
+/datum/ai_module/malf/upgrade/upgrade_cameras/proc/on_update_sight(mob/source)
SIGNAL_HANDLER
// Dim blue, pretty
source.lighting_color_cutoffs = blend_cutoff_colors(source.lighting_color_cutoffs, list(5, 25, 35))
/// AI Turret Upgrade: Increases the health and damage of all turrets.
-/datum/ai_module/upgrade/upgrade_turrets
+/datum/ai_module/malf/malf/upgrade/upgrade_turrets
name = "AI Turret Upgrade"
description = "Improves the power and health of all AI turrets. This effect is permanent. Upgrade is done immediately upon purchase."
cost = 30
@@ -875,7 +875,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
unlock_text = span_notice("You establish a power diversion to your turrets, upgrading their health and damage.")
unlock_sound = 'sound/items/rped.ogg'
-/datum/ai_module/upgrade/upgrade_turrets/upgrade(mob/living/silicon/ai/AI)
+/datum/ai_module/malf/upgrade/upgrade_turrets/upgrade(mob/living/silicon/ai/AI)
for(var/obj/machinery/porta_turret/ai/turret in GLOB.machines)
turret.AddElement(/datum/element/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES | EMP_PROTECT_CONTENTS)
turret.max_integrity = 200
@@ -884,7 +884,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
turret.lethal_projectile_sound = 'sound/weapons/lasercannonfire.ogg'
/// Enhanced Surveillance: Enables AI to hear conversations going on near its active vision.
-/datum/ai_module/upgrade/eavesdrop
+/datum/ai_module/malf/upgrade/eavesdrop
name = "Enhanced Surveillance"
description = "Via a combination of hidden microphones and lip reading software, \
you are able to use your cameras to listen in on conversations. Upgrade is done immediately upon purchase."
@@ -893,12 +893,12 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
unlock_text = span_notice("OTA firmware distribution complete! Cameras upgraded: Enhanced surveillance package online.")
unlock_sound = 'sound/items/rped.ogg'
-/datum/ai_module/upgrade/eavesdrop/upgrade(mob/living/silicon/ai/AI)
+/datum/ai_module/umalf/pgrade/eavesdrop/upgrade(mob/living/silicon/ai/AI)
if(AI.eyeobj)
AI.eyeobj.relay_speech = TRUE
/// Unlock Mech Domination: Unlocks the ability to dominate mechs. Big shocker, right?
-/datum/ai_module/upgrade/mecha_domination
+/datum/ai_module/malf/upgrade/mecha_domination
name = "Unlock Mech Domination"
description = "Allows you to hack into a mech's onboard computer, shunting all processes into it and ejecting any occupants. \
Once uploaded to the mech, it is impossible to leave. Do not allow the mech to leave the station's vicinity or allow it to be destroyed. \
@@ -909,10 +909,10 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
Loss of signal will result in total system lockout.")
unlock_sound = 'sound/mecha/nominal.ogg'
-/datum/ai_module/upgrade/mecha_domination/upgrade(mob/living/silicon/ai/AI)
+/datum/ai_module/malf/upgrade/mecha_domination/upgrade(mob/living/silicon/ai/AI)
AI.can_dominate_mechs = TRUE //Yep. This is all it does. Honk!
-/datum/ai_module/upgrade/voice_changer
+/datum/ai_module/malf/upgrade/voice_changer
name = "Voice Changer"
description = "Allows you to change the AI's voice. Upgrade is active immediately upon purchase."
cost = 40
@@ -1033,7 +1033,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
if("name")
say_name = params["name"]
-/datum/ai_module/utility/emag
+/datum/ai_module/malf/utility/emag
name = "Targetted Safeties Override"
description = "Allows you to disable the safeties of any machinery on the station, provided you can access it."
cost = 20
@@ -1127,7 +1127,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
return TRUE
-/datum/ai_module/utility/core_tilt
+/datum/ai_module/malf/utility/core_tilt
name = "Rolling Servos"
description = "Allows you to slowly roll around, crushing anything in your way with your bulk."
cost = 10
@@ -1226,7 +1226,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module))
stack_trace("non-standard dir entered to get_rotation_from_dir. (got: [dir])")
return 0
-/datum/ai_module/utility/remote_vendor_tilt
+/datum/ai_module/malf/utility/remote_vendor_tilt
name = "Remote vendor tilting"
description = "Lets you remotely tip vendors over in any direction."
cost = 15
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index bda3ed47ff80..14ec45ef1ef5 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -49,7 +49,7 @@
//MALFUNCTION
var/datum/module_picker/malf_picker
- var/list/datum/ai_module/current_modules = list()
+ var/list/datum/ai_module/malf/current_modules = list()
var/can_dominate_mechs = FALSE
var/shunted = FALSE //1 if the AI is currently shunted. Used to differentiate between shunted and ghosted/braindead
var/obj/machinery/ai_voicechanger/ai_voicechanger = null // reference to machine that holds the voicechanger
@@ -253,7 +253,7 @@
/// Removes all malfunction-related abilities from the AI
/mob/living/silicon/ai/proc/remove_malf_abilities()
QDEL_NULL(modules_action)
- for(var/datum/ai_module/AM in current_modules)
+ for(var/datum/ai_module/malf/AM in current_modules)
for(var/datum/action/A in actions)
if(istype(A, initial(AM.power_type)))
qdel(A)
@@ -1060,7 +1060,7 @@
malf_picker.processing_time += 10
var/area/apcarea = apc.area
- var/datum/ai_module/destructive/nuke_station/doom_n_boom = locate(/datum/ai_module/destructive/nuke_station) in malf_picker.possible_modules["Destructive Modules"]
+ var/datum/ai_module/malf/destructive/nuke_station/doom_n_boom = locate(/datum/ai_module/malf/destructive/nuke_station) in malf_picker.possible_modules["Destructive Modules"]
if(doom_n_boom && (is_type_in_list (apcarea, doom_n_boom.discount_areas)) && !(is_type_in_list (apcarea, doom_n_boom.hacked_command_areas)))
doom_n_boom.hacked_command_areas += apcarea
doom_n_boom.cost = max(50, 130 - (length(doom_n_boom.hacked_command_areas) * 20))
diff --git a/code/modules/mob/living/silicon/ai/ai_actions/remote_power.dm b/code/modules/mob/living/silicon/ai/ai_actions/remote_power.dm
new file mode 100644
index 000000000000..1a32d8bb3d18
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai/ai_actions/remote_power.dm
@@ -0,0 +1,42 @@
+/datum/ai_module/malf/power_apc
+ name = "Remote Power"
+ description = "remotely powers an APC from a distance"
+ one_purchase = TRUE
+ power_type = /datum/action/innate/ai/ranged/power_apc
+ unlock_text = span_notice("Remote APC power systems online.")
+
+/datum/action/innate/ai/ranged/power_apc
+ name = "Remotely Power APC"
+ desc = "Use to remotely power an APC."
+ button_icon = 'monkestation/code/modules/aesthetics/icons/apc.dmi'
+ button_icon_state = "apcewires"
+ ranged_mousepointer = 'icons/effects/mouse_pointers/supplypod_target.dmi'
+ enable_text = span_notice("You prepare to power any APC you see.")
+ disable_text = span_notice("You stop focusing on powering APCs.")
+
+/datum/action/innate/ai/ranged/power_apc/do_ability(mob/living/clicker, atom/clicked_on)
+
+ if (!isAI(clicker))
+ return FALSE
+ var/mob/living/silicon/ai/ai_clicker = clicker
+
+/* if(clicker.incapacitated)
+ unset_ranged_ability(clicker)
+ return FALSE
+*/
+
+ if(!isapc(clicked_on))
+ clicked_on.balloon_alert(ai_clicker, "not an APC!")
+ return FALSE
+
+ if(ai_clicker.battery - 50 <= 0)
+ to_chat(ai_clicker, span_warning("You do not have the battery to charge an APC!"))
+ return FALSE
+
+ var/obj/machinery/power/apc/apc = clicked_on
+ var/obj/item/stock_parts/cell/cell = apc.get_cell()
+ cell.give(1000)
+ ai_clicker.battery -= 50
+
+
+
diff --git a/code/modules/research/designs/electronics_designs.dm b/code/modules/research/designs/electronics_designs.dm
index d55e1a188bd7..6a717e1e0505 100644
--- a/code/modules/research/designs/electronics_designs.dm
+++ b/code/modules/research/designs/electronics_designs.dm
@@ -39,6 +39,18 @@
)
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+/datum/design/ai_power_transfer
+ name = "AI Power Transfer Update"
+ desc = "An upgrade package that lets an AI charge an APC from a distance"
+ id = "ai_power_upgrade"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/glass =SHEET_MATERIAL_AMOUNT * 2.5)
+ build_path = /obj/item/aiupgrade/power_transfer
+ category = list(
+ RND_CATEGORY_AI + RND_SUBCATEGORY_AI_UPGRADES
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+
////////////////////////////////////////
//////////Disk Construction Disks///////
////////////////////////////////////////
diff --git a/code/modules/research/techweb/robo_nodes.dm b/code/modules/research/techweb/robo_nodes.dm
index 5f74311d5d38..7bc21e8d1950 100644
--- a/code/modules/research/techweb/robo_nodes.dm
+++ b/code/modules/research/techweb/robo_nodes.dm
@@ -90,6 +90,7 @@
description = "State of the art lawsets to be used for AI research."
prereq_ids = list("ai_basic")
design_ids = list(
+ "ai_power_upgrade",
"asimovpp_module",
"paladin_devotion_module",
"dungeon_master_module",
diff --git a/monkestation/code/modules/antagonists/malf_ai/infected_ipc.dm b/monkestation/code/modules/antagonists/malf_ai/infected_ipc.dm
index d769b89a9495..c97e754cc2fe 100644
--- a/monkestation/code/modules/antagonists/malf_ai/infected_ipc.dm
+++ b/monkestation/code/modules/antagonists/malf_ai/infected_ipc.dm
@@ -142,7 +142,7 @@
owner.mind.remove_antag_datum(/datum/antagonist/infected_ipc)
//AI MODULE
-/datum/ai_module/utility/override_directive
+/datum/ai_module/malf/utility/override_directive
name = "Positronic Chassis Hacking"
description = "Instill a directive upon a single IPC to follow your whims and protect you, \
Requires target to be incapacitated and non-mindshielded to use. \
diff --git a/tgstation.dme b/tgstation.dme
index 5fe839cd757c..cd40b71fd6b6 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -4882,6 +4882,7 @@
#include "code\modules\mob\living\silicon\ai\multicam.dm"
#include "code\modules\mob\living\silicon\ai\robot_control.dm"
#include "code\modules\mob\living\silicon\ai\vox_sounds.dm"
+#include "code\modules\mob\living\silicon\ai\ai_actions\remote_power.dm"
#include "code\modules\mob\living\silicon\ai\freelook\cameranet.dm"
#include "code\modules\mob\living\silicon\ai\freelook\chunk.dm"
#include "code\modules\mob\living\silicon\ai\freelook\eye.dm"
From a09ffd9ebc1e1e195f91f7b3975a880cc34c0d02 Mon Sep 17 00:00:00 2001
From: Gw0sty <83688318+Gw0sty@users.noreply.github.com>
Date: Sun, 4 May 2025 12:06:59 -0500
Subject: [PATCH 2/6] touchups
---
code/game/objects/items/robot/ai_upgrades.dm | 2 +-
code/modules/mob/living/silicon/ai/ai_actions/remote_power.dm | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/code/game/objects/items/robot/ai_upgrades.dm b/code/game/objects/items/robot/ai_upgrades.dm
index d3346ec48c45..5281e5d1dad0 100644
--- a/code/game/objects/items/robot/ai_upgrades.dm
+++ b/code/game/objects/items/robot/ai_upgrades.dm
@@ -97,7 +97,7 @@
/obj/item/aiupgrade/surveillance_upgrade
name = "surveillance software upgrade"
desc = "An illegal software package that will allow an artificial intelligence to 'hear' from its cameras via lip reading and hidden microphones."
- to_gift = /datum/ai_module/upgrade/eavesdrop
+ to_gift = /datum/ai_module/malf/upgrade/eavesdrop
/obj/item/aiupgrade/power_transfer
name = "power transfer upgrade"
diff --git a/code/modules/mob/living/silicon/ai/ai_actions/remote_power.dm b/code/modules/mob/living/silicon/ai/ai_actions/remote_power.dm
index 1a32d8bb3d18..44c5ab9c6227 100644
--- a/code/modules/mob/living/silicon/ai/ai_actions/remote_power.dm
+++ b/code/modules/mob/living/silicon/ai/ai_actions/remote_power.dm
@@ -1,4 +1,4 @@
-/datum/ai_module/malf/power_apc
+/datum/ai_module/power_apc
name = "Remote Power"
description = "remotely powers an APC from a distance"
one_purchase = TRUE
From fa43b1d788bc9485d6ff7037a9a3992a6e83bf77 Mon Sep 17 00:00:00 2001
From: Gw0sty <83688318+Gw0sty@users.noreply.github.com>
Date: Mon, 5 May 2025 06:49:01 -0500
Subject: [PATCH 3/6] we do be draftin
---
code/modules/antagonists/malf_ai/malf_ai_modules.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/modules/antagonists/malf_ai/malf_ai_modules.dm b/code/modules/antagonists/malf_ai/malf_ai_modules.dm
index aa3bc298bb07..2385a5baeea6 100644
--- a/code/modules/antagonists/malf_ai/malf_ai_modules.dm
+++ b/code/modules/antagonists/malf_ai/malf_ai_modules.dm
@@ -893,7 +893,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module/malf))
unlock_text = span_notice("OTA firmware distribution complete! Cameras upgraded: Enhanced surveillance package online.")
unlock_sound = 'sound/items/rped.ogg'
-/datum/ai_module/umalf/pgrade/eavesdrop/upgrade(mob/living/silicon/ai/AI)
+/datum/ai_module/malf/upgrade/eavesdrop/upgrade(mob/living/silicon/ai/AI)
if(AI.eyeobj)
AI.eyeobj.relay_speech = TRUE
From 73429d637c065b58708f20735ddee0ccf0975808 Mon Sep 17 00:00:00 2001
From: Gw0sty <83688318+Gw0sty@users.noreply.github.com>
Date: Tue, 6 May 2025 09:31:40 -0500
Subject: [PATCH 4/6] Incapaciated check
---
code/modules/mob/living/silicon/ai/ai_actions/remote_power.dm | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/code/modules/mob/living/silicon/ai/ai_actions/remote_power.dm b/code/modules/mob/living/silicon/ai/ai_actions/remote_power.dm
index 44c5ab9c6227..1122c9ff9eb0 100644
--- a/code/modules/mob/living/silicon/ai/ai_actions/remote_power.dm
+++ b/code/modules/mob/living/silicon/ai/ai_actions/remote_power.dm
@@ -20,10 +20,9 @@
return FALSE
var/mob/living/silicon/ai/ai_clicker = clicker
-/* if(clicker.incapacitated)
+ if(clicker.incapacitated())
unset_ranged_ability(clicker)
return FALSE
-*/
if(!isapc(clicked_on))
clicked_on.balloon_alert(ai_clicker, "not an APC!")
From faa53ee1bf44f5a37778431c189449ba91c143ab Mon Sep 17 00:00:00 2001
From: Ghosti <83688318+notghosti@users.noreply.github.com>
Date: Tue, 20 May 2025 13:48:37 -0500
Subject: [PATCH 5/6] Update code/game/objects/items/robot/ai_upgrades.dm
Co-authored-by: Lucy
---
code/game/objects/items/robot/ai_upgrades.dm | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/code/game/objects/items/robot/ai_upgrades.dm b/code/game/objects/items/robot/ai_upgrades.dm
index 5281e5d1dad0..4ad162f437cc 100644
--- a/code/game/objects/items/robot/ai_upgrades.dm
+++ b/code/game/objects/items/robot/ai_upgrades.dm
@@ -8,9 +8,7 @@
var/datum/ai_module/to_gift = /datum/ai_module
/obj/item/aiupgrade/pre_attack(atom/target, mob/living/user, proximity)
- if(!proximity)
- return ..()
- if(!isAI(target))
+ if(!proximity || !isAI(target))
return ..()
var/mob/living/silicon/ai/AI = target
var/datum/action/innate/ai/action = locate(to_gift.power_type) in AI.actions
From 26c43874139625e2b26013970b9ce7668ed4e7b6 Mon Sep 17 00:00:00 2001
From: Ghosti <83688318+notghosti@users.noreply.github.com>
Date: Tue, 20 May 2025 13:49:44 -0500
Subject: [PATCH 6/6] Apply suggestions from code review
Co-authored-by: Lucy
---
code/game/objects/items/robot/ai_upgrades.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/game/objects/items/robot/ai_upgrades.dm b/code/game/objects/items/robot/ai_upgrades.dm
index 4ad162f437cc..b740d372e83c 100644
--- a/code/game/objects/items/robot/ai_upgrades.dm
+++ b/code/game/objects/items/robot/ai_upgrades.dm
@@ -31,7 +31,7 @@
if(gifted_ability.unlock_text)
to_chat(AI, gifted_ability.unlock_text)
if(gifted_ability.unlock_sound)
- AI.playsound_local(AI, gifted_ability.unlock_sound, 50, 0)
+ AI.playsound_local(AI, gifted_ability.unlock_sound, vol = 50, vary = TRUE)
update_static_data(AI)
to_chat(user, span_notice("You install [src], upgrading [AI]."))
to_chat(AI, span_userdanger("[user] has upgraded you with [src]!"))