kLv>woPlP{QL+!Q!qv|pc`4X&9O
zoT*Q92vZ9kH>Rte&t7=k9eCv93uf;~!NSsa|Inz44>uJ`WvO+19{mU63)qA=1IS-c
zpyTz%>2iS1Thx|Hqx09jN}9l%E0X
zh?<{;ebubV8_%_SmRu*OmP#+6({(6)SS*i-|%|}09aA|#+pkG=N*5FtuE_+Fn`
z@&GQLnC)RsWUVYLkS8-OHtSx@jt(O9{gLC^gCre`9fKwVX0mt+^sl|UD#CfJ%#TY1LpF3*WgpHf-x%_bp_+X}Ih}y4JJYplviacsv
z+?@}VJ|JxL>q$kd-iZ1a$}Ocp
literal 0
HcmV?d00001
From 9edc57bc1831dd98824ed5b133154997f978e839 Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Sun, 12 Jan 2025 03:11:57 -0500
Subject: [PATCH 12/44] Bluespace Miner, Multimarket Analyzer, Light Replacer
---
code/modules/mob/living/silicon/ai/ai_mob.dm | 21 +++++
.../mob/living/silicon/ai/ai_programs.dm | 89 ++++++++++++++++++-
2 files changed, 108 insertions(+), 2 deletions(-)
diff --git a/code/modules/mob/living/silicon/ai/ai_mob.dm b/code/modules/mob/living/silicon/ai/ai_mob.dm
index 68894d33c70a..edaa25805fe6 100644
--- a/code/modules/mob/living/silicon/ai/ai_mob.dm
+++ b/code/modules/mob/living/silicon/ai/ai_mob.dm
@@ -73,6 +73,12 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
var/universal_adapter = FALSE
/// How effective is the adapter?
var/adapter_efficiency = 0.5
+ /// Has the AI unlocked a bluespace miner?
+ var/bluespace_miner = FALSE
+ /// Credit payout rate
+ var/bluespace_miner_rate = 100
+ /// Time until next payout
+ var/next_payout = 10 MINUTES
//MALFUNCTION
var/datum/module_picker/malf_picker
@@ -387,6 +393,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
power_state = ACTIVE_POWER_USE
var/mob/living/silicon/ai/powered_ai = null
invisibility = 100
+ var/bluespace_miner_power = 0
/obj/machinery/ai_powersupply/New(mob/living/silicon/ai/ai=null)
powered_ai = ai
@@ -410,6 +417,20 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
change_power_mode(NO_POWER_USE)
if(powered_ai.anchored)
change_power_mode(ACTIVE_POWER_USE)
+ if(powered_ai.bluespace_miner)
+ // Money money money
+ if(powered_ai.next_payout <= world.time)
+ powered_ai.next_payout = 10 MINUTES + world.time
+ var/datum/economy/line_item/science_item = new
+ science_item.account = GLOB.station_money_database.get_account_by_department(DEPARTMENT_SCIENCE)
+ science_item.credits = powered_ai.bluespace_miner_rate
+ science_item.reason = "AI Bluespace Miner Earnings"
+ manifest.line_items += science_item
+ // Update power consumption if powering a bluespace miner
+ if(bluespace_miner_power == powered_ai.bluespace_miner_rate * 2.5)
+ return
+ bluespace_miner_power = powered_ai.bluespace_miner_rate * 2.5
+ update_active_power_consumption(power_channel, active_power_consumption + bluespace_miner_power)
/mob/living/silicon/ai/update_icons()
. = ..()
diff --git a/code/modules/mob/living/silicon/ai/ai_programs.dm b/code/modules/mob/living/silicon/ai/ai_programs.dm
index 644e4cdd378c..41cc1d5b95e8 100644
--- a/code/modules/mob/living/silicon/ai/ai_programs.dm
+++ b/code/modules/mob/living/silicon/ai/ai_programs.dm
@@ -189,7 +189,7 @@
L.brightness_color = new_color
var/mob/living/silicon/ai/AI = user
AI.program_picker.nanites -= 5
- user.playsound_local(user, "sound/effects/spray.ogg", 50, FALSE, use_reverb = FALSE)
+ user.playsound_local(user, 'sound/effects/spray.ogg', 50, FALSE, use_reverb = FALSE)
playsound(target, 'sound/effects/spray.ogg', 50, FALSE, use_reverb = FALSE)
var/obj/machinery/camera/C = find_nearest_camera(target)
if(!istype(C))
@@ -255,7 +255,7 @@
power_source.charge -= power_sent
break
AI.program_picker.nanites -= 20
- user.playsound_local(user, "sound/goonstation/misc/fuse.ogg", 50, FALSE, use_reverb = FALSE)
+ user.playsound_local(user, 'sound/goonstation/misc/fuse.ogg', 50, FALSE, use_reverb = FALSE)
playsound(target, 'sound/goonstation/misc/fuse.ogg', 50, FALSE, use_reverb = FALSE)
var/obj/machinery/camera/C = find_nearest_camera(target)
if(!istype(C))
@@ -444,3 +444,88 @@
/datum/spell/ai_spell/ranged/extinguishing_system/on_purchase_upgrade()
cooldown_handler.recharge_duration = max(min(base_cooldown, base_cooldown - (spell_level * 30)), 30 SECONDS)
+
+// Bluespace Miner Subsystem - Makes money for science, at the cost of extra power drain
+/datum/ai_program/bluespace_miner
+ program_name = "Bluespace Miner Subsystem"
+ program_id = "bluespace_miner"
+ description = "You link yourself to a miniature bluespace harvester, generating income for the science account at the cost of increasing your core's power needs."
+ nanite_cost = 0
+ max_level = 5
+ unlock_text = "Bluespace miner installation complete!"
+ upgrade = TRUE
+
+/datum/ai_program/bluespace_miner/upgrade(mob/user)
+ var/mob/living/silicon/ai/AI = user
+ if(!istype(user))
+ return
+ AI.bluespace_miner_rate = 100 + (100 * upgrade_level)
+ AI.next_payout = 10 MINUTES + world.time
+ AI.bluespace_miner = TRUE
+ upgrade_level++
+ installed = TRUE
+
+
+// Multimarket Analysis Subsystem: Reduce prices of things at cargo
+/datum/ai_program/multimarket_analyser
+ program_name = "Multimarket Analysis Subsystem"
+ program_id = "multimarket_analyser"
+ description = "You connect to a digital marketplace to price-check all orders from the station, ensuring you get the best prices! This reduces the cost of crates in cargo!"
+ nanite_cost = 0
+ unlock_text = "Online marketplace detected... connected!"
+ max_level = 6
+ upgrade = TRUE
+ /// Track the original modifier
+ var/original_price_mod = SSeconomy.pack_price_modifier
+
+/datum/ai_program/multimarket_analyser/upgrade(mob/user)
+ var/mob/living/silicon/ai/AI = user
+ if(!istype(user))
+ return
+ SSeconomy.pack_price_modifier = original_price_mod * (0.95 - (0.05 * upgrade_level))
+ upgrade_level++
+ installed = TRUE
+
+// RGB Lighting - Recolors Lights
+/datum/ai_program/light_repair
+ program_name = "Light Synthesizer"
+ program_id = "light_repair"
+ description = "Replace damaged or missing lightbulbs."
+ nanite_cost = 5
+ power_type = /datum/spell/ai_spell/ranged/light_repair
+ unlock_text = "Light replacer configuration installed."
+
+/datum/spell/ai_spell/ranged/light_repair
+ name = "Light Synthesizer"
+ desc = "Replace damaged or missing lightbulbs."
+ action_icon = 'icons/obj/janitor.dmi'
+ action_icon_state = "lightreplacer0"
+ ranged_mousepointer = 'icons/mecha/mecha_mouse.dmi'
+ auto_use_uses = FALSE
+ base_cooldown = 30 SECONDS
+ cooldown_min = 5 SECONDS
+ level_max = 5
+ selection_activated_message = "You prepare to synthesize a lightbulb..."
+ selection_deactivated_message = "You cancel the request."
+
+/datum/spell/ai_spell/ranged/light_repair/cast(list/targets, mob/user)
+ var/obj/machinery/light/target = targets[1]
+ if(!istype(target))
+ to_chat(user, "You can only repair lights!")
+ return
+ var/mob/living/silicon/ai/AI = user
+ // Handle repairs here since we're using a spell and not a tool
+ target.status = LIGHT_OK
+ target.switchcount = 0
+ target.emagged = FALSE
+ target.on = target.has_power()
+ AI.program_picker.nanites -= 5
+ user.playsound_local(user, 'sound/machines/ding.ogg',, 50, FALSE, use_reverb = FALSE)
+ playsound(target, 'sound/machines/ding.ogg',, 50, FALSE, use_reverb = FALSE)
+ var/obj/machinery/camera/C = find_nearest_camera(target)
+ if(!istype(C))
+ return
+ C.Beam(target, icon_state = "rped_upgrade", icon = 'icons/effects/effects.dmi', time = 5)
+
+/datum/spell/ai_spell/ranged/rgb_lighting/on_purchase_upgrade()
+ cooldown_handler.recharge_duration = base_cooldown - (spell_level * 5)
From 4cffe1ba2d6a0738445ab9c708b85d47882b5dcf Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Sun, 12 Jan 2025 03:56:06 -0500
Subject: [PATCH 13/44] Fixes economy thing with bluespace miner
---
code/modules/mob/living/silicon/ai/ai_mob.dm | 9 ++++-----
code/modules/mob/living/silicon/ai/ai_programs.dm | 9 +++++----
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/code/modules/mob/living/silicon/ai/ai_mob.dm b/code/modules/mob/living/silicon/ai/ai_mob.dm
index edaa25805fe6..dc7f4f7b9de7 100644
--- a/code/modules/mob/living/silicon/ai/ai_mob.dm
+++ b/code/modules/mob/living/silicon/ai/ai_mob.dm
@@ -421,11 +421,10 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
// Money money money
if(powered_ai.next_payout <= world.time)
powered_ai.next_payout = 10 MINUTES + world.time
- var/datum/economy/line_item/science_item = new
- science_item.account = GLOB.station_money_database.get_account_by_department(DEPARTMENT_SCIENCE)
- science_item.credits = powered_ai.bluespace_miner_rate
- science_item.reason = "AI Bluespace Miner Earnings"
- manifest.line_items += science_item
+ var/account = GLOB.station_money_database.get_account_by_department(DEPARTMENT_SCIENCE)
+ var/datum/money_account_database/main_station/station_db = GLOB.station_money_database
+ station_db.credit_account(account, powered_ai.bluespace_miner_rate, "Bluespace Miner Production", "AI Bluespace Miner Subsystem", FALSE)
+
// Update power consumption if powering a bluespace miner
if(bluespace_miner_power == powered_ai.bluespace_miner_rate * 2.5)
return
diff --git a/code/modules/mob/living/silicon/ai/ai_programs.dm b/code/modules/mob/living/silicon/ai/ai_programs.dm
index 41cc1d5b95e8..5f7e26d1e8f5 100644
--- a/code/modules/mob/living/silicon/ai/ai_programs.dm
+++ b/code/modules/mob/living/silicon/ai/ai_programs.dm
@@ -476,12 +476,13 @@
max_level = 6
upgrade = TRUE
/// Track the original modifier
- var/original_price_mod = SSeconomy.pack_price_modifier
+ var/original_price_mod
+
+/datum/ai_program/multimarket_analyser/New()
+ . = ..()
+ original_price_mod = SSeconomy.pack_price_modifier
/datum/ai_program/multimarket_analyser/upgrade(mob/user)
- var/mob/living/silicon/ai/AI = user
- if(!istype(user))
- return
SSeconomy.pack_price_modifier = original_price_mod * (0.95 - (0.05 * upgrade_level))
upgrade_level++
installed = TRUE
From 729fa86d37fb3d60796a34b1aaa9947780ad4555 Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Tue, 14 Jan 2025 00:59:38 -0500
Subject: [PATCH 14/44] Enhanced Door Controls, Nanosurgeons
---
code/_onclick/ai_onclick.dm | 4 +-
.../mob/living/silicon/ai/ai_programs.dm | 71 +++++++++++++++++--
.../modules/mob/living/silicon/silicon_mob.dm | 3 +
3 files changed, 71 insertions(+), 7 deletions(-)
diff --git a/code/_onclick/ai_onclick.dm b/code/_onclick/ai_onclick.dm
index 3801ae81dd78..5e31b5e9c152 100644
--- a/code/_onclick/ai_onclick.dm
+++ b/code/_onclick/ai_onclick.dm
@@ -224,7 +224,7 @@
/obj/machinery/door/airlock/AICtrlClick(mob/living/silicon/user) // Bolts doors
if(!ai_control_check(user))
return
- if(ispulsedemon(user) || user.can_instant_lockdown() || do_after_once(user, 3 SECONDS, needhand = FALSE, target = src, allow_moving = TRUE, attempt_cancel_message = "Bolting [src] cancelled.", special_identifier = "Bolt"))
+ if(ispulsedemon(user) || user.can_instant_lockdown() || do_after_once(user, user.door_bolt_delay, needhand = FALSE, target = src, allow_moving = TRUE, attempt_cancel_message = "Bolting [src] cancelled.", special_identifier = "Bolt"))
toggle_bolt(user)
@@ -236,7 +236,7 @@
if(isElectrified())
electrify(0, user, TRUE) // un-shock
else
- if(ispulsedemon(user) || user.can_instant_lockdown() || do_after_once(user, 3 SECONDS, target = src, allow_moving = TRUE, attempt_cancel_message = "Shocking [src] cancelled.", special_identifier = "Shock"))
+ if(ispulsedemon(user) || user.can_instant_lockdown() || do_after_once(user, user.door_bolt_delay, target = src, allow_moving = TRUE, attempt_cancel_message = "Shocking [src] cancelled.", special_identifier = "Shock"))
electrify(-1, user, TRUE) // permanent shock + audio cue
playsound(loc, "sparks", 100, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
diff --git a/code/modules/mob/living/silicon/ai/ai_programs.dm b/code/modules/mob/living/silicon/ai/ai_programs.dm
index 5f7e26d1e8f5..b6175a99a395 100644
--- a/code/modules/mob/living/silicon/ai/ai_programs.dm
+++ b/code/modules/mob/living/silicon/ai/ai_programs.dm
@@ -298,10 +298,6 @@
if(istype(target, /mob/living/carbon/human/machine) && !AI.universal_adapter)
to_chat(user, "This software lacks the required upgrade to recharge IPCs!")
return
- var/area/A = get_area(user)
- if(A == null)
- to_chat(user, "No SMES detected to power from!")
- return
if(istype(target, /obj/mecha)|| istype(target, /obj/machinery/power/apc))
var/obj/T = target
T.obj_integrity += min(T.max_integrity, T.max_integrity * (0.2 + min(0.3, (0.1 * spell_level))))
@@ -465,7 +461,6 @@
upgrade_level++
installed = TRUE
-
// Multimarket Analysis Subsystem: Reduce prices of things at cargo
/datum/ai_program/multimarket_analyser
program_name = "Multimarket Analysis Subsystem"
@@ -530,3 +525,69 @@
/datum/spell/ai_spell/ranged/rgb_lighting/on_purchase_upgrade()
cooldown_handler.recharge_duration = base_cooldown - (spell_level * 5)
+
+// Nanosurgeon Deployment - Uses large numbers of nanites to heal things
+/datum/ai_program/nanosurgeon_deployment
+ program_name = "Nanosurgeon Deployment"
+ program_id = "nanosurgeon_deployment"
+ description = "Heal a crew member with large numbers of robotic nanomachines!"
+ cost = 3
+ nanite_cost = 75
+ power_type = /datum/spell/ai_spell/ranged/nanosurgeon_deployment
+ unlock_text = "Surgical nanomachine firmware installation complete!"
+
+/datum/spell/ai_spell/ranged/nanosurgeon_deployment
+ name = "Nanosurgeon Deployment"
+ desc = "Heal a crew member with large numbers of robotic nanomachines!"
+ action_icon = 'icons/obj/surgery.dmi'
+ action_icon_state = "scalpel_laser1_on"
+ ranged_mousepointer = 'icons/mecha/mecha_mouse.dmi'
+ auto_use_uses = FALSE
+ base_cooldown = 450 SECONDS
+ cooldown_min = 30 SECONDS
+ level_max = 8
+ selection_activated_message = "You prepare to order your nanomachines to perform surgery..."
+ selection_deactivated_message = "You rescind the order."
+
+/datum/spell/ai_spell/ranged/nanosurgeon_deployment/cast(list/targets, mob/user)
+ var/mob/living/carbon/human/target = targets[1]
+ if(!istype(target) || istype(target, /mob/living/carbon/human/machine))
+ to_chat(user, "You can only heal organic crew!")
+ return
+ var/mob/living/silicon/ai/AI = user
+ AI.program_picker.nanites -= 75
+ user.playsound_local(user, "sound/goonstation/misc/fuse.ogg", 50, FALSE, use_reverb = FALSE)
+ playsound(target, 'sound/goonstation/misc/fuse.ogg', 50, FALSE, use_reverb = FALSE)
+ var/obj/machinery/camera/C = find_nearest_camera(target)
+ if(!istype(C))
+ return
+ C.Beam(target, icon_state = "medbeam", icon = 'icons/effects/beam.dmi', time = 10)
+ if(do_after_once(AI, 5 SECONDS, target = target, allow_moving = TRUE))
+ var/damage_healed = 20 + (min(30, (10 * spell_level)))
+ target.heal_overall_damage(damage_healed, damage_healed)
+ if(spell_level >= 5)
+ for(var/obj/item/organ/external/E in target.bodyparts)
+ if(prob(5*spell_level))
+ E.mend_fracture()
+ E.fix_internal_bleeding()
+ E.fix_burn_wound()
+
+// Enhanced Door Controls: Reduces delay in bolting and shocking doors
+/datum/ai_program/enhanced_doors
+ program_name = "Enhanced Door Controls"
+ program_id = "enhanced_doors"
+ description = "You enhance the subroutines that let you control doors, speeding up response times!"
+ nanite_cost = 0
+ unlock_text = "Doors connected and optimized. You feel right at home."
+ max_level = 5
+ upgrade = TRUE
+ /// Track the original delay
+ var/original_door_delay = 3 SECONDS
+
+/datum/ai_program/enhanced_doors/upgrade(mob/user)
+ var/mob/living/silicon/ai/AI = user
+ if(!istype(user))
+ return
+ upgrade_level++
+ AI.door_bolt_delay = original_door_delay * (1 - (upgrade_level * 0.1))
+ installed = TRUE
diff --git a/code/modules/mob/living/silicon/silicon_mob.dm b/code/modules/mob/living/silicon/silicon_mob.dm
index a0997ce25e3c..da21f10c35b2 100644
--- a/code/modules/mob/living/silicon/silicon_mob.dm
+++ b/code/modules/mob/living/silicon/silicon_mob.dm
@@ -80,6 +80,9 @@
var/datum/ai_laws/laws = null
var/list/additional_law_channels = list("State" = "")
+ /// The delay used when toggling door bolts or electrification
+ var/door_bolt_delay = 3 SECONDS
+
/mob/living/silicon/New()
GLOB.silicon_mob_list |= src
..()
From 1e0965e6f57f9a953c07b17a899f655f9120273f Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Thu, 16 Jan 2025 00:05:42 -0500
Subject: [PATCH 15/44] Experimental Research Subsystem
---
.../mob/living/silicon/ai/ai_programs.dm | 69 +++++++++++++++++++
1 file changed, 69 insertions(+)
diff --git a/code/modules/mob/living/silicon/ai/ai_programs.dm b/code/modules/mob/living/silicon/ai/ai_programs.dm
index b6175a99a395..86c900a1f0a5 100644
--- a/code/modules/mob/living/silicon/ai/ai_programs.dm
+++ b/code/modules/mob/living/silicon/ai/ai_programs.dm
@@ -591,3 +591,72 @@
upgrade_level++
AI.door_bolt_delay = original_door_delay * (1 - (upgrade_level * 0.1))
installed = TRUE
+
+// Experimental Research Subsystem - Knowledge is power
+/datum/ai_program/research_subsystem
+ program_name = "Experimental Research Subsystem"
+ program_id = "research_subsystem"
+ description = "Put your processors to work spinning centrifuges and studying results. You unlock a new point of research in a random field."
+ cost = 5
+ nanite_cost = 60
+ power_type = /datum/spell/ai_spell/research_subsystem
+ unlock_text = "Research and Discovery submodule installation complete."
+
+/datum/spell/ai_spell/research_subsystem
+ name = "Experimental Research Subsystem"
+ desc = "Heal a crew member with large numbers of robotic nanomachines!"
+ action_icon = 'icons/obj/machines/research.dmi'
+ action_icon_state = "tdoppler"
+ auto_use_uses = FALSE
+ base_cooldown = 900 SECONDS
+ cooldown_min = 600 SECONDS
+ starts_charged = FALSE
+ level_max = 10
+ selection_activated_message = "You spool up your research tools..."
+ selection_deactivated_message = "You spool down."
+ var/rnd_server = "station_rnd"
+
+/datum/spell/ai_spell/research_subsystem/cast(list/targets, mob/user)
+ // First, find the RND server
+ var/network_manager_uid = null
+ for(var/obj/machinery/computer/rnd_network_controller/RNC in GLOB.rnd_network_managers)
+ if(RNC.network_name == rnd_server)
+ network_manager_uid = RNC.UID()
+ break
+ var/obj/machinery/computer/rnd_network_controller/RNC = locateUID(network_manager_uid)
+ if(!RNC) // Could not find the RND server. It probably blew up.
+ return
+
+ var/upgraded = FALSE
+ var/datum/research/files = RNC.research_files
+ if(!files)
+ return
+ var/list/possible_tech = list()
+ for(var/datum/tech/T in files.possible_tech)
+ possible_tech += T
+ while(!upgraded)
+ var/datum/tech/tech_to_upgrade = pick_n_take(possible_tech)
+ // If there are no possible techs to upgrade, stop the program
+ if(!tech_to_upgrade)
+ to_chat(user, "Current research cannot be discovered any further.")
+ return
+ // No illegals until level 10
+ if(spell_level < 10 && istype(tech_to_upgrade, /datum/tech/syndicate))
+ continue
+ // No alien research
+ if(istype(tech_to_upgrade, /datum/tech/abductor))
+ continue
+ var/datum/tech/current = files.find_possible_tech_with_id(tech_to_upgrade.id)
+ if(!current)
+ continue
+ // If the tech is level 7 and the program too weak, don't upgrade
+ if(current.level >= 7 && spell_level < 5)
+ continue
+ // Nothing beyond 8
+ if(current.level >= 8)
+ continue
+ files.UpdateTech(tech_to_upgrade.id, current.level + 1)
+ to_chat(user, "Discovered innovations has led to an increase in the [current] field!")
+ upgraded = TRUE
+
+
From fefa974fdbbf6161097e7bac20af460b1c122896 Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Thu, 16 Jan 2025 00:07:01 -0500
Subject: [PATCH 16/44] Removed excess from RND subsystem
---
code/modules/mob/living/silicon/ai/ai_programs.dm | 1 -
1 file changed, 1 deletion(-)
diff --git a/code/modules/mob/living/silicon/ai/ai_programs.dm b/code/modules/mob/living/silicon/ai/ai_programs.dm
index 86c900a1f0a5..1cc5da5ade29 100644
--- a/code/modules/mob/living/silicon/ai/ai_programs.dm
+++ b/code/modules/mob/living/silicon/ai/ai_programs.dm
@@ -613,7 +613,6 @@
starts_charged = FALSE
level_max = 10
selection_activated_message = "You spool up your research tools..."
- selection_deactivated_message = "You spool down."
var/rnd_server = "station_rnd"
/datum/spell/ai_spell/research_subsystem/cast(list/targets, mob/user)
From e958a7745f01badaa7d2ee4f98c4213c5cdb194f Mon Sep 17 00:00:00 2001
From: XFirebirdX <142694283+XFirebirdX@users.noreply.github.com>
Date: Thu, 16 Jan 2025 17:22:44 +0100
Subject: [PATCH 17/44] Adds Processing Node
---
code/game/machinery/ai_resource.dm | 56 ++++++++++++++++++
code/game/machinery/machine_frame.dm | 14 +++++
.../research/designs/machine_designs.dm | 10 ++++
icons/obj/machines/ai_machinery.dmi | Bin 5242 -> 5244 bytes
paradise.dme | 1 +
5 files changed, 81 insertions(+)
create mode 100644 code/game/machinery/ai_resource.dm
diff --git a/code/game/machinery/ai_resource.dm b/code/game/machinery/ai_resource.dm
new file mode 100644
index 000000000000..4a875d7f2d59
--- /dev/null
+++ b/code/game/machinery/ai_resource.dm
@@ -0,0 +1,56 @@
+/obj/machinery/processingnode
+ name = "Processing Node"
+ desc = "Test Description"
+ icon = 'icons/obj/machines/ai_machinery.dmi'
+ icon_state = "processor-off"
+ density = TRUE
+ anchored = TRUE
+ max_integrity = 100
+ idle_power_consumption = 5
+ active_power_consumption = 200
+
+ var/active = FALSE
+ var/mob/living/silicon/ai/assigned_ai = null
+
+/obj/machinery/processingnode/Initialize(mapload)
+ . = ..()
+ component_parts = list()
+ component_parts += new /obj/item/circuitboard/processingnode(null)
+ component_parts += new /obj/item/stock_parts/capacitor(null, 2)
+ component_parts += new /obj/item/stack/sheet/mineral/gold(null, 10)
+ component_parts += new /obj/item/stack/sheet/mineral/silver(null, 10)
+ component_parts += new /obj/item/stack/sheet/mineral/diamond(null, 1)
+ component_parts += new /obj/item/stack/cable_coil(null, 5)
+ RefreshParts()
+
+/obj/machinery/processingnode/attack_hand(user as mob)
+ to_chat(user, "You toggle the Processing Node from [active ? "On" : "Off"] to [active ? "Off" : "On"]")
+ active = !active
+ if(active) // We're booting up
+ // Find the AI with lowest memory
+ for(var/mob/living/silicon/ai/new_ai in GLOB.ai_list)
+ if(!assigned_ai) //not found
+ assigned_ai = new_ai //search for new in global ai list
+ if(assigned_ai.program_picker.memory > new_ai.program_picker.memory)
+ assigned_ai = new_ai
+
+ if(!assigned_ai) // No eligible AI found, abort
+ active = FALSE
+ to_chat(user, "ERROR: No AI detected. Shutting down...")
+ to_chat(user, "The Processing Node turns off.")
+ else // We have an AI, up its memory
+ assigned_ai.program_picker.memory++
+
+ else // We're shutting down
+ // TODO rest of shutting down code
+ assigned_ai.program_picker.memory--
+ assigned_ai = null
+ to_chat(user, "Turning off")
+ update_icon(UPDATE_ICON_STATE)
+ if(assigned_ai)
+ // "destroyed" "do"
+ // assigned_ai.program_picker.memory--
+ // assigned_ai = null
+
+/obj/machinery/processingnode/update_icon_state()
+ icon_state = "processor-[active ? "on" : "off"]"
diff --git a/code/game/machinery/machine_frame.dm b/code/game/machinery/machine_frame.dm
index 7298d7c539f9..dae9f1e57c1c 100644
--- a/code/game/machinery/machine_frame.dm
+++ b/code/game/machinery/machine_frame.dm
@@ -1163,3 +1163,17 @@ to destroy them and players will be able to make replacements.
/obj/item/stock_parts/micro_laser = 1,
/obj/item/stack/cable_coil = 3,
/obj/item/stack/sheet/plasteel = 5)
+
+/obj/item/circuitboard/processingnode
+ board_name = "Processing Node"
+ icon_state = "science"
+ build_path = /obj/machinery/processingnode
+ board_type = "machine"
+ origin_tech = "programming=4"
+ req_components = list(
+ /obj/item/stock_parts/capacitor = 2,
+ /obj/item/stack/sheet/mineral/gold = 10,
+ /obj/item/stack/sheet/mineral/silver = 10,
+ /obj/item/stack/sheet/mineral/diamond = 1,
+ /obj/item/stack/cable_coil = 5,
+ )
diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm
index 647ee8e23441..6ac302a31ea8 100644
--- a/code/modules/research/designs/machine_designs.dm
+++ b/code/modules/research/designs/machine_designs.dm
@@ -601,3 +601,13 @@
materials = list(MAT_GLASS = 1000)
build_path = /obj/item/circuitboard/merch
category = list("Misc. Machinery")
+
+/datum/design/processingnode
+ name = "Machine Design (Processing Node)"
+ desc = "The circuit board for a processing node."
+ id = "processingnode"
+ req_tech = list("programming" = 5)
+ build_type = IMPRINTER
+ materials = list(MAT_GLASS = 1000, MAT_GOLD = 250)
+ build_path = /obj/machinery/processingnode
+ category = list("Misc. Machinery")
diff --git a/icons/obj/machines/ai_machinery.dmi b/icons/obj/machines/ai_machinery.dmi
index c2af351214a7d9d2a55a68afc1098eac4a51ea8d..ce28ed9a367654cb9a327ac3929f4ed28121e022 100644
GIT binary patch
delta 221
zcmV<303!eTDEugpB!9?yR9JLGWpiV4X>fFDZ*Bkpc$|%r(F%ev7=^F5rx<#xQD)tB
z6H0iI?=bYYjm%%Rq4f4G!%UhW=jQz5a6S&qXXo^iMl1lLh4Bpn1P*U^f+XsJgR#nwPmn$vM
ztRqKrIms<7&6wg1m>$(V|pYo?Cb#i2j2L-3UYwq_l#Yb>}yH6`O&
X(r0O&44>r%QX!v|!n4QrvGa!$4`Xkv
delta 219
zcmV<103`qXDEcUnB!9+wR9JLGWpiV4X>fFDZ*Bkpc$|%rK@Ng26hznUDH_7D$6fj
zuC!1%j~q_rBsWn1Mc06x(wCpBcO%+~96IJq9kO4CzQMM^OAgqab-b!n&Qa+x8BLNt
VOR~8CFn7
Date: Thu, 16 Jan 2025 18:06:00 -0500
Subject: [PATCH 18/44] Processing node changes and fixes
---
code/game/machinery/ai_resource.dm | 33 ++++++++++++-------
code/game/machinery/machine_frame.dm | 4 +--
.../research/designs/machine_designs.dm | 6 ++--
3 files changed, 26 insertions(+), 17 deletions(-)
diff --git a/code/game/machinery/ai_resource.dm b/code/game/machinery/ai_resource.dm
index 4a875d7f2d59..450a5f522c5c 100644
--- a/code/game/machinery/ai_resource.dm
+++ b/code/game/machinery/ai_resource.dm
@@ -1,4 +1,4 @@
-/obj/machinery/processingnode
+/obj/machinery/processing_node
name = "Processing Node"
desc = "Test Description"
icon = 'icons/obj/machines/ai_machinery.dmi'
@@ -8,14 +8,15 @@
max_integrity = 100
idle_power_consumption = 5
active_power_consumption = 200
-
+ /// Is the machine active
var/active = FALSE
+ /// What AI is this machine associated with
var/mob/living/silicon/ai/assigned_ai = null
-/obj/machinery/processingnode/Initialize(mapload)
+/obj/machinery/processing_node/Initialize(mapload)
. = ..()
component_parts = list()
- component_parts += new /obj/item/circuitboard/processingnode(null)
+ component_parts += new /obj/item/circuitboard/processing_node(null)
component_parts += new /obj/item/stock_parts/capacitor(null, 2)
component_parts += new /obj/item/stack/sheet/mineral/gold(null, 10)
component_parts += new /obj/item/stack/sheet/mineral/silver(null, 10)
@@ -23,7 +24,7 @@
component_parts += new /obj/item/stack/cable_coil(null, 5)
RefreshParts()
-/obj/machinery/processingnode/attack_hand(user as mob)
+/obj/machinery/processing_node/attack_hand(user as mob)
to_chat(user, "You toggle the Processing Node from [active ? "On" : "Off"] to [active ? "Off" : "On"]")
active = !active
if(active) // We're booting up
@@ -42,15 +43,23 @@
assigned_ai.program_picker.memory++
else // We're shutting down
- // TODO rest of shutting down code
+ if(assigned_ai)
+ assigned_ai.program_picker.memory--
+ assigned_ai = null
+ update_icon(UPDATE_ICON_STATE)
+
+/obj/machinery/processing_node/Destroy()
+ . = ..()
+ if(assigned_ai)
assigned_ai.program_picker.memory--
assigned_ai = null
- to_chat(user, "Turning off")
- update_icon(UPDATE_ICON_STATE)
+
+/obj/machinery/processing_node/on_deconstruction()
+ . = ..()
if(assigned_ai)
- // "destroyed" "do"
- // assigned_ai.program_picker.memory--
- // assigned_ai = null
+ assigned_ai.program_picker.memory--
+ assigned_ai = null
-/obj/machinery/processingnode/update_icon_state()
+/obj/machinery/processing_node/update_icon_state()
+ . = ..()
icon_state = "processor-[active ? "on" : "off"]"
diff --git a/code/game/machinery/machine_frame.dm b/code/game/machinery/machine_frame.dm
index dae9f1e57c1c..00c6243c8cff 100644
--- a/code/game/machinery/machine_frame.dm
+++ b/code/game/machinery/machine_frame.dm
@@ -1164,10 +1164,10 @@ to destroy them and players will be able to make replacements.
/obj/item/stack/cable_coil = 3,
/obj/item/stack/sheet/plasteel = 5)
-/obj/item/circuitboard/processingnode
+/obj/item/circuitboard/processing_node
board_name = "Processing Node"
icon_state = "science"
- build_path = /obj/machinery/processingnode
+ build_path = /obj/machinery/processing_node
board_type = "machine"
origin_tech = "programming=4"
req_components = list(
diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm
index 6ac302a31ea8..04772062426c 100644
--- a/code/modules/research/designs/machine_designs.dm
+++ b/code/modules/research/designs/machine_designs.dm
@@ -602,12 +602,12 @@
build_path = /obj/item/circuitboard/merch
category = list("Misc. Machinery")
-/datum/design/processingnode
+/datum/design/processing_node
name = "Machine Design (Processing Node)"
desc = "The circuit board for a processing node."
- id = "processingnode"
+ id = "processing_node"
req_tech = list("programming" = 5)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_GOLD = 250)
- build_path = /obj/machinery/processingnode
+ build_path = /obj/machinery/processing_node
category = list("Misc. Machinery")
From ae57da3d1111c67b16784e7ddf5870ec1e786e10 Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Thu, 16 Jan 2025 18:09:54 -0500
Subject: [PATCH 19/44] Processing nodes power states
---
code/game/machinery/ai_resource.dm | 2 ++
1 file changed, 2 insertions(+)
diff --git a/code/game/machinery/ai_resource.dm b/code/game/machinery/ai_resource.dm
index 450a5f522c5c..dd5a456771ad 100644
--- a/code/game/machinery/ai_resource.dm
+++ b/code/game/machinery/ai_resource.dm
@@ -41,11 +41,13 @@
to_chat(user, "The Processing Node turns off.")
else // We have an AI, up its memory
assigned_ai.program_picker.memory++
+ change_power_mode(ACTIVE_POWER_USE)
else // We're shutting down
if(assigned_ai)
assigned_ai.program_picker.memory--
assigned_ai = null
+ change_power_mode(IDLE_POWER_USE)
update_icon(UPDATE_ICON_STATE)
/obj/machinery/processing_node/Destroy()
From 61b6db99a4b5bccfaec3aa2886c001b5b649bb70 Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Thu, 16 Jan 2025 18:12:10 -0500
Subject: [PATCH 20/44] Minor cleanup
---
code/game/machinery/ai_resource.dm | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/code/game/machinery/ai_resource.dm b/code/game/machinery/ai_resource.dm
index dd5a456771ad..7e7a9ca2d9bc 100644
--- a/code/game/machinery/ai_resource.dm
+++ b/code/game/machinery/ai_resource.dm
@@ -30,11 +30,10 @@
if(active) // We're booting up
// Find the AI with lowest memory
for(var/mob/living/silicon/ai/new_ai in GLOB.ai_list)
- if(!assigned_ai) //not found
- assigned_ai = new_ai //search for new in global ai list
+ if(!assigned_ai) // Not found
+ assigned_ai = new_ai // Assign to the first AI in the list to start
if(assigned_ai.program_picker.memory > new_ai.program_picker.memory)
assigned_ai = new_ai
-
if(!assigned_ai) // No eligible AI found, abort
active = FALSE
to_chat(user, "ERROR: No AI detected. Shutting down...")
@@ -42,7 +41,6 @@
else // We have an AI, up its memory
assigned_ai.program_picker.memory++
change_power_mode(ACTIVE_POWER_USE)
-
else // We're shutting down
if(assigned_ai)
assigned_ai.program_picker.memory--
From acdd0ef52694c558cb36edcb1d9b3e080a960142 Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Thu, 16 Jan 2025 21:35:40 -0500
Subject: [PATCH 21/44] I've got a network node! And it makes heat!
---
code/game/machinery/ai_resource.dm | 166 ++++++++++++++----
code/game/machinery/machine_frame.dm | 16 +-
.../mob/living/silicon/ai/ai_programs.dm | 6 +
.../research/designs/machine_designs.dm | 12 +-
icons/obj/machines/ai_machinery.dmi | Bin 5244 -> 5240 bytes
5 files changed, 164 insertions(+), 36 deletions(-)
diff --git a/code/game/machinery/ai_resource.dm b/code/game/machinery/ai_resource.dm
index 7e7a9ca2d9bc..b9edb9265371 100644
--- a/code/game/machinery/ai_resource.dm
+++ b/code/game/machinery/ai_resource.dm
@@ -1,65 +1,163 @@
-/obj/machinery/processing_node
- name = "Processing Node"
- desc = "Test Description"
+/obj/machinery/ai_node
+ name = "AI Node"
+ desc = "If you see me, make an issue report!"
icon = 'icons/obj/machines/ai_machinery.dmi'
- icon_state = "processor-off"
+ icon_state = "processor"
density = TRUE
anchored = TRUE
max_integrity = 100
idle_power_consumption = 5
- active_power_consumption = 200
+ active_power_consumption = 250
/// Is the machine active
var/active = FALSE
/// What AI is this machine associated with
var/mob/living/silicon/ai/assigned_ai = null
+ /// Identifier for what resource to remove
+ var/resource_key
+ /// How much resources does this machine provide
+ var/resource_amount = 1
+ /// How much heat does this put out?
+ var/heat_amount = 40000
+ /// Are we overheating?
+ var/overheating = FALSE
-/obj/machinery/processing_node/Initialize(mapload)
+/obj/machinery/ai_node/Initialize(mapload)
. = ..()
- component_parts = list()
- component_parts += new /obj/item/circuitboard/processing_node(null)
- component_parts += new /obj/item/stock_parts/capacitor(null, 2)
- component_parts += new /obj/item/stack/sheet/mineral/gold(null, 10)
- component_parts += new /obj/item/stack/sheet/mineral/silver(null, 10)
- component_parts += new /obj/item/stack/sheet/mineral/diamond(null, 1)
- component_parts += new /obj/item/stack/cable_coil(null, 5)
- RefreshParts()
+ update_icon(UPDATE_ICON_STATE)
+
+/obj/machinery/ai_node/process()
+ ..()
+ if(active)
+ var/datum/milla_safe/ai_node_process/milla = new()
+ milla.invoke_async(src)
+
+/obj/machinery/ai_node/Destroy()
+ . = ..()
+ if(assigned_ai)
+ assigned_ai.program_picker.modify_resource(resource_key, -resource_amount)
+ assigned_ai = null
-/obj/machinery/processing_node/attack_hand(user as mob)
- to_chat(user, "You toggle the Processing Node from [active ? "On" : "Off"] to [active ? "Off" : "On"]")
+/obj/machinery/ai_node/on_deconstruction()
+ . = ..()
+ if(assigned_ai)
+ assigned_ai.program_picker.modify_resource(resource_key, -resource_amount)
+ assigned_ai = null
+
+/obj/machinery/ai_node/update_icon_state()
+ . = ..()
+ if(overheating)
+ icon_state = "[initial(icon_state)]-hot"
+ return
+ icon_state = "[initial(icon_state)]-[active ? "on" : "off"]"
+
+/obj/machinery/ai_node/attack_ai(mob/user)
+ return
+
+/obj/machinery/ai_node/attack_hand(user as mob)
+ if(overheating)
+ to_chat(user, "You turn the overheating [src] off.")
+ overheating = FALSE
+ update_icon(UPDATE_ICON_STATE)
+ return
active = !active
+ to_chat(user, "You turn the [src] [active ? "On" : "Off"]")
if(active) // We're booting up
- // Find the AI with lowest memory
- for(var/mob/living/silicon/ai/new_ai in GLOB.ai_list)
- if(!assigned_ai) // Not found
- assigned_ai = new_ai // Assign to the first AI in the list to start
- if(assigned_ai.program_picker.memory > new_ai.program_picker.memory)
- assigned_ai = new_ai
+ find_ai()
if(!assigned_ai) // No eligible AI found, abort
active = FALSE
to_chat(user, "ERROR: No AI detected. Shutting down...")
to_chat(user, "The Processing Node turns off.")
- else // We have an AI, up its memory
- assigned_ai.program_picker.memory++
+ else // We have an AI
+ assigned_ai.program_picker.modify_resource(resource_key, resource_amount)
change_power_mode(ACTIVE_POWER_USE)
else // We're shutting down
if(assigned_ai)
- assigned_ai.program_picker.memory--
+ assigned_ai.program_picker.modify_resource(resource_key, -resource_amount)
assigned_ai = null
change_power_mode(IDLE_POWER_USE)
update_icon(UPDATE_ICON_STATE)
-/obj/machinery/processing_node/Destroy()
- . = ..()
+/obj/machinery/ai_node/proc/find_ai()
+ if(!resource_key)
+ return
+ for(var/mob/living/silicon/ai/new_ai in GLOB.ai_list)
+ if(!assigned_ai) // Not found
+ assigned_ai = new_ai // Assign to the first AI in the list to start
+ continue
+ if(resource_key == "memory" && assigned_ai.program_picker.memory > new_ai.program_picker.memory)
+ assigned_ai = new_ai
+ continue
+ if(resource_key == "bandwidth" && assigned_ai.program_picker.bandwidth > new_ai.program_picker.bandwidth)
+ assigned_ai = new_ai
+
+/obj/machinery/ai_node/proc/Overheat()
+ active = FALSE
if(assigned_ai)
- assigned_ai.program_picker.memory--
+ assigned_ai.program_picker.modify_resource(resource_key, -resource_amount)
assigned_ai = null
+ obj_integrity -= 34 // Overheat it three times and it breaks
+ change_power_mode(IDLE_POWER_USE)
+ update_icon(UPDATE_ICON_STATE)
+
+/datum/milla_safe/ai_node_process
-/obj/machinery/processing_node/on_deconstruction()
+/datum/milla_safe/ai_node_process/on_run(var/obj/machinery/ai_node/node)
+ var/turf/simulated/L = get_turf(node)
+ if(!istype(L))
+ return
+ var/datum/gas_mixture/env = get_turf_air(L)
+ var/transfer_moles = 0.25 * env.total_moles()
+ var/datum/gas_mixture/removed = env.remove(transfer_moles)
+ if(!removed)
+ return
+ var/heat_capacity = removed.heat_capacity()
+ if(heat_capacity)
+ removed.set_temperature(removed.temperature() + node.heat_amount / heat_capacity)
+ env.merge(removed)
+ // Heat check
+ if(env.temperature() > 373 || env.temperature() < 273) // If the temperature is outside 0-100C...
+ // Turn the node off due to temperature problems
+ node.Overheat()
+
+/obj/machinery/ai_node/processing_node
+ name = "Processing Node"
+ desc = "Highly advanced machinery with a manual switch. While running, it grants an AI memory."
+ icon = 'icons/obj/machines/ai_machinery.dmi'
+ icon_state = "processor"
+ resource_key = "memory"
+
+/obj/machinery/ai_node/processing_node/Initialize(mapload)
. = ..()
- if(assigned_ai)
- assigned_ai.program_picker.memory--
- assigned_ai = null
+ component_parts = list()
+ component_parts += new /obj/item/circuitboard/processing_node(null)
+ component_parts += new /obj/item/stock_parts/capacitor(null, 2)
+ component_parts += new /obj/item/stack/sheet/mineral/gold(null, 10)
+ component_parts += new /obj/item/stack/sheet/mineral/silver(null, 10)
+ component_parts += new /obj/item/stack/sheet/mineral/diamond(null, 1)
+ component_parts += new /obj/item/stack/cable_coil(null, 5)
+ RefreshParts()
+
+/obj/machinery/ai_node/network_node
+ name = "Network Node"
+ desc = "Highly advanced machinery with an on/off switch. While running, it grants an AI bandwidth."
+ icon = 'icons/obj/machines/ai_machinery.dmi'
+ icon_state = "network"
+ resource_key = "bandwidth"
-/obj/machinery/processing_node/update_icon_state()
+/obj/machinery/ai_node/network_node/examine_more(mob/user)
. = ..()
- icon_state = "processor-[active ? "on" : "off"]"
+ . += "I don't know but I've been told\
+ Captain's got a network node!\
+ Likes to press the on/off switch!\
+ Dig that crazy corporate snitch!"
+
+/obj/machinery/ai_node/network_node/Initialize(mapload)
+ . = ..()
+ component_parts = list()
+ component_parts += new /obj/item/circuitboard/network_node(null)
+ component_parts += new /obj/item/stock_parts/capacitor(null, 2)
+ component_parts += new /obj/item/stack/sheet/mineral/gold(null, 10)
+ component_parts += new /obj/item/stack/sheet/mineral/silver(null, 10)
+ component_parts += new /obj/item/stack/sheet/mineral/diamond(null, 1)
+ component_parts += new /obj/item/stack/cable_coil(null, 5)
+ RefreshParts()
diff --git a/code/game/machinery/machine_frame.dm b/code/game/machinery/machine_frame.dm
index 00c6243c8cff..e506f6b3cb6c 100644
--- a/code/game/machinery/machine_frame.dm
+++ b/code/game/machinery/machine_frame.dm
@@ -1167,7 +1167,7 @@ to destroy them and players will be able to make replacements.
/obj/item/circuitboard/processing_node
board_name = "Processing Node"
icon_state = "science"
- build_path = /obj/machinery/processing_node
+ build_path = /obj/machinery/ai_node/processing_node
board_type = "machine"
origin_tech = "programming=4"
req_components = list(
@@ -1177,3 +1177,17 @@ to destroy them and players will be able to make replacements.
/obj/item/stack/sheet/mineral/diamond = 1,
/obj/item/stack/cable_coil = 5,
)
+
+/obj/item/circuitboard/network_node
+ board_name = "Network Node"
+ icon_state = "science"
+ build_path = /obj/machinery/ai_node/network_node
+ board_type = "machine"
+ origin_tech = "programming=4"
+ req_components = list(
+ /obj/item/stock_parts/capacitor = 2,
+ /obj/item/stack/sheet/mineral/gold = 10,
+ /obj/item/stack/sheet/mineral/silver = 10,
+ /obj/item/stack/cable_coil = 5,
+ )
+
diff --git a/code/modules/mob/living/silicon/ai/ai_programs.dm b/code/modules/mob/living/silicon/ai/ai_programs.dm
index 1cc5da5ade29..b7af49251d68 100644
--- a/code/modules/mob/living/silicon/ai/ai_programs.dm
+++ b/code/modules/mob/living/silicon/ai/ai_programs.dm
@@ -36,6 +36,12 @@
popup.open()
return
+/datum/program_picker/proc/modify_resource(var/key, var/amount)
+ if(key == "memory")
+ memory += amount
+ if(key == "bandwidth")
+ bandwidth += amount
+
/datum/program_picker/Topic(href, href_list)
..()
diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm
index 04772062426c..1687dd09c02e 100644
--- a/code/modules/research/designs/machine_designs.dm
+++ b/code/modules/research/designs/machine_designs.dm
@@ -609,5 +609,15 @@
req_tech = list("programming" = 5)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000, MAT_GOLD = 250)
- build_path = /obj/machinery/processing_node
+ build_path = /obj/machinery/ai_node/processing_node
+ category = list("Misc. Machinery")
+
+/datum/design/network_node
+ name = "Machine Design (Network Node)"
+ desc = "The circuit board for a network node."
+ id = "network_node"
+ req_tech = list("programming" = 5)
+ build_type = IMPRINTER
+ materials = list(MAT_GLASS = 1000, MAT_GOLD = 250)
+ build_path = /obj/machinery/ai_node/network_node
category = list("Misc. Machinery")
diff --git a/icons/obj/machines/ai_machinery.dmi b/icons/obj/machines/ai_machinery.dmi
index ce28ed9a367654cb9a327ac3929f4ed28121e022..250af9dfd5edb2fcc09b7475cb176279a2cf7ec3 100644
GIT binary patch
delta 217
zcmV;~04D$ZDEKIlB!9$uR9JLGWpiV4X>fFDZ*Bkpc$|%r!485j5Jb=IS2S=%qln(T
z7>MB@|Iol<8<8b#Bk}L87(rs2kh#omru#BULw3r}dBM_4P9LDO#FiadvffbA%gP8l
z>|#m;$g41)K^tzs+MrWdg4U>3*annTQfa&1Qc^2^hr!!36;3q#`S#JDoTuvAbIX+$
zXqJ)Vxt!zn-^M+ef1uuqDlS(_qOBswtVwk|E2A
TboeMQRc4*_fFDZ*Bkpc$|%r(F%ev7=^F5rx<#xQD)tB
z6H0iI?=bYYjm%%Rq4f4G!%UhW=jQz5a6S&qXXo^iMl1lLh4Bpn1P*U^f+XsJgR#nwPmn$vM
ztRqKrIms<7&6wg1m>$(V|pYo?Cb#i2j2L-3UYwq_l#Yb>}yH6`O&
X(r0O&44>r%QX!v|!n4QrvGIo!4Wn
Date: Thu, 16 Jan 2025 21:50:39 -0500
Subject: [PATCH 22/44] Adds proc to nodes to change the assigned AI
---
code/game/machinery/ai_resource.dm | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/code/game/machinery/ai_resource.dm b/code/game/machinery/ai_resource.dm
index b9edb9265371..e1314639c2a0 100644
--- a/code/game/machinery/ai_resource.dm
+++ b/code/game/machinery/ai_resource.dm
@@ -99,6 +99,15 @@
change_power_mode(IDLE_POWER_USE)
update_icon(UPDATE_ICON_STATE)
+/obj/machinery/ai_node/proc/change_ai(var/mob/living/silicon/ai/new_ai)
+ if(!new_ai)
+ return
+ if(!istype(new_ai))
+ return
+ assigned_ai.program_picker.modify_resource(resource_key, -resource_amount)
+ assigned_ai = new_attack_chain
+ assigned_ai.program_picker.modify_resource(resource_key, resource_amount)
+
/datum/milla_safe/ai_node_process
/datum/milla_safe/ai_node_process/on_run(var/obj/machinery/ai_node/node)
From c7c53596a1586c678e42970b1df29b7eb2194071 Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Thu, 16 Jan 2025 21:53:50 -0500
Subject: [PATCH 23/44] Removed extra vars
---
code/game/machinery/ai_resource.dm | 4 ++--
code/modules/mob/living/silicon/ai/ai_programs.dm | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/code/game/machinery/ai_resource.dm b/code/game/machinery/ai_resource.dm
index e1314639c2a0..b84fb511b2eb 100644
--- a/code/game/machinery/ai_resource.dm
+++ b/code/game/machinery/ai_resource.dm
@@ -99,7 +99,7 @@
change_power_mode(IDLE_POWER_USE)
update_icon(UPDATE_ICON_STATE)
-/obj/machinery/ai_node/proc/change_ai(var/mob/living/silicon/ai/new_ai)
+/obj/machinery/ai_node/proc/change_ai(mob/living/silicon/ai/new_ai)
if(!new_ai)
return
if(!istype(new_ai))
@@ -110,7 +110,7 @@
/datum/milla_safe/ai_node_process
-/datum/milla_safe/ai_node_process/on_run(var/obj/machinery/ai_node/node)
+/datum/milla_safe/ai_node_process/on_run(obj/machinery/ai_node/node)
var/turf/simulated/L = get_turf(node)
if(!istype(L))
return
diff --git a/code/modules/mob/living/silicon/ai/ai_programs.dm b/code/modules/mob/living/silicon/ai/ai_programs.dm
index b7af49251d68..63fd627327df 100644
--- a/code/modules/mob/living/silicon/ai/ai_programs.dm
+++ b/code/modules/mob/living/silicon/ai/ai_programs.dm
@@ -36,7 +36,7 @@
popup.open()
return
-/datum/program_picker/proc/modify_resource(var/key, var/amount)
+/datum/program_picker/proc/modify_resource(key, amount)
if(key == "memory")
memory += amount
if(key == "bandwidth")
From cbde3565bcb41c0a26d707711072dc0e457b723f Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Thu, 16 Jan 2025 22:16:10 -0500
Subject: [PATCH 24/44] Icon fixes
---
code/game/machinery/ai_resource.dm | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/code/game/machinery/ai_resource.dm b/code/game/machinery/ai_resource.dm
index b84fb511b2eb..b6b35528e0a6 100644
--- a/code/game/machinery/ai_resource.dm
+++ b/code/game/machinery/ai_resource.dm
@@ -2,12 +2,13 @@
name = "AI Node"
desc = "If you see me, make an issue report!"
icon = 'icons/obj/machines/ai_machinery.dmi'
- icon_state = "processor"
+ icon_state = "processor-off"
density = TRUE
anchored = TRUE
max_integrity = 100
idle_power_consumption = 5
active_power_consumption = 250
+ var/icon_base
/// Is the machine active
var/active = FALSE
/// What AI is this machine associated with
@@ -21,10 +22,6 @@
/// Are we overheating?
var/overheating = FALSE
-/obj/machinery/ai_node/Initialize(mapload)
- . = ..()
- update_icon(UPDATE_ICON_STATE)
-
/obj/machinery/ai_node/process()
..()
if(active)
@@ -46,9 +43,9 @@
/obj/machinery/ai_node/update_icon_state()
. = ..()
if(overheating)
- icon_state = "[initial(icon_state)]-hot"
+ icon_state = "[icon_base]-hot"
return
- icon_state = "[initial(icon_state)]-[active ? "on" : "off"]"
+ icon_state = "[icon_base]-[active ? "on" : "off"]"
/obj/machinery/ai_node/attack_ai(mob/user)
return
@@ -132,8 +129,9 @@
name = "Processing Node"
desc = "Highly advanced machinery with a manual switch. While running, it grants an AI memory."
icon = 'icons/obj/machines/ai_machinery.dmi'
- icon_state = "processor"
+ icon_state = "processor-off"
resource_key = "memory"
+ icon_base = "processor"
/obj/machinery/ai_node/processing_node/Initialize(mapload)
. = ..()
@@ -150,8 +148,9 @@
name = "Network Node"
desc = "Highly advanced machinery with an on/off switch. While running, it grants an AI bandwidth."
icon = 'icons/obj/machines/ai_machinery.dmi'
- icon_state = "network"
+ icon_state = "network-off"
resource_key = "bandwidth"
+ icon_base = "network"
/obj/machinery/ai_node/network_node/examine_more(mob/user)
. = ..()
From baf6bcf86a30b285a6b65a88d4af4f24036afe14 Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Fri, 17 Jan 2025 18:37:05 -0500
Subject: [PATCH 25/44] Adds overheat counter to delay overheat to prevent
random atmos hotspots from instantly turning it off
---
code/game/machinery/ai_resource.dm | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/code/game/machinery/ai_resource.dm b/code/game/machinery/ai_resource.dm
index b6b35528e0a6..c459d99b573b 100644
--- a/code/game/machinery/ai_resource.dm
+++ b/code/game/machinery/ai_resource.dm
@@ -21,6 +21,8 @@
var/heat_amount = 40000
/// Are we overheating?
var/overheating = FALSE
+ /// Used to ensure it takes a few seconds of being hot before overheating
+ var/overheat_counter = 0
/obj/machinery/ai_node/process()
..()
@@ -123,7 +125,12 @@
// Heat check
if(env.temperature() > 373 || env.temperature() < 273) // If the temperature is outside 0-100C...
// Turn the node off due to temperature problems
- node.Overheat()
+ node.overheat_counter++
+ if(node.overheat_counter >= 5)
+ node.Overheat()
+ return
+ node.overheat_counter--
+
/obj/machinery/ai_node/processing_node
name = "Processing Node"
From deb0dfa4dc6809ab9347900948c23afd72825a61 Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Fri, 17 Jan 2025 18:52:20 -0500
Subject: [PATCH 26/44] They changed my isAI check. Woe.
---
code/modules/mob/living/silicon/ai/ai_programs.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/modules/mob/living/silicon/ai/ai_programs.dm b/code/modules/mob/living/silicon/ai/ai_programs.dm
index 63fd627327df..854cb3f0f689 100644
--- a/code/modules/mob/living/silicon/ai/ai_programs.dm
+++ b/code/modules/mob/living/silicon/ai/ai_programs.dm
@@ -45,7 +45,7 @@
/datum/program_picker/Topic(href, href_list)
..()
- if(!isAI(usr))
+ if(!is_ai(usr))
return
var/mob/living/silicon/ai/A = usr
From 83d734880d95ac70e4a2db7e61f4cc0585ba0ef3 Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Sat, 18 Jan 2025 16:05:51 -0500
Subject: [PATCH 27/44] Sealant, Holosigns, HONK, bugfixes, and stock part
multipliers
---
code/game/machinery/ai_resource.dm | 22 ++-
.../mob/living/silicon/ai/ai_programs.dm | 141 +++++++++++++++++-
2 files changed, 160 insertions(+), 3 deletions(-)
diff --git a/code/game/machinery/ai_resource.dm b/code/game/machinery/ai_resource.dm
index c459d99b573b..8e28c571580e 100644
--- a/code/game/machinery/ai_resource.dm
+++ b/code/game/machinery/ai_resource.dm
@@ -18,11 +18,13 @@
/// How much resources does this machine provide
var/resource_amount = 1
/// How much heat does this put out?
- var/heat_amount = 40000
+ var/heat_amount = 25000
/// Are we overheating?
var/overheating = FALSE
/// Used to ensure it takes a few seconds of being hot before overheating
var/overheat_counter = 0
+ /// How efficient is this machine?
+ var/efficiency = 1
/obj/machinery/ai_node/process()
..()
@@ -52,6 +54,22 @@
/obj/machinery/ai_node/attack_ai(mob/user)
return
+/obj/machinery/ai_node/RefreshParts()
+ . = ..()
+ var/E
+ for(var/obj/item/stock_parts/capacitor/M in component_parts)
+ E += M.rating
+ efficiency = E / 2
+ // Adjust values according to the new stock parts.
+ heat_amount = initial(heat_amount) * efficiency
+ update_idle_power_consumption(power_channel, initial(idle_power_consumption) * efficiency)
+ update_active_power_consumption(power_channel, initial(active_power_consumption) * efficiency)
+ var/old_resource_amount = resource_amount
+ resource_amount = round_down(initial(resource_amount) * efficiency)
+ // Adjust the resources of the connected AI if the machine is on
+ if(assigned_ai)
+ assigned_ai.program_picker.modify_resource(resource_key, (resource_amount - old_resource_amount))
+
/obj/machinery/ai_node/attack_hand(user as mob)
if(overheating)
to_chat(user, "You turn the overheating [src] off.")
@@ -104,7 +122,7 @@
if(!istype(new_ai))
return
assigned_ai.program_picker.modify_resource(resource_key, -resource_amount)
- assigned_ai = new_attack_chain
+ assigned_ai = new_ai
assigned_ai.program_picker.modify_resource(resource_key, resource_amount)
/datum/milla_safe/ai_node_process
diff --git a/code/modules/mob/living/silicon/ai/ai_programs.dm b/code/modules/mob/living/silicon/ai/ai_programs.dm
index 854cb3f0f689..c046ee8ce404 100644
--- a/code/modules/mob/living/silicon/ai/ai_programs.dm
+++ b/code/modules/mob/living/silicon/ai/ai_programs.dm
@@ -488,7 +488,7 @@
upgrade_level++
installed = TRUE
-// RGB Lighting - Recolors Lights
+// Light Synthesizer - Fixes lights
/datum/ai_program/light_repair
program_name = "Light Synthesizer"
program_id = "light_repair"
@@ -664,4 +664,143 @@
to_chat(user, "Discovered innovations has led to an increase in the [current] field!")
upgraded = TRUE
+/datum/spell/ai_spell/research_subsystem/on_purchase_upgrade()
+ cooldown_handler.recharge_duration = max(min(base_cooldown, base_cooldown - (spell_level * 30)), 600 SECONDS)
+
+// Emergency Sealant - Patches holes with metal foam
+/datum/ai_program/research_semergency_sealantubsystem
+ program_name = "Emergency Sealant"
+ program_id = "emergency_sealant"
+ description = "Deploy an area of metal foam to rapidly repair and seal hull breaches."
+ cost = 2
+ nanite_cost = 50
+ power_type = /datum/spell/ai_spell/ranged/emergency_sealant
+ unlock_text = "Metal foam synthesizer online."
+
+/datum/spell/ai_spell/ranged/emergency_sealant
+ name = "Emergency Sealant"
+ desc = "Deploy an area of metal foam to rapidly repair and seal hull breaches."
+ action_icon = 'icons/obj/structures.dmi'
+ action_icon_state = "reinforced"
+ ranged_mousepointer = 'icons/mecha/mecha_mouse.dmi'
+ auto_use_uses = FALSE
+ base_cooldown = 180 SECONDS
+ cooldown_min = 30 SECONDS
+ level_max = 5
+ selection_activated_message = "You fill a canister with metal foam..."
+ selection_deactivated_message = "You dissolve the unused canister."
+
+/datum/spell/ai_spell/ranged/emergency_sealant/cast(list/targets, mob/user)
+ var/target = targets[1]
+ var/mob/living/silicon/ai/AI = user
+ AI.program_picker.nanites -= 50
+ user.playsound_local(user, 'sound/items/deconstruct.ogg', 50, FALSE, use_reverb = FALSE)
+ playsound(target, 'sound/effects/bubbles2.ogg', 50, FALSE, use_reverb = FALSE)
+ var/obj/machinery/camera/C = find_nearest_camera(target)
+ if(!istype(C))
+ return
+ var/obj/effect/particle_effect/foam/metal/F = new /obj/effect/particle_effect/foam/metal(get_turf(target), TRUE)
+ C.Beam(target, icon_state = "rped_upgrade", icon = 'icons/effects/effects.dmi', time = 15)
+ F.spread_amount = 2
+
+/datum/spell/ai_spell/ranged/door_override/on_purchase_upgrade()
+ cooldown_handler.recharge_duration = max(min(base_cooldown, base_cooldown - (spell_level * 30)), 30 SECONDS)
+
+// Holosign Deployment - Deploys a holosign on the selected turf
+/datum/ai_program/holosign_displayer
+ program_name = "Holosign Displayer"
+ program_id = "holosign_displayer"
+ description = "Deploy a holographic sign to alert crewmembers to potential hazards."
+ cost = 1
+ nanite_cost = 10
+ power_type = /datum/spell/ai_spell/ranged/holosign_displayer
+ unlock_text = "Metal foam synthesizer online."
+
+/datum/spell/ai_spell/ranged/holosign_displayer
+ name = "Holosign Displayer"
+ desc = "Deploy a holographic sign to alert crewmembers to potential hazards."
+ action_icon = 'icons/obj/device.dmi'
+ action_icon_state = "signmaker"
+ ranged_mousepointer = 'icons/mecha/mecha_mouse.dmi'
+ auto_use_uses = FALSE
+ base_cooldown = 30 SECONDS
+ cooldown_min = 30 SECONDS
+ level_max = 8
+ selection_activated_message = "You spool up your projector..."
+ selection_deactivated_message = "You stop spooling the projector."
+ /// Types of holosigns the AI can deploy
+ var/sign_choices = list(
+ "Engineering",
+ "Security",
+ "Wet Floor"
+ )
+ /// List of currently active signs
+ var/signs = list()
+
+/datum/spell/ai_spell/ranged/holosign_displayer/cast(list/targets, mob/user)
+ var/sign_id = tgui_input_list(usr, "Select an holosgn!", "AI", sign_choices)
+ if(!sign_id)
+ return
+ var/sign_type = null
+ switch(sign_id)
+ if("Engineering")
+ sign_type = /obj/structure/holosign/barrier/engineering
+ if("Wet Floor")
+ sign_type = /obj/structure/holosign/wetsign
+ if("Security")
+ sign_type = /obj/structure/holosign/barrier
+ else
+ return
+ var/target = targets[1]
+ var/mob/living/silicon/ai/AI = user
+ AI.program_picker.nanites -= 10
+ var/H = new sign_type(get_turf(target), src)
+ addtimer(CALLBACK(src, PROC_REF(sign_timer_complete), H), (60 + 30 * spell_level) SECONDS, TIMER_UNIQUE)
+ user.playsound_local(user, 'sound/machines/click.ogg', 20, FALSE, use_reverb = FALSE)
+ playsound(target, 'sound/machines/click.ogg', 20, FALSE, use_reverb = FALSE)
+ var/obj/machinery/camera/C = find_nearest_camera(target)
+ if(!istype(C))
+ return
+ C.Beam(target, icon_state = "rped_upgrade", icon = 'icons/effects/effects.dmi', time = 5)
+
+/datum/spell/ai_spell/ranged/holosign_displayer/proc/sign_timer_complete(obj/structure/holosign/H)
+ playsound(H.loc, 'sound/machines/chime.ogg', 20, 1)
+ qdel(H)
+
+// HONK Subsystem - HONK
+/datum/ai_program/honk_subsystem
+ program_name = "Honk Subsystem"
+ program_id = "honk_subsystem"
+ description = "Download a program from Clowns.NT to be able to play bike horn sounds on demand."
+ nanite_cost = 5
+ power_type = /datum/spell/ai_spell/ranged/honk_subsystem
+ unlock_text = "Honker.exe installed."
+
+/datum/spell/ai_spell/ranged/honk_subsystem
+ name = "Honk Subsystem"
+ desc = "Download a program from Clowns.NT to be able to play bike horn sounds on demand."
+ action_icon = 'icons/obj/items.dmi'
+ action_icon_state = "bike_horn"
+ ranged_mousepointer = 'icons/mecha/mecha_mouse.dmi'
+ auto_use_uses = FALSE
+ base_cooldown = 30 SECONDS
+ cooldown_min = 5 SECONDS
+ level_max = 10
+ selection_activated_message = "You prepare to honk..."
+ selection_deactivated_message = "You reduce the amount of humor in your subsystems."
+
+/datum/spell/ai_spell/ranged/honk_subsystem/cast(list/targets, mob/user)
+ var/target = targets[1]
+ if(!target)
+ return
+ var/mob/living/silicon/ai/AI = user
+ AI.program_picker.nanites -= 5
+ if(spell_level >= 10)
+ playsound(target, 'sound/items/airhorn.ogg', 100, 1)
+ user.playsound_local(user, 'sound/items/airhorn.ogg', 50, FALSE, use_reverb = FALSE)
+ else
+ playsound(target, 'sound/items/bikehorn.ogg', 50, FALSE, use_reverb = FALSE)
+ user.playsound_local(user, 'sound/items/bikehorn.ogg', 50, FALSE, use_reverb = FALSE)
+/datum/spell/ai_spell/ranged/rgb_lighting/on_purchase_upgrade()
+ cooldown_handler.recharge_duration = max(base_cooldown - (spell_level * 15) SECONDS, 15 SECONDS)
From 3fde6dce0a3263fe7ad5b0dd13a871ff1d24dd88 Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Sat, 18 Jan 2025 17:41:55 -0500
Subject: [PATCH 28/44] Enhanced Tracking Software
---
code/datums/alarm_manager.dm | 3 +-
code/game/machinery/ai_resource.dm | 1 -
code/modules/mob/living/silicon/ai/ai_life.dm | 10 ++++
code/modules/mob/living/silicon/ai/ai_mob.dm | 10 +++-
.../mob/living/silicon/ai/ai_programs.dm | 53 ++++++++++++++++++-
.../modules/mob/living/silicon/silicon_mob.dm | 3 ++
6 files changed, 75 insertions(+), 5 deletions(-)
diff --git a/code/datums/alarm_manager.dm b/code/datums/alarm_manager.dm
index 8c2dbef02796..e7ba0e282b00 100644
--- a/code/datums/alarm_manager.dm
+++ b/code/datums/alarm_manager.dm
@@ -6,7 +6,8 @@ GLOBAL_DATUM_INIT(alarm_manager, /datum/alarm_manager, new())
"Fire" = list(),
"Atmosphere" = list(),
"Power" = list(),
- "Burglar" = list()
+ "Burglar" = list(),
+ "Tracking" = list()
)
/datum/alarm_manager/proc/trigger_alarm(class, area/A, list/O, obj/alarmsource)
diff --git a/code/game/machinery/ai_resource.dm b/code/game/machinery/ai_resource.dm
index 8e28c571580e..32bf15cd888f 100644
--- a/code/game/machinery/ai_resource.dm
+++ b/code/game/machinery/ai_resource.dm
@@ -149,7 +149,6 @@
return
node.overheat_counter--
-
/obj/machinery/ai_node/processing_node
name = "Processing Node"
desc = "Highly advanced machinery with a manual switch. While running, it grants an AI memory."
diff --git a/code/modules/mob/living/silicon/ai/ai_life.dm b/code/modules/mob/living/silicon/ai/ai_life.dm
index 3ba153e83e9c..923b81c6e808 100644
--- a/code/modules/mob/living/silicon/ai/ai_life.dm
+++ b/code/modules/mob/living/silicon/ai/ai_life.dm
@@ -26,6 +26,16 @@
if(istype(machine, /obj/machinery/hologram))
check_holopad_eye()
+ // Enhanced Tracking
+ if(enhanced_tracking)
+ if(tracked_mob)
+ if(can_see(tracked_mob))
+ var/area/A = get_area(tracked_mob)
+ if(A)
+ addtimer(CALLBACK(src, PROC_REF(raise_tracking_alert), A, tracked_mob), enhanced_tracking_delay)
+ // To prevent spam, immediately unset tracking.
+ tracked_mob = null
+
if(malfhack && malfhack.aidisabled)
to_chat(src, "ERROR: APC access disabled, hack attempt canceled.")
deltimer(malfhacking)
diff --git a/code/modules/mob/living/silicon/ai/ai_mob.dm b/code/modules/mob/living/silicon/ai/ai_mob.dm
index 598d7937aa97..1478061a8825 100644
--- a/code/modules/mob/living/silicon/ai/ai_mob.dm
+++ b/code/modules/mob/living/silicon/ai/ai_mob.dm
@@ -80,6 +80,12 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
var/bluespace_miner_rate = 100
/// Time until next payout
var/next_payout = 10 MINUTES
+ /// Do we have the enhanced tracker?
+ var/enhanced_tracking = FALSE
+ /// Who are we tracking with the enhanced tracker?
+ var/mob/tracked_mob
+ /// The current delay on enhanced tracking
+ var/enhanced_tracking_delay = 10 SECONDS
//MALFUNCTION
var/datum/module_picker/malf_picker
@@ -240,7 +246,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
spawn(5)
new /obj/machinery/ai_powersupply(src)
-
+
eyeobj = new /mob/camera/eye/ai(loc, name, src, src)
builtInCamera = new /obj/machinery/camera/portable(src)
@@ -1493,7 +1499,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
/mob/living/silicon/ai/proc/toggle_fast_holograms()
set category = "AI Commands"
set name = "Toggle Fast Holograms"
-
+
if(usr.stat == DEAD || !is_ai_eye(eyeobj))
return
fast_holograms = !fast_holograms
diff --git a/code/modules/mob/living/silicon/ai/ai_programs.dm b/code/modules/mob/living/silicon/ai/ai_programs.dm
index c046ee8ce404..df969d218540 100644
--- a/code/modules/mob/living/silicon/ai/ai_programs.dm
+++ b/code/modules/mob/living/silicon/ai/ai_programs.dm
@@ -81,6 +81,7 @@
return FALSE
memory -= program.cost
SSblackbox.record_feedback("tally", "ai_program_installed", 1, new_spell.name)
+ program.upgrade(A) // Usually does nothing, but is needed for hybrid abilities like the enhanced tracker
A.AddSpell(new_spell)
to_chat(A, program.unlock_text)
A.playsound_local(A, program.unlock_sound, 50, FALSE, use_reverb = FALSE)
@@ -802,5 +803,55 @@
playsound(target, 'sound/items/bikehorn.ogg', 50, FALSE, use_reverb = FALSE)
user.playsound_local(user, 'sound/items/bikehorn.ogg', 50, FALSE, use_reverb = FALSE)
-/datum/spell/ai_spell/ranged/rgb_lighting/on_purchase_upgrade()
+/datum/spell/ai_spell/ranged/honk_subsystem/on_purchase_upgrade()
cooldown_handler.recharge_duration = max(base_cooldown - (spell_level * 15) SECONDS, 15 SECONDS)
+
+// Enhanced Tracking System - Select a target. Get alerted after a delay whenever that target enters camera sight
+/datum/ai_program/enhanced_tracker
+ program_name = "Enhanced Tracking Subsystem"
+ program_id = "enhanced_tracker"
+ description = "New camera firmware allows automated alerts when an individual of interest enters camera view."
+ cost = 5
+ nanite_cost = 0
+ power_type = /datum/spell/ai_spell/enhanced_tracker
+ unlock_text = "Tag and track software online."
+ max_level = 8
+
+/datum/ai_program/enhanced_tracker/upgrade(mob/user)
+ var/mob/living/silicon/ai/AI = user
+ if(!istype(user))
+ return
+ AI.enhanced_tracking = TRUE
+ AI.alarms_listend_for += "Tracking"
+ AI.enhanced_tracking_delay = initial(AI.enhanced_tracking_delay) - (upgrade_level * 2 SECONDS)
+
+/datum/spell/ai_spell/enhanced_tracker
+ name = "Enhanced Tracking Subsystem"
+ desc = "Select a target of interest to be alerted to their presence on cameras."
+ action_icon = 'icons/obj/items.dmi'
+ action_icon_state = "videocam"
+ auto_use_uses = FALSE
+ base_cooldown = 10 SECONDS
+ cooldown_min = 10 SECONDS
+ level_max = 0
+
+/datum/spell/ai_spell/enhanced_tracker/cast(list/targets, mob/living/silicon/ai/user)
+ if(!istype(user))
+ return
+ // Pick a mob to track
+ var/target_name = tgui_input_list(user, "Pick a trackable target...", "AI", user.trackable_mobs())
+ user.tracked_mob = (isnull(user.track.humans[target_name]) ? user.track.others[target_name] : user.track.humans[target_name])
+
+/mob/living/silicon/ai/proc/raise_tracking_alert(area/A, mob/target)
+ var/closest_camera = null
+ for(var/obj/machinery/camera/C in A)
+ if(closest_camera == null)
+ closest_camera = C
+ continue
+ if(get_dist(closest_camera, target) > get_dist(C, target))
+ closest_camera = C
+ continue
+ target.visible_message("A purple light flashes on [closest_camera]!")
+ if(GLOB.alarm_manager.trigger_alarm("Tracking", A, A.cameras, closest_camera))
+ // Cancel alert after 1 minute
+ addtimer(CALLBACK(GLOB.alarm_manager, TYPE_PROC_REF(/datum/alarm_manager, cancel_alarm), "Tracking", A, closest_camera), 1 MINUTES)
diff --git a/code/modules/mob/living/silicon/silicon_mob.dm b/code/modules/mob/living/silicon/silicon_mob.dm
index d7d060ea7b51..d60ecf8f540e 100644
--- a/code/modules/mob/living/silicon/silicon_mob.dm
+++ b/code/modules/mob/living/silicon/silicon_mob.dm
@@ -157,6 +157,9 @@
var/list/msg = list("--- ")
+ if(alarm_types_show["Tracking"])
+ msg += "TRACKING: [alarm_types_show["Tracking"]] alarms detected. - "
+
if(alarm_types_show["Burglar"])
msg += "BURGLAR: [alarm_types_show["Burglar"]] alarms detected. - "
From 48de55e1d52e2eac4a8293e6078a47c840e4d7d3 Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Mon, 20 Jan 2025 00:58:00 -0500
Subject: [PATCH 29/44] Refunds for active programs
---
.../mob/living/silicon/ai/ai_programs.dm | 58 ++++++++++++++++++-
1 file changed, 57 insertions(+), 1 deletion(-)
diff --git a/code/modules/mob/living/silicon/ai/ai_programs.dm b/code/modules/mob/living/silicon/ai/ai_programs.dm
index df969d218540..363c9e0f7174 100644
--- a/code/modules/mob/living/silicon/ai/ai_programs.dm
+++ b/code/modules/mob/living/silicon/ai/ai_programs.dm
@@ -39,8 +39,33 @@
/datum/program_picker/proc/modify_resource(key, amount)
if(key == "memory")
memory += amount
+ if(memory < 0)
+ refund_purchases()
if(key == "bandwidth")
bandwidth += amount
+ if(bandwidth < 0)
+ refund_upgrades()
+
+/datum/program_picker/proc/refund_purchases(mob/user)
+ var/list/potential_losses = possible_programs
+ while(memory < 0)
+ if(!potential_losses)
+ return
+ var/datum/ai_program/program = pick_n_take(potential_losses)
+ if(!program.installed)
+ continue
+ program.uninstall(user)
+
+/datum/program_picker/proc/refund_upgrades(mob/user)
+ var/list/potential_losses = possible_programs
+ while(bandwidth < 0)
+ if(!potential_losses)
+ return
+ var/datum/ai_program/program = pick_n_take(potential_losses)
+ if(!program.upgrade_level)
+ continue
+ while(program.upgrade_level > 0 && bandwidth < 0)
+ program.downgrade(user)
/datum/program_picker/Topic(href, href_list)
..()
@@ -73,6 +98,7 @@
if(aspell.spell_level >= aspell.level_max)
to_chat(A, "This program cannot be upgraded any further.")
aspell.on_purchase_upgrade()
+ program.upgrade(A)
bandwidth--
return TRUE
// No same program found, install
@@ -81,7 +107,7 @@
return FALSE
memory -= program.cost
SSblackbox.record_feedback("tally", "ai_program_installed", 1, new_spell.name)
- program.upgrade(A) // Usually does nothing, but is needed for hybrid abilities like the enhanced tracker
+ program.upgrade(A) // Usually does nothing for actives, but is needed for hybrid abilities like the enhanced tracker
A.AddSpell(new_spell)
to_chat(A, program.unlock_text)
A.playsound_local(A, program.unlock_sound, 50, FALSE, use_reverb = FALSE)
@@ -140,9 +166,39 @@
create_attack_logs = FALSE
/datum/ai_program/proc/upgrade(mob/user)
+ upgrade_level++
+ bandwidth_used++
return
/datum/ai_program/proc/downgrade(mob/user)
+ upgrade_level--
+ var/mob/living/silicon/ai/A = user
+ if(!istype(A))
+ return
+ A.program_picker.bandwidth++
+ if(!upgrade) // Passives need to be handled in their own procs
+ var/datum/spell/ai_spell/removed_spell = new power_type
+ for(var/datum/spell/ai_spell/aspell in A.mob_spell_list)
+ if(removed_spell.type == aspell.type)
+ aspell.name = initial(aspell.name)
+ aspell.spell_level--
+ aspell.on_purchase_upgrade() // It's a downgrade, but we still need to recalculate values
+ return
+
+/datum/ai_program/proc/uninstall(mob/user)
+ upgrade_level = 0
+ installed = FALSE
+ var/mob/living/silicon/ai/A = user
+ if(!istype(A))
+ return
+ A.program_picker.bandwidth += bandwidth_used
+ bandwidth_used = 0
+ A.program_picker.memory += cost
+ if(!upgrade) // Passives need to be handled in their own procs
+ var/datum/spell/ai_spell/removed_spell = new power_type
+ for(var/datum/spell/ai_spell/aspell in A.mob_spell_list)
+ if(removed_spell.type == aspell.type)
+ A.RemoveSpell(aspell)
return
/datum/spell/ai_spell/choose_program/cast(list/targets, mob/living/silicon/ai/user)
From 6e2320ab3dbd44ba6be021eb6c9c4ff4bee52638 Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Tue, 21 Jan 2025 19:29:24 -0500
Subject: [PATCH 30/44] Remaining uninstall functionality
---
.../mob/living/silicon/ai/ai_programs.dm | 137 ++++++++++++------
1 file changed, 95 insertions(+), 42 deletions(-)
diff --git a/code/modules/mob/living/silicon/ai/ai_programs.dm b/code/modules/mob/living/silicon/ai/ai_programs.dm
index 363c9e0f7174..a3dfc114867e 100644
--- a/code/modules/mob/living/silicon/ai/ai_programs.dm
+++ b/code/modules/mob/living/silicon/ai/ai_programs.dm
@@ -1,5 +1,7 @@
// The datum and interface for the AI powers menu
/datum/program_picker
+ /// Associated AI
+ var/mob/living/silicon/ai/assigned_ai
/// List of programs that can be bought
var/list/possible_programs
/// How much memory is available
@@ -10,7 +12,7 @@
var/nanites = 50
/// What is the maximum number of nanites?
var/max_nanites = 100
- // Handles extra information displayed
+ /// Handles extra information displayed
var/temp
/datum/program_picker/New()
@@ -21,6 +23,10 @@
possible_programs += program
/datum/program_picker/proc/use(mob/user)
+ var/mob/living/silicon/ai/A = user
+ if(!istype(A))
+ return
+ assigned_ai = A
var/dat
dat += {"Select program to install: (currently [memory] TB of memory and [bandwidth] TBPS of bandwidth left.)
@@ -31,7 +37,7 @@
dat += "
"
if(temp)
dat += "[temp]"
- var/datum/browser/popup = new(user, "modpicker", "AI Program Menu", 400, 500)
+ var/datum/browser/popup = new(A, "modpicker", "AI Program Menu", 400, 500)
popup.set_content(dat)
popup.open()
return
@@ -46,7 +52,7 @@
if(bandwidth < 0)
refund_upgrades()
-/datum/program_picker/proc/refund_purchases(mob/user)
+/datum/program_picker/proc/refund_purchases()
var/list/potential_losses = possible_programs
while(memory < 0)
if(!potential_losses)
@@ -54,9 +60,9 @@
var/datum/ai_program/program = pick_n_take(potential_losses)
if(!program.installed)
continue
- program.uninstall(user)
+ program.uninstall(assigned_ai)
-/datum/program_picker/proc/refund_upgrades(mob/user)
+/datum/program_picker/proc/refund_upgrades()
var/list/potential_losses = possible_programs
while(bandwidth < 0)
if(!potential_losses)
@@ -65,7 +71,7 @@
if(!program.upgrade_level)
continue
while(program.upgrade_level > 0 && bandwidth < 0)
- program.downgrade(user)
+ program.downgrade(assigned_ai)
/datum/program_picker/Topic(href, href_list)
..()
@@ -99,7 +105,6 @@
to_chat(A, "This program cannot be upgraded any further.")
aspell.on_purchase_upgrade()
program.upgrade(A)
- bandwidth--
return TRUE
// No same program found, install
if(program.cost > memory)
@@ -165,40 +170,44 @@
auto_use_uses = FALSE // This is an infinite ability.
create_attack_logs = FALSE
-/datum/ai_program/proc/upgrade(mob/user)
+/datum/ai_program/proc/upgrade(mob/living/silicon/ai/user)
upgrade_level++
bandwidth_used++
+ if(!istype(user))
+ return
+ user.program_picker.bandwidth--
return
-/datum/ai_program/proc/downgrade(mob/user)
+/datum/ai_program/proc/downgrade(mob/living/silicon/ai/user)
+ if(!istype(user))
+ return
upgrade_level--
- var/mob/living/silicon/ai/A = user
- if(!istype(A))
+ if(!upgrade_level)
+ uninstall(user)
return
- A.program_picker.bandwidth++
+ user.program_picker.bandwidth++
if(!upgrade) // Passives need to be handled in their own procs
var/datum/spell/ai_spell/removed_spell = new power_type
- for(var/datum/spell/ai_spell/aspell in A.mob_spell_list)
+ for(var/datum/spell/ai_spell/aspell in user.mob_spell_list)
if(removed_spell.type == aspell.type)
aspell.name = initial(aspell.name)
aspell.spell_level--
aspell.on_purchase_upgrade() // It's a downgrade, but we still need to recalculate values
return
-/datum/ai_program/proc/uninstall(mob/user)
+/datum/ai_program/proc/uninstall(mob/living/silicon/ai/user)
upgrade_level = 0
installed = FALSE
- var/mob/living/silicon/ai/A = user
- if(!istype(A))
+ if(!istype(user))
return
- A.program_picker.bandwidth += bandwidth_used
+ user.program_picker.bandwidth += bandwidth_used
bandwidth_used = 0
- A.program_picker.memory += cost
+ user.program_picker.memory += cost
if(!upgrade) // Passives need to be handled in their own procs
var/datum/spell/ai_spell/removed_spell = new power_type
- for(var/datum/spell/ai_spell/aspell in A.mob_spell_list)
+ for(var/datum/spell/ai_spell/aspell in user.mob_spell_list)
if(removed_spell.type == aspell.type)
- A.RemoveSpell(aspell)
+ user.RemoveSpell(aspell)
return
/datum/spell/ai_spell/choose_program/cast(list/targets, mob/living/silicon/ai/user)
@@ -392,15 +401,25 @@
unlock_text = "Universal adapter installation complete!"
upgrade = TRUE
-/datum/ai_program/universal_adapter/upgrade(mob/user)
- var/mob/living/silicon/ai/AI = user
+/datum/ai_program/universal_adapter/upgrade(mob/living/silicon/ai/user)
if(!istype(user))
return
- AI.universal_adapter = TRUE
- AI.adapter_efficiency = 0.5 + (0.1 * upgrade_level)
- upgrade_level++
+ user.universal_adapter = TRUE
+ user.adapter_efficiency = 0.5 + (0.1 * upgrade_level)
installed = TRUE
+/datum/ai_program/universal_adapter/downgrade(mob/living/silicon/ai/user)
+ ..()
+ if(!istype(user))
+ return
+ user.adapter_efficiency = 0.5 + (0.1 * upgrade_level)
+
+/datum/ai_program/universal_adapter/uninstall(mob/living/silicon/ai/user)
+ ..()
+ if(!istype(user))
+ return
+ user.adapter_efficiency = 0.5
+
// Door Override - Repairs door wires if the AI wire is not cut
/datum/ai_program/door_override
program_name = "Door Override"
@@ -514,16 +533,26 @@
unlock_text = "Bluespace miner installation complete!"
upgrade = TRUE
-/datum/ai_program/bluespace_miner/upgrade(mob/user)
- var/mob/living/silicon/ai/AI = user
+/datum/ai_program/bluespace_miner/upgrade(mob/living/silicon/ai/user)
if(!istype(user))
return
- AI.bluespace_miner_rate = 100 + (100 * upgrade_level)
- AI.next_payout = 10 MINUTES + world.time
- AI.bluespace_miner = TRUE
- upgrade_level++
+ user.bluespace_miner_rate = 100 + (100 * upgrade_level)
+ user.next_payout = 10 MINUTES + world.time
+ user.bluespace_miner = TRUE
installed = TRUE
+/datum/ai_program/bluespace_miner/downgrade(mob/living/silicon/ai/user)
+ ..()
+ if(!istype(user))
+ return
+ user.bluespace_miner_rate = 100 + (100 * upgrade_level)
+
+/datum/ai_program/bluespace_miner/uninstall(mob/living/silicon/ai/user)
+ ..()
+ if(!istype(user))
+ return
+ user.bluespace_miner_rate = 100
+
// Multimarket Analysis Subsystem: Reduce prices of things at cargo
/datum/ai_program/multimarket_analyser
program_name = "Multimarket Analysis Subsystem"
@@ -537,14 +566,22 @@
var/original_price_mod
/datum/ai_program/multimarket_analyser/New()
- . = ..()
+ ..()
original_price_mod = SSeconomy.pack_price_modifier
-/datum/ai_program/multimarket_analyser/upgrade(mob/user)
+/datum/ai_program/multimarket_analyser/upgrade(mob/living/silicon/ai/user)
SSeconomy.pack_price_modifier = original_price_mod * (0.95 - (0.05 * upgrade_level))
upgrade_level++
installed = TRUE
+/datum/ai_program/multimarket_analyser/downgrade(mob/living/silicon/ai/user)
+ ..()
+ SSeconomy.pack_price_modifier = original_price_mod * (0.95 - (0.05 * upgrade_level))
+
+/datum/ai_program/multimarket_analyser/uninstall(mob/living/silicon/ai/user)
+ ..()
+ SSeconomy.pack_price_modifier = original_price_mod
+
// Light Synthesizer - Fixes lights
/datum/ai_program/light_repair
program_name = "Light Synthesizer"
@@ -647,14 +684,21 @@
/// Track the original delay
var/original_door_delay = 3 SECONDS
-/datum/ai_program/enhanced_doors/upgrade(mob/user)
- var/mob/living/silicon/ai/AI = user
+/datum/ai_program/enhanced_doors/upgrade(mob/living/silicon/ai/user)
if(!istype(user))
return
- upgrade_level++
- AI.door_bolt_delay = original_door_delay * (1 - (upgrade_level * 0.1))
+ user.door_bolt_delay = original_door_delay * (1 - (upgrade_level * 0.1))
installed = TRUE
+/datum/ai_program/enhanced_doors/downgrade(mob/living/silicon/ai/user)
+ ..()
+ user.door_bolt_delay = original_door_delay * (1 - (upgrade_level * 0.1))
+
+/datum/ai_program/enhanced_doors/uninstall(mob/living/silicon/ai/user)
+ ..()
+ user.door_bolt_delay = original_door_delay
+
+
// Experimental Research Subsystem - Knowledge is power
/datum/ai_program/research_subsystem
program_name = "Experimental Research Subsystem"
@@ -873,13 +917,22 @@
unlock_text = "Tag and track software online."
max_level = 8
-/datum/ai_program/enhanced_tracker/upgrade(mob/user)
- var/mob/living/silicon/ai/AI = user
+/datum/ai_program/enhanced_tracker/upgrade(mob/living/silicon/ai/user)
if(!istype(user))
return
- AI.enhanced_tracking = TRUE
- AI.alarms_listend_for += "Tracking"
- AI.enhanced_tracking_delay = initial(AI.enhanced_tracking_delay) - (upgrade_level * 2 SECONDS)
+ user.enhanced_tracking = TRUE
+ user.alarms_listend_for += "Tracking"
+ user.enhanced_tracking_delay = initial(user.enhanced_tracking_delay) - (upgrade_level * 2 SECONDS)
+
+/datum/ai_program/enhanced_tracker/downgrade(mob/living/silicon/ai/user)
+ ..()
+ user.enhanced_tracking_delay = initial(user.enhanced_tracking_delay) - (upgrade_level * 2 SECONDS)
+
+/datum/ai_program/enhanced_tracker/uninstall(mob/living/silicon/ai/user)
+ ..()
+ user.enhanced_tracking = FALSE
+ user.alarms_listend_for -= "Tracking"
+ user.enhanced_tracking_delay = initial(user.enhanced_tracking_delay)
/datum/spell/ai_spell/enhanced_tracker
name = "Enhanced Tracking Subsystem"
From e76b818ef6acf5e932098072a300e91d224dbf41 Mon Sep 17 00:00:00 2001
From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Date: Tue, 21 Jan 2025 20:01:49 -0500
Subject: [PATCH 31/44] AI RMC
---
_maps/map_files/generic/centcomm.dmm | 2 +-
_maps/map_files/stations/boxstation.dmm | 49 +++++++-----
_maps/map_files/stations/cerestation.dmm | 17 +++--
_maps/map_files/stations/deltastation.dmm | 8 +-
_maps/map_files/stations/emeraldstation.dmm | 18 +++--
_maps/map_files/stations/metastation.dmm | 18 +++--
code/game/machinery/ai_resource.dm | 71 +++++++++++++++++-
.../mob/living/silicon/ai/ai_programs.dm | 6 ++
icons/obj/computer.dmi | Bin 124953 -> 138407 bytes
icons/obj/machines/ai_machinery.dmi | Bin 5240 -> 4476 bytes
10 files changed, 144 insertions(+), 45 deletions(-)
diff --git a/_maps/map_files/generic/centcomm.dmm b/_maps/map_files/generic/centcomm.dmm
index f0648fbd6ca3..a576c611e907 100644
--- a/_maps/map_files/generic/centcomm.dmm
+++ b/_maps/map_files/generic/centcomm.dmm
@@ -5180,7 +5180,6 @@
/turf/simulated/floor/transparent/glass,
/area/tdome/tdomeobserve)
"rt" = (
-/obj/machinery/photocopier,
/obj/machinery/door_control/no_emag{
id = "sec";
name = "CentCom Security Shutters";
@@ -5203,6 +5202,7 @@
pixel_y = 30;
req_access = list(109)
},
+/obj/machinery/computer/ai_resource,
/turf/simulated/floor/wood,
/area/centcom/control)
"ru" = (
diff --git a/_maps/map_files/stations/boxstation.dmm b/_maps/map_files/stations/boxstation.dmm
index b029b6f90a04..7f8762402c3a 100644
--- a/_maps/map_files/stations/boxstation.dmm
+++ b/_maps/map_files/stations/boxstation.dmm
@@ -32411,7 +32411,7 @@
dir = 8
},
/turf/simulated/floor/plasteel{
- dir = 10;
+ dir = 8;
icon_state = "darkpurple"
},
/area/station/command/office/rd)
@@ -46008,6 +46008,8 @@
/area/station/maintenance/asmaint)
"dlD" = (
/obj/effect/decal/cleanable/dirt,
+/obj/structure/table/wood,
+/obj/machinery/bottler,
/turf/simulated/floor/plating,
/area/station/maintenance/asmaint2)
"dlK" = (
@@ -56997,11 +56999,6 @@
},
/turf/simulated/floor/plasteel,
/area/station/maintenance/assembly_line)
-"ifT" = (
-/obj/structure/table/wood,
-/obj/machinery/bottler,
-/turf/simulated/floor/wood,
-/area/station/maintenance/asmaint2)
"ifY" = (
/obj/machinery/door/airlock/external{
id_tag = "eng_door_ext";
@@ -59905,6 +59902,14 @@
},
/turf/simulated/floor/carpet/grimey,
/area/station/security/detective)
+"jIA" = (
+/obj/structure/cable{
+ icon_state = "4-8"
+ },
+/turf/simulated/floor/plasteel{
+ icon_state = "dark"
+ },
+/area/station/command/office/rd)
"jIX" = (
/obj/effect/decal/cleanable/dirt,
/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers,
@@ -74958,6 +74963,17 @@
/obj/effect/spawner/window/reinforced/grilled,
/turf/simulated/floor/plating,
/area/station/engineering/atmos)
+"rpn" = (
+/obj/structure/closet/secure_closet/rd,
+/obj/machinery/light_switch{
+ pixel_x = -24;
+ dir = 4
+ },
+/turf/simulated/floor/plasteel{
+ dir = 10;
+ icon_state = "darkpurple"
+ },
+/area/station/command/office/rd)
"rqq" = (
/obj/machinery/atmospherics/pipe/simple/hidden/supply{
dir = 5
@@ -78153,8 +78169,7 @@
/area/station/engineering/transmission_laser)
"ten" = (
/turf/simulated/floor/plasteel{
- dir = 8;
- icon_state = "darkpurplecorners"
+ icon_state = "dark"
},
/area/station/command/office/rd)
"ter" = (
@@ -82351,17 +82366,13 @@
/turf/simulated/floor/plasteel,
/area/station/hallway/secondary/entry/south)
"vwC" = (
-/obj/structure/closet/secure_closet/rd,
-/obj/machinery/light_switch{
- dir = 4;
- name = "west bump";
- pixel_x = -24
- },
/obj/machinery/keycard_auth{
pixel_y = -26
},
+/obj/machinery/computer/ai_resource{
+ dir = 1
+ },
/turf/simulated/floor/plasteel{
- dir = 10;
icon_state = "darkpurple"
},
/area/station/command/office/rd)
@@ -133000,7 +133011,7 @@ bYj
bYj
bYj
bYj
-ifT
+bYj
cbd
cbd
cbd
@@ -133256,8 +133267,8 @@ bIa
tGg
cjN
clq
-bYj
-bYj
+mMB
+rpn
cbd
cmV
yjv
@@ -133513,7 +133524,7 @@ wwZ
iDL
fPy
ten
-mMB
+jIA
vwC
cbd
vIO
diff --git a/_maps/map_files/stations/cerestation.dmm b/_maps/map_files/stations/cerestation.dmm
index 4e2c464e12be..b9682eba79ca 100644
--- a/_maps/map_files/stations/cerestation.dmm
+++ b/_maps/map_files/stations/cerestation.dmm
@@ -31920,16 +31920,13 @@
},
/area/station/service/theatre)
"faF" = (
-/obj/structure/rack,
-/obj/item/aicard,
-/obj/item/circuitboard/aicore{
- pixel_x = -2;
- pixel_y = 4
- },
/obj/effect/turf_decal/stripes/line{
dir = 8
},
/obj/machinery/alarm/directional/south,
+/obj/machinery/computer/ai_resource{
+ dir = 1
+ },
/turf/simulated/floor/plasteel/white,
/area/station/command/office/rd)
"faM" = (
@@ -80536,6 +80533,12 @@
pixel_x = 28;
name = "custom placement"
},
+/obj/structure/rack,
+/obj/item/circuitboard/aicore{
+ pixel_x = -2;
+ pixel_y = 4
+ },
+/obj/item/aicard,
/turf/simulated/floor/plasteel/white,
/area/station/command/office/rd)
"rEx" = (
@@ -96483,7 +96486,6 @@
},
/area/station/science/toxins/mixing)
"vwB" = (
-/obj/structure/rack,
/obj/item/paicard{
pixel_x = 4
},
@@ -96491,6 +96493,7 @@
pixel_x = -3
},
/obj/machinery/firealarm/directional/south,
+/obj/structure/rack,
/turf/simulated/floor/plasteel/white,
/area/station/command/office/rd)
"vxm" = (
diff --git a/_maps/map_files/stations/deltastation.dmm b/_maps/map_files/stations/deltastation.dmm
index 8ba30d96109a..cec07877679f 100644
--- a/_maps/map_files/stations/deltastation.dmm
+++ b/_maps/map_files/stations/deltastation.dmm
@@ -49451,11 +49451,11 @@
/turf/simulated/floor/plasteel/white,
/area/station/maintenance/port2)
"doJ" = (
-/obj/structure/table/reinforced,
-/obj/item/clipboard,
-/obj/item/toy/figure/crew/rd,
/obj/machinery/requests_console/directional/west,
/obj/effect/turf_decal/delivery/hollow,
+/obj/machinery/computer/ai_resource{
+ dir = 4
+ },
/turf/simulated/floor/plasteel{
dir = 8;
icon_state = "whitepurplecorner"
@@ -51430,6 +51430,8 @@
dir = 8
},
/obj/effect/turf_decal/delivery/hollow,
+/obj/item/toy/figure/crew/rd,
+/obj/item/clipboard,
/turf/simulated/floor/plasteel{
dir = 4;
icon_state = "whitepurplecorner"
diff --git a/_maps/map_files/stations/emeraldstation.dmm b/_maps/map_files/stations/emeraldstation.dmm
index a696e6911a66..aec95c77ab32 100644
--- a/_maps/map_files/stations/emeraldstation.dmm
+++ b/_maps/map_files/stations/emeraldstation.dmm
@@ -13443,14 +13443,10 @@
},
/area/station/maintenance/apmaint)
"cHe" = (
-/obj/structure/table/glass,
-/obj/item/paper_bin/nanotrasen,
-/obj/item/pen/rd,
-/obj/item/stamp/rd{
- pixel_x = 5;
- pixel_y = -2
- },
/obj/machinery/newscaster/directional/south,
+/obj/machinery/computer/ai_resource{
+ dir = 1
+ },
/turf/simulated/floor/plasteel{
dir = 6;
icon_state = "darkpurple"
@@ -111441,7 +111437,6 @@
/turf/simulated/floor/plasteel,
/area/station/hallway/primary/central/sw)
"waz" = (
-/obj/structure/rack,
/obj/item/aicard,
/obj/item/paicard{
pixel_x = 4
@@ -111451,6 +111446,7 @@
pixel_y = 4
},
/obj/machinery/requests_console/directional/south,
+/obj/structure/rack,
/turf/simulated/floor/plasteel{
dir = 10;
icon_state = "darkpurple"
@@ -121706,6 +121702,12 @@
pixel_x = 4
},
/obj/item/paper/monitorkey,
+/obj/item/stamp/rd{
+ pixel_x = 5;
+ pixel_y = -2
+ },
+/obj/item/pen/rd,
+/obj/item/paper_bin/nanotrasen,
/turf/simulated/floor/plasteel{
dir = 4;
icon_state = "darkpurple"
diff --git a/_maps/map_files/stations/metastation.dmm b/_maps/map_files/stations/metastation.dmm
index 8a791462bad9..5ba6d87f6563 100644
--- a/_maps/map_files/stations/metastation.dmm
+++ b/_maps/map_files/stations/metastation.dmm
@@ -34655,14 +34655,13 @@
},
/area/station/service/chapel)
"cGF" = (
-/obj/structure/table,
-/obj/item/aicard,
-/obj/item/paicard,
-/obj/item/circuitboard/aicore,
/obj/machinery/keycard_auth{
pixel_x = -5;
pixel_y = 26
},
+/obj/machinery/computer/ai_resource{
+ dir = 4
+ },
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "whitepurple"
@@ -61388,6 +61387,15 @@
icon_state = "dark"
},
/area/station/engineering/atmos)
+"mEA" = (
+/obj/structure/table,
+/obj/item/circuitboard/aicore,
+/obj/item/paicard,
+/obj/item/aicard,
+/turf/simulated/floor/plasteel{
+ icon_state = "darkgreycheck"
+ },
+/area/station/command/office/rd)
"mEW" = (
/obj/structure/disposalpipe/segment,
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,
@@ -120291,7 +120299,7 @@ cte
dcX
cte
cmB
-cNk
+mEA
nFh
aZr
nlS
diff --git a/code/game/machinery/ai_resource.dm b/code/game/machinery/ai_resource.dm
index 32bf15cd888f..f5e16661224e 100644
--- a/code/game/machinery/ai_resource.dm
+++ b/code/game/machinery/ai_resource.dm
@@ -150,7 +150,7 @@
node.overheat_counter--
/obj/machinery/ai_node/processing_node
- name = "Processing Node"
+ name = "processing node"
desc = "Highly advanced machinery with a manual switch. While running, it grants an AI memory."
icon = 'icons/obj/machines/ai_machinery.dmi'
icon_state = "processor-off"
@@ -169,7 +169,7 @@
RefreshParts()
/obj/machinery/ai_node/network_node
- name = "Network Node"
+ name = "network node"
desc = "Highly advanced machinery with an on/off switch. While running, it grants an AI bandwidth."
icon = 'icons/obj/machines/ai_machinery.dmi'
icon_state = "network-off"
@@ -193,3 +193,70 @@
component_parts += new /obj/item/stack/sheet/mineral/diamond(null, 1)
component_parts += new /obj/item/stack/cable_coil(null, 5)
RefreshParts()
+
+/obj/machinery/computer/ai_resource
+ name = "AI resource control console"
+ desc = "Used to reassign memory and bandwidth between multiple AI units."
+ icon = 'icons/obj/computer.dmi'
+ icon_keyboard = "tech_key"
+ icon_screen = "ai_resource"
+ req_access = list(ACCESS_RD)
+ circuit = /obj/item/circuitboard/ai_resource_console
+ light_color = LIGHT_COLOR_PURPLE
+
+/obj/machinery/computer/ai_resource/attack_ai(mob/user as mob) // Bad AI, no access to stealing resources
+ return
+
+/obj/machinery/computer/ai_resource/attack_hand(mob/user as mob)
+ if(..())
+ return
+ if(stat & (NOPOWER|BROKEN))
+ return
+ ui_interact(user)
+
+/obj/machinery/computer/ai_resource/proc/is_authenticated(mob/user)
+ if(!istype(user))
+ return FALSE
+ if(user.can_admin_interact())
+ return TRUE
+ if(allowed(user))
+ return TRUE
+ return FALSE
+
+/obj/machinery/computer/ai_resource/ui_state(mob/user)
+ return GLOB.default_state
+
+/obj/machinery/computer/ai_resource/ui_interact(mob/user, datum/tgui/ui = null)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "RoboticsControlConsole", name)
+ ui.open()
+
+/obj/machinery/computer/ai_resource/ui_data(mob/user)
+ var/list/data = list()
+ data["auth"] = is_authenticated(user)
+ data["ais"] = list()
+ for(var/mob/living/silicon/ai/A in GLOB.ai_list)
+ if(!console_shows(A))
+ continue
+ var/list/ai_data = list(
+ name = A.name,
+ uid = A.UID(),
+ memory = A.program_picker.memory,
+ memory_max = A.program_picker.total_memory,
+ bandwidth = A.program_picker.bandwidth,
+ bandwidth_max = A.program_picker.total_bandwidth
+ )
+ data["ais"] += list(ai_data)
+ return data
+
+/obj/machinery/computer/ai_resource/ui_act(action, params)
+ if(..())
+ return
+ . = FALSE
+ if(!is_authenticated(usr))
+ to_chat(usr, "Access denied.")
+ return
+ if(SSticker.current_state == GAME_STATE_FINISHED)
+ to_chat(usr, "Access denied, AIs are no longer your station's property.")
+ return
diff --git a/code/modules/mob/living/silicon/ai/ai_programs.dm b/code/modules/mob/living/silicon/ai/ai_programs.dm
index a3dfc114867e..2e38e6fe00ae 100644
--- a/code/modules/mob/living/silicon/ai/ai_programs.dm
+++ b/code/modules/mob/living/silicon/ai/ai_programs.dm
@@ -6,8 +6,12 @@
var/list/possible_programs
/// How much memory is available
var/memory = 1
+ /// How much total memory does the system have?
+ var/total_memory = 1
/// How much bandwidth is available
var/bandwidth = 1
+ /// How much total bandwidth does the system have?
+ var/total_bandwidth = 1
/// How many nanites are available?
var/nanites = 50
/// What is the maximum number of nanites?
@@ -45,10 +49,12 @@
/datum/program_picker/proc/modify_resource(key, amount)
if(key == "memory")
memory += amount
+ total_memory += amount
if(memory < 0)
refund_purchases()
if(key == "bandwidth")
bandwidth += amount
+ total_bandwidth += amount
if(bandwidth < 0)
refund_upgrades()
diff --git a/icons/obj/computer.dmi b/icons/obj/computer.dmi
index a9095781e6538afb70fd082a27a2b7b83de738e3..40822d164daba54d43ff1a90bccd2064431b917d 100644
GIT binary patch
literal 138407
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