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 = "

Warning!


We have detected usage of the AI Control Code for unlocking a console at coordinates ([src.x], [src.y], [src.z]) by [usr.name]. Please verify that this is correct. Be aware we have cancelled the current control code.
\ + If needed a new code can be printed at a communications console." + priority_announce(msg, sender_override = "Central Cyber Security Update", has_important_message = TRUE, sanitize = FALSE) + GLOB.ai_control_code = null + else + to_chat(usr, span_warning("Incorrect code. Make sure you have the latest one.")) return switch(action) if("log_out") + if(one_time_password_used) + return authenticated = FALSE . = TRUE if("upload_intellicard") @@ -316,4 +408,13 @@ to_chat(user, span_notice("You fail to remove the device.")) -#undef AI_DOWNLOAD_PER_PROCESS + +/obj/item/paper/ai_control_code/Initialize(mapload) + ..() + print() + +/obj/item/paper/ai_control_code/proc/print() + name = "paper - 'AI control code'" + info = "

Daily AI Control Key Reset


The new authentication key is '[GLOB.ai_control_code]'.
Please keep this a secret and away from the clown.
This code may be invalidated if a new one is requested." + add_overlay("paper_words") + 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 index 4f54cb62738b..ca0da93cf824 100644 --- a/code/modules/mob/living/silicon/ai/decentralized/management/ai_dashboard.dm +++ b/code/modules/mob/living/silicon/ai/decentralized/management/ai_dashboard.dm @@ -1,5 +1,3 @@ -GLOBAL_VAR_INIT(sent_crash_message, FALSE) - /datum/ai_dashboard var/mob/living/silicon/ai/owner 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 index 8db29448e5d0..842a91b932aa 100644 --- a/code/modules/mob/living/silicon/ai/decentralized/projects/_ai_project.dm +++ b/code/modules/mob/living/silicon/ai/decentralized/projects/_ai_project.dm @@ -1,14 +1,3 @@ -//AI Project Categories. -#define AI_PROJECT_HUDS "Sensor HUDs" -#define AI_PROJECT_CAMERAS "Visiblity Upgrades" -#define AI_PROJECT_MISC "Misc." - -GLOBAL_LIST_INIT(ai_project_categories, list( - AI_PROJECT_HUDS, - AI_PROJECT_CAMERAS, - AI_PROJECT_MISC -)) - GLOBAL_LIST_EMPTY(ai_projects) /datum/ai_project diff --git a/code/modules/mob/living/silicon/ai/decentralized/projects/ai_huds.dm b/code/modules/mob/living/silicon/ai/decentralized/projects/ai_huds.dm index ceef8e7a0818..5db9ed5ff47c 100644 --- a/code/modules/mob/living/silicon/ai/decentralized/projects/ai_huds.dm +++ b/code/modules/mob/living/silicon/ai/decentralized/projects/ai_huds.dm @@ -10,18 +10,20 @@ . = ..(force_run) if(!.) return . - ai.sec_hud = DATA_HUD_SECURITY_ADVANCED if(ai.sensors_on) - ai.toggle_sensors() - ai.toggle_sensors() + ai.toggle_sensors(TRUE) + + ai.sec_hud = DATA_HUD_SECURITY_ADVANCED + + ai.toggle_sensors(TRUE) /datum/ai_project/security_hud/stop() if(ai.sensors_on) //HUDs are weird. This has to be first so we're removed from the "advanced" HUD. It checks the sec_hud variable to see which one we remove from first. - ai.toggle_sensors() + ai.toggle_sensors(TRUE) ai.sec_hud = DATA_HUD_SECURITY_BASIC - ai.toggle_sensors() + ai.toggle_sensors(TRUE) ..() /datum/ai_project/diag_med_hud @@ -36,20 +38,21 @@ . = ..(force_run) if(!.) return . + if(ai.sensors_on) + ai.toggle_sensors(TRUE) + ai.d_hud = DATA_HUD_DIAGNOSTIC_ADVANCED ai.med_hud = DATA_HUD_MEDICAL_ADVANCED - - if(ai.sensors_on) - ai.toggle_sensors() - ai.toggle_sensors() + + ai.toggle_sensors(TRUE) /datum/ai_project/diag_med_hud/stop() if(ai.sensors_on) //HUDs are weird. This has to be first so we're removed from the "advanced" HUD. It checks the d_hud and med_hud variable to see which one we remove from first. - ai.toggle_sensors() + ai.toggle_sensors(TRUE) ai.d_hud = DATA_HUD_DIAGNOSTIC_BASIC ai.med_hud = DATA_HUD_MEDICAL_BASIC - ai.toggle_sensors() + ai.toggle_sensors(TRUE) ..() diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 6e33ee27e7bb..1d0bc2c3e0b8 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -1125,6 +1125,8 @@ mainframe.connected_robots |= src lawupdate = TRUE lawsync() + if(sensors_on) + add_sensors() if(radio && AI.radio) //AI keeps all channels, including Syndie if it is a Traitor if(AI.radio.syndie) radio.make_syndie() diff --git a/tgui/packages/tgui/interfaces/AiControlPanel.js b/tgui/packages/tgui/interfaces/AiControlPanel.js index 2582093c0855..9ce46be51c37 100644 --- a/tgui/packages/tgui/interfaces/AiControlPanel.js +++ b/tgui/packages/tgui/interfaces/AiControlPanel.js @@ -1,7 +1,8 @@ import { Fragment } from 'inferno'; import { useBackend, useLocalState } from '../backend'; -import { Box, Button, LabeledList, Tabs, ProgressBar, Section, Flex, Icon, NoticeBox } from '../components'; +import { Box, Button, Input, Tabs, ProgressBar, Section, Flex, Icon, NoticeBox } from '../components'; import { Window } from '../layouts'; +import { KEY_ENTER } from 'common/keycodes'; export const AiControlPanel = (props, context) => { const { act, data } = useBackend(context); @@ -10,6 +11,35 @@ export const AiControlPanel = (props, context) => { const [tab, setTab] = useLocalState(context, 'tab', 1); + const [code, setCode] = useLocalState(context, 'code', null); + + if (!data.cleared_for_use) { + return ( + + +
+ + Enter AI control code and press enter. (6 numbers) + { + if (e.keyCode === KEY_ENTER) { + setCode(null); + act('clear_for_use', { 'control_code': value }); + } else { + setCode(value); + } + }} + /> + +
+
+
+ ); + } + return ( {
- + )}> Upload also possible by inserting an MMI or Positronic Brain @@ -121,7 +151,7 @@ export const AiControlPanel = (props, context) => { ) || (
- + {data.user_image && ( @@ -155,7 +185,17 @@ export const AiControlPanel = (props, context) => { + + Alternatively you can use the AI Control Code as a one-time password. This will alert the station of your location and name. + + { + if (e.keyCode === KEY_ENTER) { + act('log_in_control_code', { 'control_code': value }); + } + }} /> +
)} diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole.js b/tgui/packages/tgui/interfaces/CommunicationsConsole.js index 6886b4bdd1b1..4a888e1515de 100644 --- a/tgui/packages/tgui/interfaces/CommunicationsConsole.js +++ b/tgui/packages/tgui/interfaces/CommunicationsConsole.js @@ -283,7 +283,7 @@ const PageMain = (props, context) => { shuttleCalledPreviously, shuttleCanEvacOrFailReason, shuttleLastCalled, - canPrintId, + canPrintIdAndCode, shuttleRecallable, } = data; @@ -388,13 +388,21 @@ const PageMain = (props, context) => { onClick={() => act("makeVoiceAnnouncement")} />} - {!!canPrintId &&