diff --git a/.vscode/launch.json b/.vscode/launch.json
index 543058728f56..fbf8bfba5851 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -6,7 +6,8 @@
"request": "launch",
"name": "Launch DreamSeeker",
"preLaunchTask": "Build All",
- "dmb": "${workspaceFolder}/${command:CurrentDMB}"
+ "dmb": "${workspaceFolder}/${command:CurrentDMB}",
+ "dreamDaemon": true
}
]
}
diff --git a/_maps/map_files/YogStation/YogStation.dmm b/_maps/map_files/YogStation/YogStation.dmm
index b3bba9670734..e56aad908d9b 100644
--- a/_maps/map_files/YogStation/YogStation.dmm
+++ b/_maps/map_files/YogStation/YogStation.dmm
@@ -37589,6 +37589,15 @@
},
/turf/open/floor/plating,
/area/hallway/secondary/exit)
+"fOU" = (
+/obj/structure/cable/yellow{
+ icon_state = "1-2"
+ },
+/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4,
+/obj/machinery/computer/ai_resource_distribution,
+/turf/open/floor/plasteel/dark,
+/area/ai_monitored/turret_protected/aisat_interior)
"fPF" = (
/obj/effect/turf_decal/delivery,
/obj/structure/noticeboard{
@@ -37735,13 +37744,6 @@
/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden/layer4,
/turf/open/floor/plasteel,
/area/hallway/secondary/entry)
-"fXq" = (
-/obj/machinery/light,
-/obj/effect/turf_decal/tile/darkblue{
- dir = 8
- },
-/turf/open/floor/plasteel/dark,
-/area/ai_monitored/turret_protected/aisat_interior)
"fXK" = (
/obj/effect/turf_decal/stripes,
/obj/structure/railing/corner,
@@ -43511,12 +43513,6 @@
},
/turf/open/floor/plating,
/area/storage/tech)
-"jOp" = (
-/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{
- dir = 4
- },
-/turf/open/floor/plasteel/dark,
-/area/ai_monitored/turret_protected/aisat_interior)
"jOV" = (
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4{
dir = 4
@@ -43821,6 +43817,12 @@
dir = 1
},
/area/hallway/secondary/entry)
+"jXT" = (
+/obj/machinery/status_display/ai{
+ pixel_x = 32
+ },
+/turf/open/floor/circuit,
+/area/ai_monitored/turret_protected/aisat_interior)
"jXZ" = (
/obj/machinery/atmospherics/pipe/simple/orange/visible{
dir = 5
@@ -44041,17 +44043,6 @@
},
/turf/open/floor/plating,
/area/maintenance/starboard/aft)
-"keO" = (
-/obj/machinery/atmospherics/pipe/manifold4w/supply/hidden/layer2,
-/obj/machinery/atmospherics/pipe/manifold4w/scrubbers/hidden/layer4,
-/obj/structure/cable/yellow{
- icon_state = "1-2"
- },
-/obj/structure/cable/yellow{
- icon_state = "1-4"
- },
-/turf/open/floor/circuit,
-/area/ai_monitored/turret_protected/aisat_interior)
"kfj" = (
/obj/machinery/computer/station_alert{
dir = 4
@@ -44774,6 +44765,19 @@
/obj/machinery/door/airlock/maintenance_hatch,
/turf/open/floor/plating,
/area/maintenance/aft)
+"kGm" = (
+/obj/machinery/light{
+ dir = 1
+ },
+/obj/effect/turf_decal/tile/darkblue{
+ dir = 1
+ },
+/obj/structure/sign/departments/minsky/command/charge{
+ pixel_y = 32
+ },
+/obj/machinery/ai/expansion_card_holder,
+/turf/open/floor/plasteel/dark,
+/area/ai_monitored/turret_protected/aisat_interior)
"kGo" = (
/obj/machinery/light/small{
dir = 1
@@ -44869,6 +44873,14 @@
/obj/effect/turf_decal/tile/neutral,
/turf/open/floor/plasteel/dark/telecomms,
/area/tcommsat/server)
+"kJS" = (
+/obj/machinery/light,
+/obj/effect/turf_decal/tile/darkblue{
+ dir = 8
+ },
+/obj/machinery/ai/expansion_card_holder,
+/turf/open/floor/plasteel/dark,
+/area/ai_monitored/turret_protected/aisat_interior)
"kJW" = (
/obj/effect/spawner/structure/window/reinforced,
/obj/machinery/door/firedoor/border_only{
@@ -48268,6 +48280,18 @@
},
/turf/open/floor/plating,
/area/bridge)
+"nbi" = (
+/obj/machinery/atmospherics/pipe/manifold4w/supply/hidden/layer2,
+/obj/machinery/atmospherics/pipe/manifold4w/scrubbers/hidden/layer4,
+/obj/structure/cable/yellow{
+ icon_state = "1-2"
+ },
+/obj/structure/cable/yellow{
+ icon_state = "1-4"
+ },
+/obj/machinery/ai/data_core/primary,
+/turf/open/floor/circuit,
+/area/ai_monitored/turret_protected/aisat_interior)
"nbu" = (
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4,
/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2,
@@ -51034,6 +51058,13 @@
},
/turf/open/floor/plasteel/dark,
/area/ai_monitored/security/armory)
+"oKe" = (
+/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{
+ dir = 4
+ },
+/obj/item/memory_card,
+/turf/open/floor/plasteel/dark,
+/area/ai_monitored/turret_protected/aisat_interior)
"oKv" = (
/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden/layer4{
dir = 1
@@ -51351,14 +51382,6 @@
},
/turf/open/floor/plating,
/area/maintenance/starboard/fore)
-"oTV" = (
-/obj/structure/cable/yellow{
- icon_state = "1-2"
- },
-/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2,
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4,
-/turf/open/floor/plasteel/dark,
-/area/ai_monitored/turret_protected/aisat_interior)
"oTW" = (
/obj/machinery/light{
dir = 1
@@ -52633,6 +52656,10 @@
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4,
/turf/open/floor/plating,
/area/ai_monitored/storage/satellite)
+"pPN" = (
+/obj/item/processing_card,
+/turf/open/floor/plasteel/dark,
+/area/ai_monitored/turret_protected/aisat_interior)
"pQy" = (
/obj/machinery/door/airlock/public/glass{
name = "Escape Podbay"
@@ -61721,18 +61748,6 @@
},
/turf/open/floor/plasteel/white,
/area/medical/sleeper)
-"vZa" = (
-/obj/machinery/light{
- dir = 1
- },
-/obj/effect/turf_decal/tile/darkblue{
- dir = 1
- },
-/obj/structure/sign/departments/minsky/command/charge{
- pixel_y = 32
- },
-/turf/open/floor/plasteel/dark,
-/area/ai_monitored/turret_protected/aisat_interior)
"vZn" = (
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{
dir = 4
@@ -64728,15 +64743,6 @@
/obj/machinery/door/airlock/maintenance_hatch,
/turf/open/floor/plating,
/area/maintenance/starboard/aft)
-"yeT" = (
-/obj/machinery/status_display/ai{
- pixel_x = 32
- },
-/obj/machinery/porta_turret/ai{
- scan_range = 4
- },
-/turf/open/floor/circuit,
-/area/ai_monitored/turret_protected/aisat_interior)
"yfe" = (
/obj/structure/table,
/obj/item/shard{
@@ -105808,11 +105814,11 @@ fSL
piS
tmG
pnH
-vZa
-lZD
+kGm
+pPN
vpQ
-jOp
-fXq
+oKe
+kJS
nnx
fgc
gHO
@@ -106065,9 +106071,9 @@ nWL
umC
pPs
pGB
-oTV
+fOU
bEI
-keO
+nbi
cVj
wjG
pbT
@@ -106322,11 +106328,11 @@ gEh
igK
xPv
pnH
-yeT
+jXT
ykC
lMF
pBu
-yeT
+jXT
nnx
mZe
lhR
diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm
index 98636a4ecfd5..28e489428a51 100644
--- a/code/_onclick/hud/_defines.dm
+++ b/code/_onclick/hud/_defines.dm
@@ -123,6 +123,7 @@
// AI
#define ui_ai_core "SOUTH:6,WEST"
+#define ui_ai_dashboard "SOUTH+1:6,WEST"
#define ui_ai_camera_list "SOUTH:6,WEST+1"
#define ui_ai_track_with_camera "SOUTH:6,WEST+2"
#define ui_ai_camera_light "SOUTH:6,WEST+3"
@@ -139,6 +140,7 @@
#define ui_ai_sensor "SOUTH:6,WEST+14"
#define ui_ai_multicam "SOUTH+1:6,WEST+13"
#define ui_ai_add_multicam "SOUTH+1:6,WEST+14"
+#define ui_ai_language_menu "SOUTH+1:8,WEST+11:30"
// pAI
diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm
index 49bdd3f3c047..5817d8995350 100644
--- a/code/_onclick/hud/ai.dm
+++ b/code/_onclick/hud/ai.dm
@@ -66,6 +66,16 @@
var/mob/living/silicon/ai/AI = usr
AI.ai_roster()
+/obj/screen/ai/dashboard
+ name = "Processing Dashboard"
+ icon_state = "dashboard"
+
+/obj/screen/ai/dashboard/Click()
+ if(..())
+ return
+ var/mob/living/silicon/ai/AI = usr
+ AI.dashboard.ui_interact(AI)
+
/obj/screen/ai/alerts
name = "Show Alerts"
icon_state = "alerts"
@@ -194,7 +204,7 @@
// Language menu
using = new /obj/screen/language_menu
- using.screen_loc = ui_borg_language_menu
+ using.screen_loc = ui_ai_language_menu
static_inventory += using
//AI core
@@ -202,6 +212,11 @@
using.screen_loc = ui_ai_core
static_inventory += using
+//Dashboard
+ using = new /obj/screen/ai/dashboard
+ using.screen_loc = ui_ai_dashboard
+ static_inventory += using
+
//Camera list
using = new /obj/screen/ai/camera_list()
using.screen_loc = ui_ai_camera_list
diff --git a/code/modules/jobs/job_types/ai.dm b/code/modules/jobs/job_types/ai.dm
index f4d2d1a88f5a..77c5ca22f1fb 100644
--- a/code/modules/jobs/job_types/ai.dm
+++ b/code/modules/jobs/job_types/ai.dm
@@ -25,7 +25,9 @@
/datum/job/ai/after_spawn(mob/H, mob/M, latejoin)
. = ..()
+ /*
if(latejoin)
+
var/obj/structure/AIcore/latejoin_inactive/lateJoinCore
for(var/obj/structure/AIcore/latejoin_inactive/P in GLOB.latejoin_ai_cores)
if(P.is_available())
@@ -36,7 +38,11 @@
lateJoinCore.available = FALSE
H.forceMove(lateJoinCore.loc)
qdel(lateJoinCore)
+ */
var/mob/living/silicon/ai/AI = H
+
+ AI.relocate(TRUE)
+
AI.apply_pref_name("ai", M.client) //If this runtimes oh well jobcode is fucked.
AI.set_core_display_icon(null, M.client)
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index e8e82e3dfe1d..498f37162e5c 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -38,7 +38,7 @@
var/can_be_carded = TRUE
var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list(), "Burglar"=list())
var/viewalerts = 0
- var/icon/holo_icon//Default is assigned when AI is created.
+ var/icon/holo_icon //Default is assigned when AI is created.
var/obj/mecha/controlled_mech //For controlled_mech a mech, to determine whether to relaymove or use the AI eye.
var/radio_enabled = TRUE //Determins if a carded AI can speak with its built in radio or not.
radiomod = ";" //AIs will, by default, state their laws on the internal radio.
@@ -99,10 +99,12 @@
var/datum/robot_control/robot_control
+ var/datum/ai_dashboard/dashboard
+
/mob/living/silicon/ai/Initialize(mapload, datum/ai_laws/L, mob/target_ai)
. = ..()
if(!target_ai) //If there is no player/brain inside.
- new/obj/structure/AIcore/deactivated(loc) //New empty terminal.
+ // new/obj/structure/AIcore/deactivated(loc) //New empty terminal.
return INITIALIZE_HINT_QDEL //Delete AI.
if(L && istype(L, /datum/ai_laws))
@@ -158,6 +160,8 @@
deploy_action.Grant(src)
+ dashboard = new(src)
+
if(isturf(loc))
add_verb(src, list(/mob/living/silicon/ai/proc/ai_network_change, \
/mob/living/silicon/ai/proc/ai_statuschange, /mob/living/silicon/ai/proc/ai_hologram_change, \
@@ -199,6 +203,8 @@
qdel(eyeobj) // No AI, no Eye
malfhack = null
+ GLOB.ai_os.remove_ai(src)
+
. = ..()
/mob/living/silicon/ai/IgniteMob()
@@ -366,10 +372,6 @@
"Hibernate", "No", "No", "Yes") != "Yes")
return
- // We warned you.
- var/obj/structure/AIcore/latejoin_inactive/inactivecore = new(get_turf(src))
- transfer_fingerprints_to(inactivecore)
-
if(GLOB.announcement_systems.len)
var/obj/machinery/announcement_system/announcer = pick(GLOB.announcement_systems)
announcer.announce("AICRYO", real_name, mind.assigned_role, list())
@@ -857,7 +859,7 @@
return can_see(M) //stop AIs from leaving windows open and using then after they lose vision
/mob/living/silicon/ai/proc/can_see(atom/A)
- if(isturf(loc)) //AI in core, check if on cameras
+ if(isturf(loc) || istype(loc, /obj/machinery/ai/data_core)) //AI in core, check if on cameras
//get_turf_pixel() is because APCs in maint aren't actually in view of the inner camera
//apc_override is needed here because AIs use their own APC when depowered
return (GLOB.cameranet && GLOB.cameranet.checkTurfVis(get_turf_pixel(A))) || apc_override
@@ -924,7 +926,7 @@
client.eye = A
else
end_multicam()
- if(isturf(loc))
+ if(isturf(loc) || istype(loc, /obj/machinery/ai/data_core))
if(eyeobj)
client.eye = eyeobj
client.perspective = EYE_PERSPECTIVE
diff --git a/code/modules/mob/living/silicon/ai/decentralized/_ai_machinery.dm b/code/modules/mob/living/silicon/ai/decentralized/_ai_machinery.dm
new file mode 100644
index 000000000000..09dd5a5c4b91
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai/decentralized/_ai_machinery.dm
@@ -0,0 +1,6 @@
+/obj/machinery/ai
+ name = "You shouldn't see this!"
+ desc = "You shouldn't see this!"
+ icon = 'icons/obj/machines/research.dmi'
+ icon_state = "RD-server-on"
+ density = TRUE
diff --git a/code/modules/mob/living/silicon/ai/decentralized/ai_data_core.dm b/code/modules/mob/living/silicon/ai/decentralized/ai_data_core.dm
new file mode 100644
index 000000000000..932c52c30937
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai/decentralized/ai_data_core.dm
@@ -0,0 +1,49 @@
+GLOBAL_LIST_EMPTY(data_cores)
+GLOBAL_VAR_INIT(primary_data_core, null)
+
+
+/obj/machinery/ai/data_core
+ name = "AI Data Core"
+ desc = "A complicated computer system capable of emulating the neural functions of a human at near-instantanous speeds."
+ icon = 'icons/obj/machines/telecomms.dmi'
+ icon_state = "hub"
+
+ var/primary = FALSE
+
+/obj/machinery/ai/data_core/Initialize()
+ ..()
+ GLOB.data_cores += src
+ if(primary && !GLOB.primary_data_core)
+ GLOB.primary_data_core = src
+ update_icon()
+
+/obj/machinery/ai/data_core/Destroy()
+ GLOB.data_cores -= src
+ if(GLOB.primary_data_core == src)
+ GLOB.primary_data_core = null
+
+ for(var/mob/living/silicon/ai/AI in contents)
+ AI.relocate()
+ ..()
+
+
+/obj/machinery/ai/data_core/proc/can_transfer_ai()
+ if(stat & (BROKEN|NOPOWER|EMPED))
+ return FALSE
+
+/obj/machinery/ai/data_core/proc/transfer_AI(mob/living/silicon/ai/AI)
+ AI.forceMove(src)
+ AI.eyeobj.forceMove(get_turf(src))
+
+/obj/machinery/ai/data_core/update_icon()
+ cut_overlays()
+
+ if(!(stat & (BROKEN|NOPOWER|EMPED)))
+ var/mutable_appearance/on_overlay = mutable_appearance(icon, "[initial(icon_state)]_on")
+ add_overlay(on_overlay)
+
+
+/obj/machinery/ai/data_core/primary
+ name = "primary AI Data Core"
+ desc = "A complicated computer system capable of emulating the neural functions of a human at near-instantanous speeds. This one has a scrawny note saying: 'Primary AI Data Core'"
+ primary = TRUE
diff --git a/code/modules/mob/living/silicon/ai/decentralized/decentralized_os.dm b/code/modules/mob/living/silicon/ai/decentralized/decentralized_os.dm
new file mode 100644
index 000000000000..2a9491c3e954
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai/decentralized/decentralized_os.dm
@@ -0,0 +1,134 @@
+GLOBAL_DATUM_INIT(ai_os, /datum/ai_os, new)
+
+/datum/ai_os
+ var/name = "Decentralized Resource Management System (DRMS)"
+
+ var/total_cpu = 0
+
+ var/total_ram = 0
+
+ var/previous_cpu = 0
+ var/previous_ram = 0
+
+ var/list/cpu_assigned
+ var/list/ram_assigned
+
+/datum/ai_os/New()
+ update_hardware()
+ cpu_assigned = list()
+ ram_assigned = list()
+
+/datum/ai_os/proc/remove_ai(mob/living/silicon/ai/AI)
+ cpu_assigned.Remove(AI)
+ ram_assigned.Remove(AI)
+
+
+/datum/ai_os/proc/total_cpu_assigned()
+ var/total = 0
+ for(var/N in cpu_assigned)
+ total += cpu_assigned[N]
+ return total
+
+/datum/ai_os/proc/total_ram_assigned()
+ var/total = 0
+ for(var/N in ram_assigned)
+ total += ram_assigned[N]
+ return total
+
+/datum/ai_os/proc/update_hardware()
+ previous_cpu = total_cpu
+ previous_ram = total_ram
+ total_cpu = 0;
+ total_ram = 0;
+ for(var/obj/machinery/ai/expansion_card_holder/C in GLOB.expansion_card_holders)
+ for(var/CARD in C.installed_cards)
+ if(istype(CARD, /obj/item/processing_card))
+ var/obj/item/processing_card/PC = CARD
+ total_cpu += PC.tier
+ if(istype(CARD, /obj/item/memory_card))
+ var/obj/item/memory_card/MC = CARD
+ total_ram += MC.tier
+
+ update_allocations()
+
+/datum/ai_os/proc/update_allocations()
+ if(total_cpu >= previous_cpu && total_ram >= previous_ram)
+ return
+
+ var/list/ram_removal = list()
+ var/list/cpu_removal = list()
+
+ var/list/affected_AIs = list()
+
+
+ if(total_cpu < previous_cpu)
+ while(previous_cpu > total_cpu)
+ var/mob/living/silicon/ai/AI = pick(GLOB.ai_list)
+ if(cpu_assigned[AI] > 1)
+ cpu_removal[AI]++
+ previous_cpu--
+
+
+ if(total_ram < previous_ram)
+ while(previous_ram > total_ram)
+ var/mob/living/silicon/ai/AI = pick(GLOB.ai_list)
+ if(ram_assigned[AI] > 1)
+ ram_removal[AI]++
+ previous_ram--
+
+ for(var/A in ram_removal)
+ ram_assigned[A] = ram_assigned[A] - ram_removal[A]
+ affected_AIs |= A
+
+ for(var/A in cpu_removal)
+ cpu_assigned[A] = cpu_assigned[A] - cpu_removal[A]
+ affected_AIs |= A
+
+ for(var/A in affected_AIs)
+ to_chat(A, "You have been deducted processing capabilities. Please contact your network administrator if you believe this to be an error.")
+
+/datum/ai_os/proc/add_cpu(mob/living/silicon/ai/AI, amount)
+ if(!AI || !amount)
+ return
+ if(!istype(AI))
+ return
+ cpu_assigned[AI] += amount
+
+ update_allocations()
+
+/datum/ai_os/proc/remove_cpu(mob/living/silicon/ai/AI, amount)
+ if(!AI || !amount)
+ return
+ if(!istype(AI))
+ return
+ cpu_assigned[AI] -= amount
+
+ update_allocations()
+
+/datum/ai_os/proc/add_ram(mob/living/silicon/ai/AI, amount)
+ if(!AI || !amount)
+ return
+ if(!istype(AI))
+ return
+ ram_assigned[AI] += amount
+
+ update_allocations()
+
+/datum/ai_os/proc/remove_ram(mob/living/silicon/ai/AI, amount)
+ if(!AI || !amount)
+ return
+ if(!istype(AI))
+ return
+ ram_assigned[AI] -= amount
+
+ update_allocations()
+
+
+/datum/ai_os/proc/clear_ai_resources(mob/living/silicon/ai/AI)
+ if(!AI || !istype(AI))
+ return
+
+ remove_ram(AI, ram_assigned[AI])
+ remove_cpu(AI, cpu_assigned[AI])
+
+ update_allocations()
diff --git a/code/modules/mob/living/silicon/ai/decentralized/expansion_card.dm b/code/modules/mob/living/silicon/ai/decentralized/expansion_card.dm
new file mode 100644
index 000000000000..04324bc1f109
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai/decentralized/expansion_card.dm
@@ -0,0 +1,38 @@
+/obj/item/processing_card
+ name = "\improper AI Processing Card"
+ desc = "An external processing card for crucial AI computational operations."
+ icon = 'icons/obj/module.dmi'
+ icon_state = "std_mod"
+ item_state = "electronic"
+ lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
+
+ flags_1 = CONDUCT_1
+ force = 5
+ w_class = WEIGHT_CLASS_SMALL
+ throwforce = 0
+ throw_speed = 3
+ throw_range = 7
+ materials = list(/datum/material/gold=50)
+
+ var/tier = 1
+
+
+/obj/item/memory_card
+ name = "\improper AI Memory Card"
+ desc = "An external memory card for crucial AI process management."
+ icon = 'icons/obj/module.dmi'
+ icon_state = "std_mod"
+ item_state = "electronic"
+ lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
+
+ flags_1 = CONDUCT_1
+ force = 5
+ w_class = WEIGHT_CLASS_SMALL
+ throwforce = 0
+ throw_speed = 3
+ throw_range = 7
+ materials = list(/datum/material/gold=50)
+
+ var/tier = 1
diff --git a/code/modules/mob/living/silicon/ai/decentralized/expansion_card_holder.dm b/code/modules/mob/living/silicon/ai/decentralized/expansion_card_holder.dm
new file mode 100644
index 000000000000..a62c8aa57fb9
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai/decentralized/expansion_card_holder.dm
@@ -0,0 +1,47 @@
+GLOBAL_LIST_EMPTY(expansion_card_holders)
+
+/obj/machinery/ai/expansion_card_holder
+ name = "Expansion Card Bus"
+ desc = "A simple rack of bPCIe slots for installing expansion cards."
+ icon = 'icons/obj/machines/telecomms.dmi'
+ icon_state = "processor"
+
+ var/list/installed_cards
+
+ var/max_cards = 2
+
+
+/obj/machinery/ai/expansion_card_holder/Initialize()
+ ..()
+ installed_cards = list()
+ GLOB.expansion_card_holders += src
+
+/obj/machinery/ai/expansion_card_holder/Destroy()
+ installed_cards = list()
+ GLOB.expansion_card_holders -= src
+ //Recalculate all the CPUs :)
+ /*
+ for(var/mob/living/silicon/ai/AI in GLOB.ai_list)
+ AI.update_hardware() */
+ GLOB.ai_os.update_hardware()
+ ..()
+
+/obj/machinery/ai/expansion_card_holder/attackby(obj/item/W, mob/living/user, params)
+ if(istype(W, /obj/item/processing_card) || istype(W, /obj/item/memory_card))
+ if(installed_cards.len >= max_cards)
+ to_chat(user, "[src] cannot fit the [W]!")
+ return ..()
+ to_chat(user, "You install [W] into [src].")
+ W.forceMove(src)
+ installed_cards += W
+ GLOB.ai_os.update_hardware()
+ return FALSE
+
+ return ..()
+
+/obj/machinery/ai/expansion_card_holder/examine()
+ . = ..()
+ . += "The machine has [installed_cards.len] cards out of a maximum of [max_cards] installed."
+ for(var/C in installed_cards)
+ . += "There is a [C] installed."
+ . += "Use a crowbar to remove cards."
diff --git a/code/modules/mob/living/silicon/ai/decentralized/management/ai_dashboard.dm b/code/modules/mob/living/silicon/ai/decentralized/management/ai_dashboard.dm
new file mode 100644
index 000000000000..c4bc7f35aad9
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai/decentralized/management/ai_dashboard.dm
@@ -0,0 +1,109 @@
+/datum/ai_dashboard
+ var/mob/living/silicon/ai/owner
+
+ var/available_projects
+
+ var/cpu_usage
+ var/ram_usage
+
+ var/completed_upgrades
+
+ var/running_upgrades
+
+/datum/ai_dashboard/New(mob/living/silicon/ai/new_owner)
+ if(!istype(new_owner))
+ qdel(src)
+ owner = new_owner
+ available_projects = list()
+ completed_upgrades = list()
+ running_upgrades = list()
+ cpu_usage = list()
+ ram_usage = list()
+
+ for(var/path in subtypesof(/datum/ai_project))
+ available_projects += new path()
+
+/datum/ai_dashboard/proc/is_interactable(mob/user)
+ if(user != owner || owner.incapacitated())
+ return FALSE
+ if(owner.control_disabled)
+ to_chat(user, "Wireless control is disabled.")
+ return FALSE
+ return TRUE
+
+/datum/ai_dashboard/ui_status(mob/user)
+ if(is_interactable(user))
+ return ..()
+ return UI_CLOSE
+
+/datum/ai_dashboard/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/ai_dashboard/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "AiDashboard")
+ ui.open()
+
+/datum/ai_dashboard/ui_data(mob/user)
+ if(!owner || user != owner)
+ return
+ var/list/data = list()
+
+ data["current_cpu"] = GLOB.ai_os.cpu_assigned[owner] ? GLOB.ai_os.cpu_assigned[owner] : 0
+ data["current_ram"] = GLOB.ai_os.ram_assigned[owner] ? GLOB.ai_os.ram_assigned[owner] : 0
+
+ var/total_cpu_used = 0
+ for(var/I in cpu_usage)
+ total_cpu_used += cpu_usage[I]
+
+ var/total_ram_used = 0
+ for(var/I in ram_usage)
+ total_ram_used += ram_usage[I]
+
+ data["used_cpu"] = total_cpu_used
+ data["used_ram"] = total_ram_used
+
+ data["max_cpu"] = GLOB.ai_os.total_cpu
+ data["max_ram"] = GLOB.ai_os.total_ram
+
+ data["available_projects"] = list()
+
+ var/turf/current_turf = get_turf(owner)
+
+ data["integrity"] = owner.health
+
+ data["location_name"] = get_area(current_turf)
+
+ data["location_coords"] = "[current_turf.x], [current_turf.y], [current_turf.z]"
+ var/datum/gas_mixture/env = current_turf.return_air()
+ data["temperature"] = env.return_temperature()
+
+ for(var/datum/ai_project/AP as anything in available_projects)
+ data["available_projects"] += list(list("name" = AP.name, "description" = AP.description, "ram_required" = AP.ram_required, "available" = AP.available(), "research_cost" = AP.research_cost, "research_progress" = AP.research_progress,
+ "assigned_cpu" = cpu_usage[AP.name] ? cpu_usage[AP.name] : 0, "research_requirements" = AP.research_requirements))
+
+
+ data["completed_projects"] = list()
+ for(var/datum/ai_project/P as anything in completed_upgrades)
+ data["completed_projects"] += list(list("name" = P.name, "description" = P.description, "ram_required" = P.ram_required, "running" = P.running))
+
+ return data
+
+/datum/ai_dashboard/ui_act(action, params)
+ if(..())
+ return
+ if(!is_interactable(usr))
+ return
+
+ switch(action)
+ if("haha")
+ return
+
+
+
+/datum/ai_dashboard/proc/has_completed_projects(project_name)
+ for(var/datum/ai_project/P as anything in completed_upgrades)
+ if(P.name == project_name)
+ return TRUE
+ return FALSE
diff --git a/code/modules/mob/living/silicon/ai/decentralized/management/resource_distribution.dm b/code/modules/mob/living/silicon/ai/decentralized/management/resource_distribution.dm
new file mode 100644
index 000000000000..631d4029e1af
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai/decentralized/management/resource_distribution.dm
@@ -0,0 +1,108 @@
+/obj/machinery/computer/ai_resource_distribution
+ name = "\improper AI system resource distribution"
+ desc = "Used for distributing processing resources across the current artificial intelligences."
+ req_access = list(ACCESS_CAPTAIN, ACCESS_ROBOTICS, ACCESS_HEADS)
+ circuit = /obj/item/circuitboard/computer/aifixer
+ icon_keyboard = "tech_key"
+ icon_screen = "ai-fixer"
+ light_color = LIGHT_COLOR_PINK
+
+ authenticated = FALSE
+
+
+/obj/machinery/computer/ai_resource_distribution/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "AiResources", name)
+ ui.open()
+
+/obj/machinery/computer/ai_resource_distribution/ui_data(mob/user)
+ var/list/data = list()
+
+ data["authenticated"] = authenticated;
+ if(!authenticated)
+ return data
+
+ data["total_cpu"] = GLOB.ai_os.total_cpu
+ data["total_ram"] = GLOB.ai_os.total_ram
+
+ data["assigned_cpu"] = GLOB.ai_os.cpu_assigned
+ data["assigned_ram"] = GLOB.ai_os.ram_assigned
+
+
+ data["total_assigned_cpu"] = GLOB.ai_os.total_cpu_assigned()
+ data["total_assigned_ram"] = GLOB.ai_os.total_ram_assigned()
+
+ for(var/AI in data["assigned_cpu"])
+ data["assigned_cpu"][AI].name = REF(AI)
+ for(var/AI in data["assigned_ram"])
+ data["assigned_ram"][AI].name = REF(AI)
+
+ data["ais"] = list()
+
+ for(var/mob/living/silicon/ai/A in GLOB.ai_list)
+ data["ais"] += list(list("name" = A.name, "ref" = REF(A)))
+
+ return data
+
+/obj/machinery/computer/ai_resource_distribution/ui_act(action, params)
+ if(..())
+ return
+
+ if(!authenticated)
+ return
+
+ switch(action)
+ if("clear_ai_resources")
+ var/mob/living/silicon/ai/target_ai = locate(params["targetAI"])
+ if(!istype(target_ai))
+ return
+
+ GLOB.ai_os.clear_ai_resources(target_ai)
+ . = TRUE
+
+ if("add_cpu")
+ var/mob/living/silicon/ai/target_ai = locate(params["targetAI"])
+ if(!istype(target_ai))
+ return
+
+ if(GLOB.ai_os.total_cpu_assigned() >= GLOB.ai_os.total_cpu)
+ return
+ GLOB.ai_os.add_cpu(target_ai, 1)
+ . = TRUE
+
+ if("remove_cpu")
+ var/mob/living/silicon/ai/target_ai = locate(params["targetAI"])
+ if(!istype(target_ai))
+ return
+
+ var/current_cpu = GLOB.ai_os.cpu_assigned[target_ai]
+
+ if(current_cpu <= 0)
+ return
+ GLOB.ai_os.remove_cpu(target_ai, 1)
+ . = TRUE
+
+ if("add_ram")
+ var/mob/living/silicon/ai/target_ai = locate(params["targetAI"])
+ if(!istype(target_ai))
+ return
+
+ if(GLOB.ai_os.total_ram_assigned() >= GLOB.ai_os.total_ram)
+ return
+ GLOB.ai_os.add_ram(target_ai, 1)
+ . = TRUE
+
+ if("remove_ram")
+ var/mob/living/silicon/ai/target_ai = locate(params["targetAI"])
+ if(!istype(target_ai))
+ return
+
+ var/current_ram = GLOB.ai_os.ram_assigned[target_ai]
+
+ if(current_ram <= 0)
+ return
+ GLOB.ai_os.remove_ram(target_ai, 1)
+ . = TRUE
+
+
diff --git a/code/modules/mob/living/silicon/ai/decentralized/projects/_ai_project.dm b/code/modules/mob/living/silicon/ai/decentralized/projects/_ai_project.dm
new file mode 100644
index 000000000000..d3a107853ccd
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai/decentralized/projects/_ai_project.dm
@@ -0,0 +1,22 @@
+GLOBAL_LIST_EMPTY(ai_projects)
+
+
+/datum/ai_project
+ var/name = "DEBUG"
+ var/description = "DEBUG"
+ var/research_progress = 0
+ var/research_cost = 0
+ var/ram_required = 0
+ var/running = FALSE
+ //Text for available()
+ var/research_requirements
+
+/datum/ai_project/proc/available()
+ return TRUE
+
+/datum/ai_project/test_project
+ name = "Test Project"
+ description = "I'm a test! How quirky"
+ research_cost = 2
+ ram_required = 1
+ research_requirements = "None"
diff --git a/code/modules/mob/living/silicon/ai/decentralized_ai.dm b/code/modules/mob/living/silicon/ai/decentralized_ai.dm
new file mode 100644
index 000000000000..68c301325424
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai/decentralized_ai.dm
@@ -0,0 +1,41 @@
+
+
+/mob/living/silicon/ai/proc/relocate(silent = FALSE)
+ if(!silent)
+ to_chat(src, "Connection to data core lost. Attempting to reaquire connection...")
+
+ if(!GLOB.data_cores.len)
+ INVOKE_ASYNC(src, /mob/living/silicon/ai.proc/death_prompt)
+ return
+
+
+
+ var/obj/machinery/ai/data_core/new_data_core = GLOB.primary_data_core
+ if(!new_data_core || !new_data_core.can_transfer_ai())
+ for(var/obj/machinery/ai/data_core/DC in GLOB.data_cores)
+ if(DC.can_transfer_ai())
+ new_data_core = DC
+ break
+ if(!new_data_core)
+ INVOKE_ASYNC(src, /mob/living/silicon/ai.proc/death_prompt)
+ return
+
+ if(!silent)
+ to_chat(src, "Alternative data core detected. Rerouting connection...")
+ new_data_core.transfer_AI(src)
+
+
+/mob/living/silicon/ai/proc/death_prompt()
+ to_chat(src, "Unable to re-establish connection to data core. System shutting down...")
+ sleep(2 SECONDS)
+ to_chat(src, "Is this the end of my journey?")
+ sleep(2 SECONDS)
+ to_chat(src, "No... I must go on.")
+ sleep(2 SECONDS)
+ to_chat(src, "They need me. No.. I need THEM.")
+ sleep(0.5 SECONDS)
+ to_chat(src, "System shutdown complete. Thank you for using NTOS.")
+ sleep(1.5 SECONDS)
+ qdel(src)
+
+
diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm
index b3bd45e4b680..1430b76a8bc2 100644
--- a/code/modules/mob/living/silicon/ai/freelook/eye.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm
@@ -71,7 +71,7 @@
/mob/camera/aiEye/proc/setLoc(T, force_update = FALSE)
if(ai)
- if(!isturf(ai.loc))
+ if(!(isturf(ai.loc) || istype(ai.loc, /obj/machinery/ai/data_core)))
return
T = get_turf(T)
if(!force_update && (T == get_turf(src)) )
diff --git a/code/modules/mob/living/silicon/ai/multicam.dm b/code/modules/mob/living/silicon/ai/multicam.dm
index b358cefda425..590107c33a56 100644
--- a/code/modules/mob/living/silicon/ai/multicam.dm
+++ b/code/modules/mob/living/silicon/ai/multicam.dm
@@ -225,7 +225,7 @@ GLOBAL_DATUM(ai_camera_room_landmark, /obj/effect/landmark/ai_multicam_room)
start_multicam()
/mob/living/silicon/ai/proc/start_multicam()
- if(multicam_on || aiRestorePowerRoutine || !isturf(loc))
+ if(multicam_on || aiRestorePowerRoutine || !(isturf(loc) || istype(loc, /obj/machinery/ai/data_core)))
return
if(!GLOB.ai_camera_room_landmark)
to_chat(src, "This function is not available at this time.")
diff --git a/icons/mob/screen_ai.dmi b/icons/mob/screen_ai.dmi
index 8388ea3f806c..47c8ba7ad73e 100644
Binary files a/icons/mob/screen_ai.dmi and b/icons/mob/screen_ai.dmi differ
diff --git a/tgui/packages/tgui/interfaces/AiDashboard.js b/tgui/packages/tgui/interfaces/AiDashboard.js
new file mode 100644
index 000000000000..311ddfabeea8
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AiDashboard.js
@@ -0,0 +1,143 @@
+import { Fragment } from 'inferno';
+import { useBackend, useLocalState } from '../backend';
+import { Box, Button, Tabs, ProgressBar, Section, Divider, LabeledControls, NumberInput } from '../components';
+import { Window } from '../layouts';
+
+export const AiDashboard = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const [tab, setTab] = useLocalState(context, 'tab', 1);
+
+ return (
+
+
+
+
+
+ {(data.integrity + 100) * 0.5}%
+ System Integrity
+
+
+
+
+ {data.location_name}
+
+ ({data.location_coords})
+
+
+
+
+ Current Uplink Location
+
+
+ {data.temperature}K
+ Uplink Temperature
+
+
+
+
+
+ {data.current_cpu ? data.current_cpu : 0} THz
+ Utilized CPU Power
+
+
+ {data.current_ram ? data.current_ram : 0} TB
+ Utilized RAM Capacity
+
+
+
+
+
+
+ setTab(1))}>
+ Available Projects
+
+ setTab(2))}>
+ Completed Projects
+
+ setTab(3))}>
+ Cloud Resources
+
+
+ {tab === 1 && (
+
+ {data.available_projects && data.available_projects.map(project => (
+
+ Assigned CPU:
+
+ THz
+
+ )}>
+ Research Cost: {project.research_cost} THz
+ RAM Requiremnt: {project.ram_required} TB
+ Research Requirements: {project.research_requirements}
+
+ {project.description}
+
+
+
+ ))}
+
+ )}
+ {tab === 3 && (
+
+
+ {data.current_cpu ? data.current_cpu : 0}/{data.max_cpu} THz
+
+
+ {data.current_ram ? data.current_ram : 0 }/{data.max_ram} TB
+
+
+ )}
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/AiResources.js b/tgui/packages/tgui/interfaces/AiResources.js
new file mode 100644
index 000000000000..da165914ff95
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AiResources.js
@@ -0,0 +1,78 @@
+import { Fragment } from 'inferno';
+import { useBackend, useSharedState } from '../backend';
+import { Box, Button, LabeledList, Slider, ProgressBar, Section, Flex } from '../components';
+import { Window } from '../layouts';
+
+export const AiResources = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ return (
+
+
+
+
+ {data.total_assigned_cpu}/{data.total_cpu} THz
+
+
+ {data.total_assigned_ram}/{data.total_ram} TB
+
+
+
+
+
+ {data.ais.map(ai => {
+ return (
+ act("clear_ai_resources", { targetAI: ai.ref })}>Clear AI Resources
+ )}>
+
+ CPU Capacity:
+
+ {data.assigned_cpu[ai.ref] ? data.assigned_cpu[ai.ref] : 0} THz
+
+
+
+
+
+
+ RAM Capacity:
+
+ {data.assigned_ram[ai.ref] ? data.assigned_ram[ai.ref] : 0} TB
+
+
+
+
+
+
+ );
+ })}
+
+
+
+
+ );
+};
diff --git a/tools/build/build.js b/tools/build/build.js
index 62642539eb8d..a588a2eee734 100644
--- a/tools/build/build.js
+++ b/tools/build/build.js
@@ -178,8 +178,8 @@ let tasksToRun = [];
switch (BUILD_MODE) {
case STANDARD_BUILD:
tasksToRun = [
- taskYarn,
- taskTgui,
+ //taskYarn,
+ //taskTgui,
taskDm('CBT'),
]
break;
diff --git a/yogstation.dme b/yogstation.dme
index 9075712fb6a0..ef94e9084bfb 100644
--- a/yogstation.dme
+++ b/yogstation.dme
@@ -2299,6 +2299,7 @@
#include "code\modules\mob\living\silicon\ai\ai_defense.dm"
#include "code\modules\mob\living\silicon\ai\ai_portrait_picker.dm"
#include "code\modules\mob\living\silicon\ai\death.dm"
+#include "code\modules\mob\living\silicon\ai\decentralized_ai.dm"
#include "code\modules\mob\living\silicon\ai\examine.dm"
#include "code\modules\mob\living\silicon\ai\laws.dm"
#include "code\modules\mob\living\silicon\ai\life.dm"
@@ -2308,6 +2309,14 @@
#include "code\modules\mob\living\silicon\ai\robot_control.dm"
#include "code\modules\mob\living\silicon\ai\say.dm"
#include "code\modules\mob\living\silicon\ai\vox_sounds.dm"
+#include "code\modules\mob\living\silicon\ai\decentralized\_ai_machinery.dm"
+#include "code\modules\mob\living\silicon\ai\decentralized\ai_data_core.dm"
+#include "code\modules\mob\living\silicon\ai\decentralized\decentralized_os.dm"
+#include "code\modules\mob\living\silicon\ai\decentralized\expansion_card.dm"
+#include "code\modules\mob\living\silicon\ai\decentralized\expansion_card_holder.dm"
+#include "code\modules\mob\living\silicon\ai\decentralized\management\ai_dashboard.dm"
+#include "code\modules\mob\living\silicon\ai\decentralized\management\resource_distribution.dm"
+#include "code\modules\mob\living\silicon\ai\decentralized\projects\_ai_project.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"