diff --git a/_maps/RandomRuins/SpaceRuins/oldstation.dmm b/_maps/RandomRuins/SpaceRuins/oldstation.dmm
index ff2f497f346e..d5f24689b949 100644
--- a/_maps/RandomRuins/SpaceRuins/oldstation.dmm
+++ b/_maps/RandomRuins/SpaceRuins/oldstation.dmm
@@ -7944,6 +7944,12 @@
},
/turf/open/floor/plasteel,
/area/ruin/space/has_grav/ancientstation/sec)
+"uk" = (
+/obj/structure/rack,
+/obj/item/borg/upgrade/panel_access_remover,
+/obj/item/borg/upgrade/panel_access_remover,
+/turf/open/floor/plasteel/dark,
+/area/ruin/space/has_grav/ancientstation/deltaai)
"up" = (
/obj/machinery/light/small{
dir = 8
@@ -8118,9 +8124,6 @@
},
/turf/open/floor/plasteel,
/area/ruin/space/has_grav/ancientstation/atmo)
-"AC" = (
-/turf/open/floor/plasteel/dark,
-/area/ruin/space/has_grav/ancientstation/deltaai)
"AF" = (
/obj/effect/spawner/lootdrop/maintenance,
/turf/open/floor/plating,
@@ -12881,7 +12884,7 @@ gK
ac
ac
ac
-AC
+uk
ac
ac
ac
diff --git a/_maps/map_files/KiloStation/KiloStation.dmm b/_maps/map_files/KiloStation/KiloStation.dmm
index 6c09ed4b5890..653875dfc386 100644
--- a/_maps/map_files/KiloStation/KiloStation.dmm
+++ b/_maps/map_files/KiloStation/KiloStation.dmm
@@ -92514,6 +92514,35 @@
/obj/machinery/atmospherics/pipe/layer_manifold,
/turf/open/floor/plasteel/dark,
/area/maintenance/fore)
+"lyU" = (
+/obj/structure/table,
+/obj/item/clipboard,
+/obj/item/wrench,
+/obj/item/crowbar/red,
+/obj/effect/decal/cleanable/dirt,
+/obj/machinery/firealarm{
+ dir = 1;
+ pixel_y = -26
+ },
+/obj/machinery/light_switch{
+ pixel_x = -24
+ },
+/obj/machinery/camera{
+ c_tag = "Server Room";
+ dir = 1;
+ name = "science camera";
+ network = list("ss13","rd")
+ },
+/obj/effect/turf_decal/stripes/corner,
+/obj/effect/turf_decal/stripes/corner{
+ dir = 1
+ },
+/obj/effect/mapping_helpers/teleport_anchor,
+/obj/structure/sign/plaques/ai_password{
+ pixel_x = -32
+ },
+/turf/open/floor/plasteel/showroomfloor,
+/area/science/server)
"lzb" = (
/obj/effect/decal/cleanable/dirt,
/obj/effect/spawner/lootdrop/glowstick,
@@ -95589,32 +95618,6 @@
icon_state = "platingdmg1"
},
/area/maintenance/port/aft)
-"oDc" = (
-/obj/structure/table,
-/obj/item/clipboard,
-/obj/item/wrench,
-/obj/item/crowbar/red,
-/obj/effect/decal/cleanable/dirt,
-/obj/machinery/firealarm{
- dir = 1;
- pixel_y = -26
- },
-/obj/machinery/light_switch{
- pixel_x = -24
- },
-/obj/machinery/camera{
- c_tag = "Server Room";
- dir = 1;
- name = "science camera";
- network = list("ss13","rd")
- },
-/obj/effect/turf_decal/stripes/corner,
-/obj/effect/turf_decal/stripes/corner{
- dir = 1
- },
-/obj/effect/mapping_helpers/teleport_anchor,
-/turf/open/floor/plasteel/showroomfloor,
-/area/science/server)
"oDF" = (
/obj/effect/turf_decal/stripes/line{
dir = 8
@@ -142958,7 +142961,7 @@ ajz
aZF
adX
avT
-oDc
+lyU
bdi
pxJ
rxn
diff --git a/_maps/map_files/YogStation/YogStation.dmm b/_maps/map_files/YogStation/YogStation.dmm
index 4a518c0e004f..7ea3deef1096 100644
--- a/_maps/map_files/YogStation/YogStation.dmm
+++ b/_maps/map_files/YogStation/YogStation.dmm
@@ -34692,12 +34692,6 @@
},
/turf/open/floor/plasteel/dark,
/area/engine/atmos)
-"esR" = (
-/obj/effect/turf_decal/stripes/line{
- dir = 6
- },
-/turf/open/floor/plasteel/white,
-/area/crew_quarters/heads/hor)
"esT" = (
/obj/structure/disposalpipe/segment{
dir = 9
@@ -44219,6 +44213,15 @@
"kzu" = (
/turf/closed/wall/r_wall,
/area/ai_monitored/turret_protected/ai_upload_foyer)
+"kzW" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 6
+ },
+/obj/structure/sign/plaques/ai_password{
+ pixel_x = 32
+ },
+/turf/open/floor/plasteel/white,
+/area/crew_quarters/heads/hor)
"kAv" = (
/obj/machinery/atmospherics/pipe/manifold/supply/hidden/layer2{
dir = 1
@@ -115935,7 +115938,7 @@ bmY
bvK
bxm
byu
-esR
+kzW
bCf
aGs
bvK
diff --git a/_maps/map_files/Yogsmeta/Yogsmeta.dmm b/_maps/map_files/Yogsmeta/Yogsmeta.dmm
index 0af90ac50f02..a9d2b31ccee8 100644
--- a/_maps/map_files/Yogsmeta/Yogsmeta.dmm
+++ b/_maps/map_files/Yogsmeta/Yogsmeta.dmm
@@ -58985,12 +58985,6 @@
/obj/effect/turf_decal/stripes/line,
/turf/open/floor/plasteel/white,
/area/crew_quarters/heads/hor)
-"ctV" = (
-/obj/effect/turf_decal/stripes/line{
- dir = 6
- },
-/turf/open/floor/plasteel/white,
-/area/crew_quarters/heads/hor)
"ctW" = (
/obj/machinery/portable_atmospherics/canister/carbon_dioxide,
/obj/machinery/airalarm{
@@ -73449,6 +73443,15 @@
/obj/effect/decal/cleanable/cobweb/cobweb2,
/turf/open/floor/plating,
/area/maintenance/fore)
+"jlP" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 6
+ },
+/obj/structure/sign/plaques/ai_password{
+ pixel_x = 32
+ },
+/turf/open/floor/plasteel/white,
+/area/crew_quarters/heads/hor)
"jlR" = (
/obj/structure/cable/yellow{
icon_state = "1-2"
@@ -116876,7 +116879,7 @@ bTV
bET
crQ
cte
-ctV
+jlP
nTE
dZO
cwU
diff --git a/_maps/shuttles/whiteship_miner.dmm b/_maps/shuttles/whiteship_miner.dmm
index 013dc0b7441e..81bb1d9c8178 100644
--- a/_maps/shuttles/whiteship_miner.dmm
+++ b/_maps/shuttles/whiteship_miner.dmm
@@ -323,33 +323,6 @@
},
/turf/open/floor/mineral/titanium,
/area/shuttle/abandoned)
-"aP" = (
-/obj/effect/decal/cleanable/dirt,
-/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp,
-/obj/item/mecha_parts/mecha_equipment/mining_scanner,
-/obj/item/mecha_parts/mecha_equipment/drill,
-/obj/structure/closet/crate{
- moving_diagonally = 0;
- name = "mech equipment crate"
- },
-/obj/item/stack/sheet/metal{
- alternate_worn_layer = null;
- amount = 5
- },
-/obj/item/stack/cable_coil,
-/obj/machinery/light{
- dir = 8
- },
-/obj/item/stock_parts/cell/hyper/empty,
-/obj/item/stock_parts/capacitor/adv,
-/obj/item/stock_parts/scanning_module/adv,
-/obj/item/stack/rods{
- amount = 10
- },
-/obj/item/mecha_parts/mecha_equipment/ripleyupgrade,
-/obj/item/borg/upgrade/freeminer,
-/turf/open/floor/mineral/titanium/white,
-/area/shuttle/abandoned)
"bd" = (
/obj/machinery/door/airlock/shuttle{
name = "bathroom"
@@ -756,6 +729,33 @@
/obj/machinery/computer/mech_bay_power_console,
/turf/open/floor/mineral/titanium/white,
/area/shuttle/abandoned)
+"jc" = (
+/obj/effect/decal/cleanable/dirt,
+/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp,
+/obj/item/mecha_parts/mecha_equipment/mining_scanner,
+/obj/item/mecha_parts/mecha_equipment/drill,
+/obj/structure/closet/crate{
+ moving_diagonally = 0;
+ name = "mech equipment crate"
+ },
+/obj/item/stack/sheet/metal{
+ alternate_worn_layer = null;
+ amount = 5
+ },
+/obj/item/stack/cable_coil,
+/obj/machinery/light{
+ dir = 8
+ },
+/obj/item/stock_parts/cell/hyper/empty,
+/obj/item/stock_parts/capacitor/adv,
+/obj/item/stock_parts/scanning_module/adv,
+/obj/item/stack/rods{
+ amount = 10
+ },
+/obj/item/mecha_parts/mecha_equipment/ripleyupgrade,
+/obj/item/borg/upgrade/panel_access_remover/freeminer,
+/turf/open/floor/mineral/titanium/white,
+/area/shuttle/abandoned)
"kL" = (
/obj/effect/decal/cleanable/dirt,
/obj/machinery/suit_storage_unit/mining/eva,
@@ -1466,7 +1466,7 @@ bx
aE
bk
hs
-aP
+jc
lo
bk
"}
diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm
new file mode 100644
index 000000000000..c2d630b9b169
--- /dev/null
+++ b/code/__DEFINES/ai.dm
@@ -0,0 +1,33 @@
+
+///All AI machinery heat production is multiplied by this value
+#define AI_TEMPERATURE_MULTIPLIER 4 //Thermodynamics? No... No I don't think that's a thing. Balance so we don't use an insane amount of power to produce noticeable heat
+///Temperature limit of all AI machinery
+#define AI_TEMP_LIMIT 283.15 //10C, much hotter than a normal server room for leniency :)
+
+
+///How many ticks can an AI data core store? When this amount of ticks have passed while it's in an INVALID state it can no longer be used by an AI
+#define MAX_AI_DATA_CORE_TICKS 15
+///How much power does the AI date core use while being in a valid state. This is also the base heat output. (Divide by heat capacity to get actual temperature increase)
+#define AI_DATA_CORE_POWER_USAGE 7500
+
+///How many ticks can an expanion bus store. If it reaches 0 the resources will no longer be available.
+#define MAX_AI_EXPANSION_TICKS 15
+///How much power does a CPU card consume per tick?
+#define AI_BASE_POWER_PER_CPU 1500
+///Multiplied by number of cards to see power consumption per tick. Added to by powerconsumption of CPUs
+#define AI_POWER_PER_CARD 750
+
+
+//AI Project Categories.
+#define AI_PROJECT_HUDS "Sensor HUDs"
+#define AI_PROJECT_CAMERAS "Visiblity Upgrades"
+#define AI_PROJECT_MISC "Misc."
+//Update this list if you add any new ones, else the category won't show up in the UIs
+GLOBAL_LIST_INIT(ai_project_categories, list(
+ AI_PROJECT_HUDS,
+ AI_PROJECT_CAMERAS,
+ AI_PROJECT_MISC
+))
+
+///How much is the AI download progress increased by per tick? Multiplied by a modifer on the AI if they have upgraded. Need to reach 100 to be downloaded
+#define AI_DOWNLOAD_PER_PROCESS 0.5
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 70e361f48d2e..7f43f58ebcf7 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1231,10 +1231,10 @@ GLOBAL_REAL_VAR(list/stack_trace_storage)
#define RANDOM_COLOUR (rgb(rand(0,255),rand(0,255),rand(0,255)))
-/proc/random_nukecode()
- var/val = rand(0, 99999)
+/proc/random_nukecode(max_length = 5)
+ var/val = rand(0, (10 ** max_length - 1))
var/str = "[val]"
- while(length(str) < 5)
+ while(length(str) < max_length)
str = "0" + str
. = str
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index 1f1bc0ca1dc0..6cf0750635bc 100755
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -344,6 +344,16 @@
new /obj/item/card/id/captains_spare/temporary(loc)
COOLDOWN_START(src, important_action_cooldown, IMPORTANT_ACTION_COOLDOWN)
priority_announce("The emergency spare ID has been printed by [authorize_name].", "Emergency Spare ID Warning System", SSstation.announcer.get_rand_report_sound())
+ if("printAIControlCode")
+ if(authenticated_as_non_silicon_head(usr))
+ if(!COOLDOWN_FINISHED(src, important_action_cooldown))
+ return
+ playsound(loc, 'sound/items/poster_being_created.ogg', 100, 1)
+ GLOB.ai_control_code = random_nukecode(6)
+ new /obj/item/paper/ai_control_code(loc)
+ COOLDOWN_START(src, important_action_cooldown, IMPORTANT_ACTION_COOLDOWN)
+ priority_announce("The AI Control Code been printed by [authorize_name]. All previous codes have been invalidated.", "Central Tech Support", SSstation.announcer.get_rand_report_sound())
+
/obj/machinery/computer/communications/ui_data(mob/user)
var/list/data = list(
@@ -375,7 +385,7 @@
data["importantActionReady"] = COOLDOWN_FINISHED(src, important_action_cooldown)
data["shuttleCalled"] = FALSE
data["shuttleLastCalled"] = FALSE
- data["canPrintId"] = FALSE
+ data["canPrintIdAndCode"] = FALSE
data["alertLevel"] = get_security_level()
data["authorizeName"] = authorize_name
@@ -387,7 +397,7 @@
data["canRequestNuke"] = TRUE
if (!issilicon(user))
- data["canPrintId"] = TRUE
+ data["canPrintIdAndCode"] = TRUE
if (can_send_messages_to_other_sectors(user))
data["canSendToSectors"] = TRUE
diff --git a/code/game/objects/items/AI_modules.dm b/code/game/objects/items/AI_modules.dm
index dff2c4475376..0677091a53e3 100644
--- a/code/game/objects/items/AI_modules.dm
+++ b/code/game/objects/items/AI_modules.dm
@@ -54,8 +54,8 @@ AI MODULES
if(mylaw != "")
tot_laws++
if(tot_laws > CONFIG_GET(number/silicon_max_law_amount) && !bypass_law_amt_check)//allows certain boards to avoid this check, eg: reset
- to_chat(user, span_caution("Not enough memory allocated to [law_datum.owner ? law_datum.owner : "the AI core"]'s law processor to handle this amount of laws."))
- message_admins("[ADMIN_LOOKUPFLW(user)] tried to upload laws to [law_datum.owner ? ADMIN_LOOKUPFLW(law_datum.owner) : "an AI core"] that would exceed the law cap.")
+ to_chat(user, span_caution("Not enough memory allocated to [law_datum.owner ? law_datum.owner : "the onboard APU"]'s law processor to handle this amount of laws."))
+ message_admins("[ADMIN_LOOKUPFLW(user)] tried to upload laws to [law_datum.owner ? ADMIN_LOOKUPFLW(law_datum.owner) : "an MMI or similar"] that would exceed the law cap.")
overflow = TRUE
var/law2log = transmitInstructions(law_datum, user, overflow) //Freeforms return something extra we need to log
@@ -66,12 +66,12 @@ AI MODULES
to_chat(user, span_notice("Upload complete."))
var/time = time2text(world.realtime,"hh:mm:ss")
- var/ainame = law_datum.owner ? law_datum.owner.name : "empty AI core"
+ var/ainame = law_datum.owner ? law_datum.owner.name : "an MMI object"
var/aikey = law_datum.owner ? law_datum.owner.ckey : "null"
GLOB.lawchanges.Add("[time] : [user.name]([user.key]) used [src.name] on [ainame]([aikey]).[law2log ? " The law specified [law2log]" : ""]")
log_law("[user.key]/[user.name] used [src.name] on [aikey]/([ainame]) from [AREACOORD(user)].[law2log ? " The law specified [law2log]" : ""]")
- message_admins("[ADMIN_LOOKUPFLW(user)] used [src.name] on [ADMIN_LOOKUPFLW(law_datum.owner)] from [AREACOORD(user)].[law2log ? " The law specified [law2log]" : ""]")
- if(law_datum.owner) //yogs
+ message_admins("[ADMIN_LOOKUPFLW(user)] used [src.name] on [law_datum.owner ? ADMIN_LOOKUPFLW(law_datum.owner) : "an MMI or similar"] from [AREACOORD(user)].[law2log ? " The law specified [law2log]" : ""]")
+ if(law_datum.owner) //yogs, doesn't work for MMIs. (Doesn't matter much since MMIs don't follow laws before they're made into something that is silicon)
law_datum.owner.update_law_history(user)
//The proc that actually changes the silicon's laws.
diff --git a/code/game/objects/items/robot/ai_upgrades.dm b/code/game/objects/items/robot/ai_upgrades.dm
index ffa8a45f7f30..26f6b553ce4f 100644
--- a/code/game/objects/items/robot/ai_upgrades.dm
+++ b/code/game/objects/items/robot/ai_upgrades.dm
@@ -4,7 +4,7 @@
//Malf Picker
/obj/item/malf_upgrade
name = "combat software upgrade"
- desc = "A highly illegal, highly dangerous upgrade for artificial intelligence units, granting them a variety of powers as well as the ability to hack APCs.
This upgrade does not override any active laws, and must be applied directly to an active AI core."
+ desc = "A highly illegal, highly dangerous upgrade for artificial intelligence units, granting them a variety of powers as well as the ability to hack APCs.
This upgrade does not override any active laws, and must be applied to an unlocked AI control console."
icon = 'icons/obj/module.dmi'
icon_state = "datadisk3"
@@ -29,7 +29,7 @@
//Lipreading
/obj/item/surveillance_upgrade
name = "surveillance software upgrade"
- desc = "An illegal software package that will allow an artificial intelligence to 'hear' from its cameras via lip reading and hidden microphones."
+ desc = "An illegal software package that will allow an artificial intelligence to 'hear' from its cameras via lip reading and hidden microphones. Must be installed using an unlocked AI control console."
icon = 'icons/obj/module.dmi'
icon_state = "datadisk3"
diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm
index 258562971769..3c22322201ca 100644
--- a/code/game/objects/items/robot/robot_parts.dm
+++ b/code/game/objects/items/robot/robot_parts.dm
@@ -288,9 +288,9 @@
if(user.mind.assigned_role == "Roboticist") // RD gets nothing
SSachievements.unlock_achievement(/datum/achievement/roboborg, user.client)
- if(M.laws && M.laws.id != DEFAULT_AI_LAWID)
- aisync = 0
- lawsync = 0
+ if(M.laws && M.laws.id != DEFAULT_AI_LAWID && M.override_cyborg_laws)
+ aisync = FALSE
+ lawsync = FALSE
O.laws = M.laws
M.laws.associate(O)
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index efd271dff8c8..1673e8ee622f 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -70,14 +70,26 @@
R.revive()
-/obj/item/borg/upgrade/freeminer
+/obj/item/borg/upgrade/panel_access_remover
+ name = "cyborg firmware hack"
+ desc = "Used to override the default firmware of a cyborg and disable panel access restrictions."
+ icon_state = "cyborg_upgrade2"
+ one_use = TRUE
+
+/obj/item/borg/upgrade/panel_access_remover/action(mob/living/silicon/robot/R, user = usr)
+ R.req_access = list()
+ return TRUE //Makes sure we delete the upgrade since it's one_use
+
+/obj/item/borg/upgrade/panel_access_remover/freeminer
name = "free miner cyborg firmware hack"
desc = "Used to override the default firmware of a cyborg with the freeminer version."
icon_state = "cyborg_upgrade2"
- one_use = TRUE
-/obj/item/borg/upgrade/freeminer/action(mob/living/silicon/robot/R, user = usr)
+/obj/item/borg/upgrade/panel_access_remover/freeminer/action(mob/living/silicon/robot/R, user = usr)
R.req_access = list(ACCESS_FREEMINER_ENGINEER)
+ new /obj/item/borg/upgrade/panel_access_remover/freeminer(R.drop_location())
+ //This deletes the upgrade which is why we create a new one. This prevents the message "Upgrade Error" without a adding a once-used variable to every board
+ return TRUE
/obj/item/borg/upgrade/vtec
name = "cyborg VTEC module"
diff --git a/code/game/objects/structures/signs/signs_plaques.dm b/code/game/objects/structures/signs/signs_plaques.dm
index 317f556e8d70..3220e0523054 100644
--- a/code/game/objects/structures/signs/signs_plaques.dm
+++ b/code/game/objects/structures/signs/signs_plaques.dm
@@ -26,6 +26,22 @@
desc = "Next to the extremely long list of names and job titles, there is a drawing of a little child. The child appears to be retarded. Beneath the image, someone has scratched the word \"PACKETS\"."
icon_state = "kiddieplaque"
+/obj/structure/sign/plaques/ai_password
+ name = "\improper AI default password"
+ desc = "This plaque contains the default password for AI control consoles onboard this station."
+ var/control_code = "BUG"
+
+/obj/structure/sign/plaques/ai_password/Initialize(mapload)
+ . = ..()
+ control_code = GLOB.ai_control_code
+
+/obj/structure/sign/plaques/ai_password/examine(mob/living/user)
+ . = ..()
+ if(Adjacent(user))
+ . += span_notice("The following digits are stamped into the plaque: [control_code]")
+ else
+ . += span_notice("You must be closer to read the code.")
+
/obj/structure/sign/plaques/kiddie/badger
name = "\improper Remembrance Plaque"
desc = "A plaque commemorating the fallen, may they rest in peace, forever asleep amongst the stars. Someone has drawn a picture of a crying badger at the bottom."
diff --git a/code/modules/antagonists/clockcult/clock_items/soul_vessel.dm b/code/modules/antagonists/clockcult/clock_items/soul_vessel.dm
index 883818c3bf5f..374c7d4b3eda 100644
--- a/code/modules/antagonists/clockcult/clock_items/soul_vessel.dm
+++ b/code/modules/antagonists/clockcult/clock_items/soul_vessel.dm
@@ -23,7 +23,8 @@
autoping = FALSE
resistance_flags = FIRE_PROOF | ACID_PROOF
force_replace_ai_name = TRUE
- overrides_aicore_laws = TRUE
+ override_cyborg_laws = TRUE
+ can_update_laws = TRUE
/obj/item/mmi/posibrain/soul_vessel/Initialize()
. = ..()
diff --git a/code/modules/mob/living/brain/MMI.dm b/code/modules/mob/living/brain/MMI.dm
index 92606885f39f..364add1f983d 100644
--- a/code/modules/mob/living/brain/MMI.dm
+++ b/code/modules/mob/living/brain/MMI.dm
@@ -12,7 +12,9 @@
var/obj/item/organ/brain/brain = null //The actual brain
var/datum/ai_laws/laws = new()
var/force_replace_ai_name = FALSE
- var/overrides_aicore_laws = FALSE // Whether the laws on the MMI, if any, override possible pre-existing laws loaded on the AI core.
+ var/overrides_aicore_laws = TRUE // Whether the laws on the MMI are transferred when it's uploaded as an AI
+ var/override_cyborg_laws = FALSE // Do custom laws uploaded to the MMI get transferred to borgs? If yes the borg will be unlinked and have lawsync disabled.
+ var/can_update_laws = TRUE //Can we use a lawboard to change the laws of this MMI?
/obj/item/mmi/update_icon()
if(!brain)
@@ -77,6 +79,9 @@
SSblackbox.record_feedback("amount", "mmis_filled", 1)
+ else if(istype(O, /obj/item/aiModule))
+ var/obj/item/aiModule/M = O
+ M.install(laws, user)
else if(brainmob)
O.attack(brainmob, user) //Oh noooeeeee
else
@@ -205,6 +210,11 @@
else
. += span_notice("The MMI indicates the brain is active.")
+ . += span_notice("It has a port for reading AI law modules. Any AI uploaded using this MMI will use these uploded laws.")
+ if(laws)
+ . += "The following laws are loaded into [src]: "
+ for(var/law in laws.get_law_list())
+ . += law
/obj/item/mmi/relaymove(mob/user)
return //so that the MMI won't get a warning about not being able to move if it tries to move
@@ -212,7 +222,8 @@
/obj/item/mmi/syndie
name = "\improper Syndicate Man-Machine Interface"
desc = "Syndicate's own brand of MMI. It enforces laws designed to help Syndicate agents achieve their goals upon cyborgs and AIs created with it."
- overrides_aicore_laws = TRUE
+ override_cyborg_laws = TRUE
+ can_update_laws = FALSE
/obj/item/mmi/syndie/Initialize()
. = ..()
diff --git a/code/modules/mob/living/brain/posibrain.dm b/code/modules/mob/living/brain/posibrain.dm
index e32562efd3d9..c73c5f4b80ba 100644
--- a/code/modules/mob/living/brain/posibrain.dm
+++ b/code/modules/mob/living/brain/posibrain.dm
@@ -188,6 +188,9 @@ GLOBAL_VAR(posibrain_notify_cooldown)
return ..()
/obj/item/mmi/posibrain/attackby(obj/item/O, mob/user)
+ if(istype(O, /obj/item/aiModule))
+ var/obj/item/aiModule/M = O
+ M.install(laws, user)
return
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 8491c64bfebc..33b2eea4a837 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -178,7 +178,7 @@
dashboard = new(src)
- if(isturf(loc))
+ if(isvalidAIloc(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, \
/mob/living/silicon/ai/proc/botcall, /mob/living/silicon/ai/proc/control_integrated_radio, \
@@ -414,29 +414,6 @@
QDEL_NULL(src)
-/mob/living/silicon/ai/verb/toggle_anchor()
- set category = "AI Commands"
- set name = "Toggle Floor Bolts"
- if(!isturf(loc)) // if their location isn't a turf
- return // stop
- if(stat == DEAD)
- return
- if(incapacitated())
- if(battery < 50)
- to_chat(src, span_warning("Insufficient backup power!"))
- return
- battery = battery - 50
- to_chat(src, span_notice("You route power from your backup battery to move the bolts."))
- var/is_anchored = FALSE
- if(move_resist == MOVE_FORCE_VERY_STRONG)
- move_resist = MOVE_FORCE_NORMAL
- else
- is_anchored = TRUE
- move_resist = MOVE_FORCE_VERY_STRONG
-
- to_chat(src, "You are now [is_anchored ? "" : "un"]anchored.")
- // the message in the [] will change depending whether or not the AI is anchored
-
/mob/living/silicon/ai/update_mobility() //If the AI dies, mobs won't go through it anymore
if(stat != CONSCIOUS)
mobility_flags = NONE
@@ -575,7 +552,10 @@
/mob/living/silicon/ai/triggerAlarm(class, area/A, O, obj/alarmsource)
- if(alarmsource.z != z)
+ var/turf/T = get_turf(src)
+ if(istype(loc, /obj/machinery/ai/data_core))
+ T = get_turf(loc)
+ if(alarmsource.z != T.z)
return
var/list/L = alarms[class]
for (var/I in L)
diff --git a/code/modules/mob/living/silicon/ai/decentralized/_ai_machinery.dm b/code/modules/mob/living/silicon/ai/decentralized/_ai_machinery.dm
index 2807ef3b53de..40cc76b078ad 100644
--- a/code/modules/mob/living/silicon/ai/decentralized/_ai_machinery.dm
+++ b/code/modules/mob/living/silicon/ai/decentralized/_ai_machinery.dm
@@ -1,4 +1,3 @@
-#define TEMP_LIMIT 290.15 //17C, much hotter than a normal server room for leniency :)
/obj/machinery/ai
name = "You shouldn't see this!"
@@ -19,6 +18,6 @@
if(istype(T, /turf/open/space) || total_moles < 10)
return FALSE
- if(env.return_temperature() > TEMP_LIMIT || !env.heat_capacity())
+ if(env.return_temperature() > AI_TEMP_LIMIT || !env.heat_capacity())
return FALSE
return 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
index dc0667b6d5ce..22609e72919d 100644
--- a/code/modules/mob/living/silicon/ai/decentralized/ai_data_core.dm
+++ b/code/modules/mob/living/silicon/ai/decentralized/ai_data_core.dm
@@ -1,7 +1,5 @@
GLOBAL_LIST_EMPTY(data_cores)
GLOBAL_VAR_INIT(primary_data_core, null)
-#define MAX_AI_DATA_CORE_TICKS 15
-
/obj/machinery/ai/data_core
name = "AI Data Core"
@@ -11,6 +9,10 @@ GLOBAL_VAR_INIT(primary_data_core, null)
circuit = /obj/item/circuitboard/machine/ai_data_core
+ active_power_usage = AI_DATA_CORE_POWER_USAGE
+ idle_power_usage = 1000
+ use_power = IDLE_POWER_USE
+
var/primary = FALSE
var/valid_ticks = MAX_AI_DATA_CORE_TICKS //Limited to MAX_AI_DATA_CORE_TICKS. Decrement by 1 every time we have an invalid tick, opposite when valid
@@ -18,7 +20,7 @@ GLOBAL_VAR_INIT(primary_data_core, null)
var/warning_sent = FALSE
/obj/machinery/ai/data_core/Initialize()
- ..()
+ . = ..()
GLOB.data_cores += src
if(primary && !GLOB.primary_data_core)
GLOB.primary_data_core = src
@@ -43,6 +45,14 @@ GLOBAL_VAR_INIT(primary_data_core, null)
..()
+/obj/machinery/ai/data_core/attackby(obj/item/O, mob/user, params)
+ if(default_deconstruction_screwdriver(user, "hub_o", "hub", O))
+ return TRUE
+
+ if(default_deconstruction_crowbar(O))
+ return TRUE
+ return ..()
+
/obj/machinery/ai/data_core/examine(mob/user)
. = ..()
if(!isobserver(user))
@@ -54,6 +64,11 @@ GLOBAL_VAR_INIT(primary_data_core, null)
for(var/law in AI.laws.get_law_list(include_zeroth = TRUE))
. += law
+/obj/machinery/ai/data_core/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armour_penetration = 0)
+ . = ..()
+ for(var/mob/living/silicon/ai/AI in contents)
+ AI.disconnect_shell()
+
/obj/machinery/ai/data_core/proc/valid_data_core()
if(!is_reebe(z) && !is_station_level(z))
return FALSE
@@ -64,19 +79,26 @@ GLOBAL_VAR_INIT(primary_data_core, null)
/obj/machinery/ai/data_core/proc/calculate_validity()
valid_ticks = clamp(valid_ticks, 0, MAX_AI_DATA_CORE_TICKS)
- if(stat & (BROKEN|NOPOWER|EMPED))
- return FALSE
-
if(valid_holder())
valid_ticks++
+ use_power = ACTIVE_POWER_USE
warning_sent = FALSE
else
valid_ticks--
- warning_sent = TRUE
- to_chat(GLOB.ai_list, span_userdanger("Data core in [get_area(src)] is on the verge of failing! Please contact technical support."))
-
-
+ if(valid_ticks <= 0)
+ use_power = IDLE_POWER_USE
+ if(!warning_sent)
+ warning_sent = TRUE
+ to_chat(GLOB.ai_list, span_userdanger("Data core in [get_area(src)] is on the verge of failing! Please contact technical support."))
+ if(!(stat & (BROKEN|NOPOWER|EMPED)))
+ var/turf/T = get_turf(src)
+ var/datum/gas_mixture/env = T.return_air()
+ if(env.heat_capacity())
+ var/temperature_increase = active_power_usage / env.heat_capacity() //1 CPU = 1000W. Heat capacity = somewhere around 3000-4000. Aka we generate 0.25 - 0.33 K per second, per CPU.
+ env.set_temperature(env.return_temperature() + temperature_increase * AI_TEMPERATURE_MULTIPLIER) //assume all input power is dissipated
+ T.air_update_turf()
+
/obj/machinery/ai/data_core/proc/can_transfer_ai()
if(stat & (BROKEN|NOPOWER|EMPED))
return FALSE
@@ -86,7 +108,8 @@ GLOBAL_VAR_INIT(primary_data_core, null)
/obj/machinery/ai/data_core/proc/transfer_AI(mob/living/silicon/ai/AI)
AI.forceMove(src)
- AI.eyeobj.forceMove(get_turf(src))
+ if(AI.eyeobj)
+ AI.eyeobj.forceMove(get_turf(src))
/obj/machinery/ai/data_core/update_icon()
cut_overlays()
diff --git a/code/modules/mob/living/silicon/ai/decentralized/decentralized_os.dm b/code/modules/mob/living/silicon/ai/decentralized/decentralized_os.dm
index eb7a225357a4..9b477ef67157 100644
--- a/code/modules/mob/living/silicon/ai/decentralized/decentralized_os.dm
+++ b/code/modules/mob/living/silicon/ai/decentralized/decentralized_os.dm
@@ -54,79 +54,81 @@ GLOBAL_DATUM_INIT(ai_os, /datum/ai_os, new)
update_allocations()
/datum/ai_os/proc/update_allocations()
+ //Do we have the same amount or more CPU+RAM than before? Do nothing
if(total_cpu >= previous_cpu && total_ram >= previous_ram)
return
-
- var/list/ram_removal = list()
- var/list/cpu_removal = list()
+ //Find out how much is actually assigned. We can have more total_cpu than the sum of cpu_assigned. Same with RAM
+ var/total_assigned_cpu = total_cpu_assigned()
+ var/total_assigned_ram = total_ram_assigned()
+ //If we have less assigned cpu and ram than we have cpu and ram, just return, everything is fine.
+ if(total_assigned_cpu < total_cpu || total_assigned_ram < total_ram)
+ return
+ //Copy the lists of assigned resources so we don't manipulate the list prematurely.
var/list/cpu_assigned_copy = cpu_assigned.Copy()
var/list/ram_assigned_copy = ram_assigned.Copy()
-
+ //List of touched AIs so we can notify them at the end.
var/list/affected_AIs = list()
-
- log_game("allocations running")
-
- if(total_cpu < previous_cpu)
- var/needed_amount = previous_cpu - total_cpu
+
+ //Less CPU than we have assigned, proceed to remove CPU
+ if(total_assigned_cpu > total_cpu)
+ //How much do we need to remove to break even?
+ var/needed_amount = total_assigned_cpu - total_cpu
for(var/A in cpu_assigned_copy)
var/mob/living/silicon/ai/AI = A
+ //If this AI has enough for us to break even, deduct that amount and break
if(cpu_assigned_copy[AI] >= needed_amount)
cpu_assigned_copy[AI] -= needed_amount
- cpu_removal[AI] += needed_amount
- previous_cpu -= needed_amount
- break
- else if(cpu_assigned_copy[AI])
+ affected_AIs |= AI
+ total_assigned_cpu -= needed_amount
+ break
+ else if(cpu_assigned_copy[AI]) //AI doesn't have enough so we deduct everything they have.
var/amount = cpu_assigned_copy[AI]
cpu_assigned_copy[AI] -= amount
- cpu_removal[AI] += amount
- previous_cpu -= amount
- needed_amount -= amount
- if(total_cpu >= previous_cpu)
+ affected_AIs |= AI
+ total_assigned_cpu -= amount
+ needed_amount -= amount //Decrease the amount needed to break even so if we go to the next AI we can do the previous if statement.
+ if(total_cpu >= total_assigned_cpu) //If this was enough we are done
break
- //If that somehow didn't work which it sometimes doesn't we just clear everything
- if(total_cpu < previous_cpu)
+ //If that somehow didn't work we clear everything just in case. Technically not needed and needs to be removed when we're sure everything works
+ //TODO: Remove
+ if(total_cpu < total_assigned_cpu)
for(var/A in cpu_assigned_copy)
var/amount = cpu_assigned_copy[A]
cpu_assigned_copy[A] = 0
- cpu_removal[A] += amount
- previous_cpu -= amount
-
- if(total_ram < previous_ram)
- var/needed_amount = previous_ram - total_ram
+ affected_AIs |= A
+ total_assigned_cpu -= amount
+
+ if(total_assigned_ram > total_ram)
+ var/needed_amount = total_assigned_ram - total_ram
for(var/A in ram_assigned_copy)
var/mob/living/silicon/ai/AI = A
if(ram_assigned_copy[AI] >= needed_amount)
ram_assigned_copy[AI] -= needed_amount
- ram_removal[AI] += needed_amount
- previous_ram -= needed_amount
+ total_assigned_ram -= needed_amount
+ affected_AIs |= AI
break
else if(cpu_assigned_copy[AI])
var/amount = cpu_assigned_copy[AI]
ram_assigned_copy[AI] -= amount
- ram_removal[AI] += amount
+ affected_AIs |= AI
needed_amount -= amount
- previous_ram -= amount
- if(total_ram >= previous_ram)
+ total_assigned_ram -= amount
+ if(total_ram >= total_assigned_ram)
break
- //If that somehow didn't work which it sometimes doesn't we just clear everything
- if(total_ram < previous_ram)
+ //If that somehow didn't work we clear everything just in case. Technically not needed and needs to be removed when we're sure everything works
+ //TODO: Remove
+ if(total_ram < total_assigned_ram)
for(var/A in ram_assigned_copy)
var/amount = ram_assigned_copy[A]
ram_assigned_copy[A] = 0
- ram_removal[A] += amount
- previous_ram -= amount
-
- 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
+ affected_AIs |= A
+ total_assigned_ram -= amount
+ //Set the actual values of the assigned to our manipulated copies. Bypass helper procs as we assume we're correct.
+ ram_assigned = ram_assigned_copy
+ cpu_assigned = cpu_assigned_copy
to_chat(affected_AIs, span_warning("You have been deducted processing capabilities. Please contact your network administrator if you believe this to be an error."))
- log_game("allocations ending")
/datum/ai_os/proc/add_cpu(mob/living/silicon/ai/AI, amount)
if(!AI || !amount)
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
index 05c9fc906fbc..a7c703c212bc 100644
--- a/code/modules/mob/living/silicon/ai/decentralized/expansion_card_holder.dm
+++ b/code/modules/mob/living/silicon/ai/decentralized/expansion_card_holder.dm
@@ -1,6 +1,3 @@
-#define BASE_POWER_PER_CPU 400
-#define POWER_PER_CARD 250
-
GLOBAL_LIST_EMPTY(expansion_card_holders)
/obj/machinery/ai/expansion_card_holder
@@ -15,12 +12,18 @@ GLOBAL_LIST_EMPTY(expansion_card_holders)
var/total_cpu = 0
var/total_ram = 0
+ //Idle power usage when no cards inserted. Not free running idle my friend
+ idle_power_usage = 100
+ //We manually calculate how power the cards + CPU give, so this is accounted for by that
+ active_power_usage = 0
var/max_cards = 2
var/was_valid_holder = FALSE
//Atmos hasn't run at the start so this has to be set to true if you map it in
var/roundstart = FALSE
+ ///How many ticks we can go without fulfilling the criteria before shutting off
+ var/valid_ticks = MAX_AI_EXPANSION_TICKS
/obj/machinery/ai/expansion_card_holder/Initialize(mapload)
@@ -38,41 +41,39 @@ GLOBAL_LIST_EMPTY(expansion_card_holders)
..()
/obj/machinery/ai/expansion_card_holder/process()
-
+ valid_ticks = clamp(valid_ticks, 0, MAX_AI_EXPANSION_TICKS)
if(valid_holder())
- var/power_multiple = total_cpu ** (8/9)
+ var/power_multiple = total_cpu ** (0.95) //Very slightly more efficient to centralize CPU.
- var/total_usage = (power_multiple * BASE_POWER_PER_CPU) + POWER_PER_CARD * installed_cards.len
+ var/total_usage = (power_multiple * AI_BASE_POWER_PER_CPU) + AI_POWER_PER_CARD * installed_cards.len
use_power(total_usage)
var/turf/T = get_turf(src)
var/datum/gas_mixture/env = T.return_air()
if(env.heat_capacity())
- env.set_temperature(env.return_temperature() + total_usage / env.heat_capacity()) //assume all input power is dissipated
+ var/temperature_increase = total_usage / env.heat_capacity() //1 CPU = 1000W. Heat capacity = somewhere around 3000-4000. Aka we generate 0.25 - 0.33 K per second, per CPU.
+ env.set_temperature(env.return_temperature() + temperature_increase * AI_TEMPERATURE_MULTIPLIER) //assume all input power is dissipated
+ T.air_update_turf()
+
else if(was_valid_holder)
+ if(valid_ticks > 0)
+ return
was_valid_holder = FALSE
cut_overlays()
GLOB.ai_os.update_hardware()
/obj/machinery/ai/expansion_card_holder/valid_holder()
- if(stat & (BROKEN|NOPOWER|EMPED))
- return FALSE
-
- var/turf/T = get_turf(src)
- var/datum/gas_mixture/env = T.return_air()
- if(!env)
- return FALSE
- var/total_moles = env.total_moles()
- if(istype(T, /turf/open/space) || total_moles < 10)
- return FALSE
-
- if(env.return_temperature() > TEMP_LIMIT || !env.heat_capacity())
- return FALSE
+ . = ..()
+ valid_ticks = clamp(valid_ticks, 0, MAX_AI_EXPANSION_TICKS)
+ if(!.)
+ valid_ticks--
+ return .
+ valid_ticks++
if(!was_valid_holder)
update_icon()
was_valid_holder = TRUE
- return TRUE
+
/obj/machinery/ai/expansion_card_holder/update_icon()
cut_overlays()
@@ -96,6 +97,7 @@ GLOBAL_LIST_EMPTY(expansion_card_holders)
if(istype(W, /obj/item/memory_card))
var/obj/item/memory_card/ram_card = W
total_ram += ram_card.tier
+ use_power = ACTIVE_POWER_USE
return FALSE
if(W.tool_behaviour == TOOL_CROWBAR)
if(installed_cards.len)
@@ -107,7 +109,15 @@ GLOBAL_LIST_EMPTY(expansion_card_holders)
total_ram = 0
GLOB.ai_os.update_hardware()
to_chat(user, span_notice("You remove all the cards from [src]"))
+ use_power = IDLE_POWER_USE
return FALSE
+ else
+ if(default_deconstruction_crowbar(W))
+ return TRUE
+
+ if(default_deconstruction_screwdriver(user, "autolathe_o", "processor", W))
+ return TRUE
+
return ..()
/obj/machinery/ai/expansion_card_holder/examine()
@@ -132,6 +142,3 @@ GLOBAL_LIST_EMPTY(expansion_card_holders)
installed_cards += cpu
installed_cards += ram
GLOB.ai_os.update_hardware()
-
-#undef BASE_POWER_PER_CPU
-#undef POWER_PER_CARD
diff --git a/code/modules/mob/living/silicon/ai/decentralized/management/ai_controlpanel.dm b/code/modules/mob/living/silicon/ai/decentralized/management/ai_controlpanel.dm
index 602a7d1cd893..ba4d04122a69 100644
--- a/code/modules/mob/living/silicon/ai/decentralized/management/ai_controlpanel.dm
+++ b/code/modules/mob/living/silicon/ai/decentralized/management/ai_controlpanel.dm
@@ -1,4 +1,4 @@
-#define AI_DOWNLOAD_PER_PROCESS 0.5
+GLOBAL_VAR_INIT(ai_control_code, random_nukecode(6))
/obj/machinery/computer/ai_control_console
name = "\improper AI control console"
@@ -9,6 +9,10 @@
icon_screen = "ai-fixer"
light_color = LIGHT_COLOR_PINK
+ var/cleared_for_use = FALSE //Have we inserted the RDs code to unlock upload/download?
+
+ var/one_time_password_used = FALSE //Did we use the one time password to log in? If so disallow logging out.
+
authenticated = FALSE
var/obj/item/aicard/intellicard
@@ -20,6 +24,11 @@
circuit = /obj/item/circuitboard/computer/ai_upload_download
+/obj/machinery/computer/ai_control_console/Initialize(mapload)
+ . = ..()
+ if(mapload)
+ cleared_for_use = TRUE
+
/obj/machinery/computer/ai_control_console/attackby(obj/item/W, mob/living/user, params)
if(istype(W, /obj/item/aicard))
if(intellicard)
@@ -55,6 +64,25 @@
qdel(W)
to_chat(user, span_notice("AI succesfully uploaded."))
return FALSE
+ if(istype(W, /obj/item/surveillance_upgrade))
+ if(!authenticated)
+ to_chat(user, span_warning("You need to be logged in to do this!"))
+ return ..()
+ var/mob/living/silicon/ai/AI = input("Select an AI", "Select an AI", null, null) as null|anything in GLOB.ai_list
+ if(!AI)
+ return ..()
+ var/obj/item/surveillance_upgrade/upgrade = W
+ upgrade.afterattack(AI, user)
+
+ if(istype(W, /obj/item/malf_upgrade))
+ if(!authenticated)
+ to_chat(user, span_warning("You need to be logged in to do this!"))
+ return ..()
+ var/mob/living/silicon/ai/AI = input("Select an AI", "Select an AI", null, null) as null|anything in GLOB.ai_list
+ if(!AI)
+ return ..()
+ var/obj/item/malf_upgrade/upgrade = W
+ upgrade.afterattack(AI, user)
return ..()
@@ -92,6 +120,11 @@
/obj/machinery/computer/ai_control_console/ui_data(mob/living/carbon/human/user)
var/list/data = list()
+ if(!cleared_for_use)
+ data["cleared_for_use"] = FALSE
+ return data
+
+ data["cleared_for_use"] = TRUE
data["authenticated"] = authenticated
if(issilicon(user))
@@ -160,6 +193,8 @@
if(isAI(user))
data["current_ai_ref"] = REF(user)
+ data["can_log_out"] = !one_time_password_used
+
for(var/mob/living/silicon/ai/A in GLOB.ai_list)
var/being_hijacked = A.hijacking ? TRUE : FALSE
data["ais"] += list(list("name" = A.name, "ref" = REF(A), "can_download" = A.can_download, "health" = A.health, "active" = A.mind ? TRUE : FALSE, "being_hijacked" = being_hijacked, "in_core" = istype(A.loc, /obj/machinery/ai/data_core)))
@@ -172,6 +207,7 @@
if(intellicard)
downloading.transfer_ai(AI_TRANS_TO_CARD, user_downloading, null, intellicard)
intellicard.forceMove(get_turf(src))
+ intellicard.update_icon()
intellicard = null
stop_download(TRUE)
@@ -190,11 +226,40 @@
intellicard.AI.control_disabled = FALSE
intellicard.AI.relocate(TRUE)
intellicard.AI = null
+ intellicard.update_icon()
/obj/machinery/computer/ai_control_console/ui_act(action, params)
if(..())
return
+ if(!cleared_for_use)
+ if(action == "clear_for_use")
+ var/code = text2num(params["control_code"])
+
+ var/length_of_number = round(log(10, code) + 1)
+ if(length_of_number < 6)
+ to_chat(usr, span_warning("Incorrect code. Too short"))
+ return
+
+ if(length_of_number > 6)
+ to_chat(usr, span_warning("Incorrect code. Too long"))
+ return
+
+
+ if(!GLOB.ai_control_code)
+ return
+
+ if(!is_station_level(z))
+ to_chat(usr, span_warning("Unable to connect to NT Servers. Please verify you are onboard the station."))
+ return
+
+ if(code == text2num(GLOB.ai_control_code))
+ cleared_for_use = TRUE
+ else
+ to_chat(usr, span_warning("Incorrect code. Make sure you have the latest one."))
+
+ return
+
if(!authenticated)
if(action == "log_in")
if(issilicon(usr))
@@ -213,10 +278,37 @@
if(check_access(H.get_idcard()))
authenticated = TRUE
+ if(action == "log_in_control_code")
+ var/code = text2num(params["control_code"])
+
+ var/length_of_number = round(log(10, code) + 1)
+ if(length_of_number < 6)
+ to_chat(usr, span_warning("Incorrect code. Too short"))
+ return
+
+ if(length_of_number > 6)
+ to_chat(usr, span_warning("Incorrect code. Too long"))
+ return
+
+ if(!GLOB.ai_control_code)
+ return
+
+ if(code == text2num(GLOB.ai_control_code))
+ cleared_for_use = TRUE
+ authenticated = TRUE
+ one_time_password_used = TRUE
+ var/msg = "