diff --git a/code/__defines/machinery.dm b/code/__defines/machinery.dm index 67fae3b62a0..f9af0b86c68 100644 --- a/code/__defines/machinery.dm +++ b/code/__defines/machinery.dm @@ -9,6 +9,14 @@ #define DOOR_CRUSH_DAMAGE 20 #define ALIEN_SELECT_AFK_BUFFER 1 // How many minutes that a person can be AFK before not being allowed to be an alien. +// Airlock defines for setting up the AI's ability to bolt them +#define AIRLOCK_AI_BOLTING_AUTO 0 // automatic setup depending on whether the airlock starts locked +#define AIRLOCK_AI_BOLTING_TRUE 1 // AI can bolt the airlock, but this can be toggled by pulsing the AI control wire +#define AIRLOCK_AI_BOLTING_FALSE 2 // AI can NOT bolt the airlock, but this can be toggled by pulsing the AI control wire +#define AIRLOCK_AI_BOLTING_ALLOW 3 // AI can always operate the airlock, pulsing the AI control wire does nothing +#define AIRLOCK_AI_BOLTING_DENY 4 // AI can never operate the airlock, pulsing the AI control wire does nothing +#define AIRLOCK_AI_BOLTING_NEVER 5 // AI can never operate the airlock, even if they are antag + // Channel numbers for power. #define EQUIP 1 #define LIGHT 2 @@ -78,7 +86,7 @@ var/list/restricted_camera_networks = list(NETWORK_ERT,NETWORK_MERCENARY,"Secret #define STAGE_FIVE 9 #define STAGE_SUPER 11 -// Interaction flags +// Interaction flags #define STATUS_INTERACTIVE 2 // GREEN Visability #define STATUS_UPDATE 1 // ORANGE Visability #define STATUS_DISABLED 0 // RED Visability @@ -112,7 +120,7 @@ var/list/restricted_camera_networks = list(NETWORK_ERT,NETWORK_MERCENARY,"Secret #define M_PROCESSES 0x1 #define M_USES_POWER 0x2 -// If this is returned from a machine's process() proc, the machine will stop processing but +// If this is returned from a machine's process() proc, the machine will stop processing but // will continue to have power calculations done. #define M_NO_PROCESS 27 diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm index 9c72d252485..53475537fd0 100644 --- a/code/_onclick/ai.dm +++ b/code/_onclick/ai.dm @@ -135,11 +135,12 @@ /atom/proc/AICtrlClick(mob/user) return -/obj/machinery/door/airlock/AICtrlClick() // Bolts doors +/obj/machinery/door/airlock/AICtrlClick(mob/user) // Bolts doors + var/command = player_is_antag(user.mind) ? "bolts_override" : "bolts" if(locked) - Topic(src, list("command"="bolts", "activate" = "0")) + Topic(src, list("command"=command, "activate" = "0")) else - Topic(src, list("command"="bolts", "activate" = "1")) + Topic(src, list("command"=command, "activate" = "1")) return 1 /obj/machinery/power/apc/AICtrlClick() // turns off/on APCs. @@ -170,7 +171,6 @@ return 0 /obj/machinery/door/airlock/AIMiddleClick() // Toggles door bolt lights. - if(..()) return diff --git a/code/datums/wires/airlock.dm b/code/datums/wires/airlock.dm index 69c19fbfe9c..b68491d67a4 100644 --- a/code/datums/wires/airlock.dm +++ b/code/datums/wires/airlock.dm @@ -40,15 +40,33 @@ var/const/AIRLOCK_WIRE_LIGHT = 2048 var/haspower = A.arePowerSystemsOn() //If there's no power, then no lights will be on. . += ..() - . += text("
\n[]
\n[]
\n[]
\n[]
\n[]
\n[]
\n[]
\n[]", - (A.locked ? "The door bolts have fallen!" : "The door bolts look up."), - ((A.lights && haspower) ? "The door bolt lights are on." : "The door bolt lights are off!"), - ((haspower) ? "The test light is on." : "The test light is off!"), - ((A.backup_power_lost_until) ? "The backup power light is off!" : "The backup power light is on."), - ((A.aiControlDisabled==0 && !A.emagged && haspower)? "The 'AI control allowed' light is on." : "The 'AI control allowed' light is off."), - ((A.safe==0 && haspower)? "The 'Check Wiring' light is on." : "The 'Check Wiring' light is off."), - ((A.normalspeed==0 && haspower)? "The 'Check Timing Mechanism' light is on." : "The 'Check Timing Mechanism' light is off."), - ((A.aiDisabledIdScanner==0 && haspower)? "The IDScan light is on." : "The IDScan light is off.")) + var/list/text = list() + + if(!haspower) + text += "The test light and all the other indicator lights are [SPAN_BAD("off")]!" + text += A.locked ? "The door bolts seem to be down!" : "The door bolts seem to be up." + else + text += "The test light is [SPAN_GOOD("on")]." + text += A.backup_power_lost_until ? "The backup power light is [SPAN_BAD("off")]!" : "The backup power light is [SPAN_GOOD("on")]." + if(A.lights) // show different bolt message depending on whether we have lights + text += "The door bolt indicator lights are [SPAN_GOOD("on")]." + text += "They show the bolts are [A.locked ? SPAN_BAD("down") : SPAN_GOOD("up")]." + else + text += "The door bolt indicator lights are [SPAN_BAD("off")]!" + text += A.locked ? "The door bolts seem to be down!" : "The door bolts seem to be up." // this is a closer inspection + if(A.aiControlDisabled==0) + if(A.emagged) + text += "The 'AI control allowed' light is red." + else if(A.aiBolting) + text += "The 'AI control allowed' light is green." + else + text += "The 'AI control allowed' light is orange." + else + text += "The 'AI control allowed' light is [SPAN_BAD("off")]." + text += A.safe==0 ? "The 'Check Wiring' light is [SPAN_GOOD("on")]." : "The 'Check Wiring' light is [SPAN_BAD("off")]." + text += A.normalspeed==0 ? "The 'Check Timing Mechanism' light is [SPAN_GOOD("on")]." : "The 'Check Timing Mechanism' light is [SPAN_BAD("off")]." + text += A.aiDisabledIdScanner==0 ? "The IDScan light is [SPAN_GOOD("on")]." : "The IDScan light is [SPAN_BAD("off")]." + . += "
\n" + jointext(text, "
\n") /datum/wires/airlock/UpdateCut(var/index, var/mended) @@ -56,8 +74,8 @@ var/const/AIRLOCK_WIRE_LIGHT = 2048 switch(index) if(AIRLOCK_WIRE_IDSCAN) A.aiDisabledIdScanner = !mended - if(AIRLOCK_WIRE_MAIN_POWER1, AIRLOCK_WIRE_MAIN_POWER2) + if(AIRLOCK_WIRE_MAIN_POWER1, AIRLOCK_WIRE_MAIN_POWER2) if(!mended) //Cutting either one disables the main door power, but unless backup power is also cut, the backup power re-powers the door in 10 seconds. While unpowered, the door may be crowbarred open, but bolts-raising will not work. Cutting these wires may electocute the user. A.loseMainPower() @@ -67,7 +85,6 @@ var/const/AIRLOCK_WIRE_LIGHT = 2048 A.shock(usr, 50) if(AIRLOCK_WIRE_BACKUP_POWER1, AIRLOCK_WIRE_BACKUP_POWER2) - if(!mended) //Cutting either one disables the backup door power (allowing it to be crowbarred open, but disabling bolts-raising), but may electocute the user. A.loseBackupPower() @@ -77,14 +94,12 @@ var/const/AIRLOCK_WIRE_LIGHT = 2048 A.shock(usr, 50) if(AIRLOCK_WIRE_DOOR_BOLTS) - if(!mended) //Cutting this wire also drops the door bolts, and mending it does not raise them. (This is what happens now, except there are a lot more wires going to door bolts at present) A.lock(1) A.update_icon() if(AIRLOCK_WIRE_AI_CONTROL) - if(!mended) //one wire for AI control. Cutting this prevents the AI from controlling the door unless it has hacked the door through the power connection (which takes about a minute). If both main and backup power are cut, as well as this wire, then the AI cannot operate or hack the door at all. //aiControlDisabled: If 1, AI control is disabled until the AI hacks back in and disables the lock. If 2, the AI has bypassed the lock. If -1, the control is enabled but the AI had bypassed it earlier, so if it is disabled again the AI would have no trouble getting back in. @@ -128,9 +143,11 @@ var/const/AIRLOCK_WIRE_LIGHT = 2048 //Sending a pulse through flashes the red light on the door (if the door has power). if(A.arePowerSystemsOn() && A.density) A.do_animate("deny") + if(AIRLOCK_WIRE_MAIN_POWER1, AIRLOCK_WIRE_MAIN_POWER2) //Sending a pulse through either one causes a breaker to trip, disabling the door for 10 seconds if backup power is connected, or 1 minute if not (or until backup power comes back on, whichever is shorter). A.loseMainPower() + if(AIRLOCK_WIRE_DOOR_BOLTS) //one wire for door bolts. Sending a pulse through this drops door bolts if they're not down (whether power's on or not), //raises them if they are down (only if power's on) @@ -142,29 +159,32 @@ var/const/AIRLOCK_WIRE_LIGHT = 2048 if(AIRLOCK_WIRE_BACKUP_POWER1, AIRLOCK_WIRE_BACKUP_POWER2) //two wires for backup power. Sending a pulse through either one causes a breaker to trip, but this does not disable it unless main power is down too (in which case it is disabled for 1 minute or however long it takes main power to come back, whichever is shorter). A.loseBackupPower() - if(AIRLOCK_WIRE_AI_CONTROL) - if(A.aiControlDisabled == 0) - A.aiControlDisabled = 1 - else if(A.aiControlDisabled == -1) - A.aiControlDisabled = 2 - spawn(10) - if(A) - if(A.aiControlDisabled == 1) - A.aiControlDisabled = 0 - else if(A.aiControlDisabled == 2) - A.aiControlDisabled = -1 + if(AIRLOCK_WIRE_AI_CONTROL) + //Sending a pulse toggles whether AI can bolt the doors (if allowed by the door type and not emagged) + if(A.emagged) + return + switch(A.aiBoltingSetup) + if(AIRLOCK_AI_BOLTING_ALLOW, AIRLOCK_AI_BOLTING_DENY, AIRLOCK_AI_BOLTING_NEVER) // these doors don't allow change + return + else + A.aiBolting = !A.aiBolting if(AIRLOCK_WIRE_ELECTRIFY) //one wire for electrifying the door. Sending a pulse through this electrifies the door for 30 seconds. A.electrify(30) + if(AIRLOCK_WIRE_OPEN_DOOR) //tries to open the door without ID //will succeed only if the ID wire is cut or the door requires no access and it's not emagged - if(A.emagged) return + if(A.emagged) + return if(!A.requiresID() || A.check_access(null)) - if(A.density) A.open() - else A.close() + if(A.density) + A.open() + else + A.close() + if(AIRLOCK_WIRE_SAFETY) A.safe = !A.safe if(!A.density) @@ -188,27 +208,27 @@ var/const/AIRLOCK_WIRE_LIGHT = 2048 /datum/wires/airlock/proc/index_to_type(var/index) switch(index) - if(1) + if(AIRLOCK_WIRE_IDSCAN) return "ID Scan" - if(2) + if(AIRLOCK_WIRE_MAIN_POWER1) return "Power" - if(4) + if(AIRLOCK_WIRE_MAIN_POWER2) return "Power" - if(8) + if(AIRLOCK_WIRE_DOOR_BOLTS) return "Bolts" - if(16) + if(AIRLOCK_WIRE_BACKUP_POWER1) return "Backup Power" - if(32) + if(AIRLOCK_WIRE_BACKUP_POWER2) return "Backup Power" - if(64) + if(AIRLOCK_WIRE_OPEN_DOOR) return "Actuation" - if(128) + if(AIRLOCK_WIRE_AI_CONTROL) return "AI Control" - if(256) + if(AIRLOCK_WIRE_ELECTRIFY) return "Anti-tampering" - if(512) + if(AIRLOCK_WIRE_SAFETY) return "Safety" - if(1024) + if(AIRLOCK_WIRE_SPEED) return "Speed" - if(2048) - return "Bolt Lights" \ No newline at end of file + if(AIRLOCK_WIRE_LIGHT) + return "Bolt Lights" diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 7b3cba071ac..7ddced850af 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -29,6 +29,11 @@ var/lights = TRUE // bolt lights show by default var/aiDisabledIdScanner = FALSE var/aiHacking = FALSE + var/aiBolting = null // whether AI is allowed to bolt this door (use the aiBoltingSetup var to override the initial value of this) + var/aiBoltingSetup = AIRLOCK_AI_BOLTING_AUTO + var/aiBoltingDelay = 8 // how long it takes AIs to drop bolts (in seconds) + var/aiUnBoltingDelay = 4 // how long it takes AIs to raise bolts (in seconds) + var/aiActionTimer = null var/obj/machinery/door/airlock/closeOther = null var/closeOtherId = null var/lockdownbyai = null @@ -62,6 +67,72 @@ var/insecure = 1 //if the door is insecure it will open when power runs out var/securitylock = FALSE + +/obj/machinery/door/airlock/Initialize(mapload, obj/structure/door_assembly/assembly = null) + var/on_admin_z = FALSE + //wires & hatch - this needs to be done up here so the hatch isn't generated by the parent Initialize(). + if(loc && isAdminLevel(z)) + on_admin_z = TRUE + hashatch = FALSE + + . = ..() + + //if assembly is given, create the new door from the assembly + if (istype(assembly)) + assembly_type = assembly.type + + electronics = assembly.electronics + electronics.forceMove(src) + + //update the door's access to match the electronics' + secured_wires = electronics.secure + if(electronics.one_access) + req_access = null + req_one_access = electronics.conf_access + else + req_one_access = null + req_access = electronics.conf_access + + //get the name from the assembly + if(assembly.created_name) + name = assembly.created_name + else + name = "[istext(assembly.glass) ? "[assembly.glass] airlock" : assembly.base_name]" + + //get the dir from the assembly + set_dir(assembly.dir) + + unres_dir = electronics.unres_dir + + if (on_admin_z) + secured_wires = TRUE + + if (secured_wires) + wires = new/datum/wires/airlock/secure(src) + else + wires = new/datum/wires/airlock(src) + + if(mapload && src.closeOtherId != null) + for (var/obj/machinery/door/airlock/A in SSmachinery.processing_machines) + if(A.closeOtherId == src.closeOtherId && A != src) + src.closeOther = A + break + + switch(aiBoltingSetup) + if(AIRLOCK_AI_BOLTING_AUTO) + aiBolting = locked // in automatic setup AI can bolt doors that start bolted + if(AIRLOCK_AI_BOLTING_TRUE, AIRLOCK_AI_BOLTING_ALLOW) + aiBolting = TRUE + if(AIRLOCK_AI_BOLTING_FALSE, AIRLOCK_AI_BOLTING_DENY, AIRLOCK_AI_BOLTING_NEVER) + aiBolting = FALSE + +/obj/machinery/door/airlock/Destroy() + qdel(wires) + wires = null + qdel(wifi_receiver) + wifi_receiver = null + return ..() + /obj/machinery/door/airlock/attack_generic(var/mob/user, var/damage) if(stat & (BROKEN|NOPOWER)) if(damage >= 10) @@ -125,6 +196,7 @@ icon = 'icons/obj/doors/Doorext.dmi' assembly_type = /obj/structure/door_assembly/door_assembly_ext hashatch = FALSE + aiBoltingSetup = AIRLOCK_AI_BOLTING_TRUE insecure = 0 /obj/machinery/door/airlock/science @@ -168,6 +240,7 @@ hatch_colour = "#606061" hashatch = FALSE hackProof = TRUE + aiBoltingSetup = AIRLOCK_AI_BOLTING_NEVER /obj/machinery/door/airlock/centcom/attackby(obj/item/I, mob/user) if (operating) @@ -204,6 +277,7 @@ hatch_colour = "#606061" hashatch = FALSE hackProof = TRUE + aiBoltingSetup = AIRLOCK_AI_BOLTING_NEVER obj/machinery/door/airlock/glass_centcom/attackby(obj/item/I, mob/user) if (operating) @@ -243,6 +317,9 @@ obj/machinery/door/airlock/glass_centcom/attackby(obj/item/I, mob/user) maxhealth = 800 panel_visible_while_open = TRUE insecure = 0 + aiBoltingSetup = AIRLOCK_AI_BOLTING_ALLOW + aiBoltingDelay = 12 + aiUnBoltingDelay = 8 /obj/machinery/door/airlock/vault/bolted icon_state = "door_locked" @@ -276,6 +353,7 @@ obj/machinery/door/airlock/glass_centcom/attackby(obj/item/I, mob/user) hatch_colour = "#5b5b5b" var/hatch_colour_bolted = "#695a5a" insecure = 0 + aiBoltingSetup = AIRLOCK_AI_BOLTING_TRUE /obj/machinery/door/airlock/hatch/update_icon()//Special hatch colour setting for this one snowflakey door that changes color when bolted if (hashatch) @@ -428,6 +506,9 @@ obj/machinery/door/airlock/glass_centcom/attackby(obj/item/I, mob/user) hatch_colour = "#5a5a66" maxhealth = 600 insecure = 0 + aiBoltingSetup = AIRLOCK_AI_BOLTING_ALLOW + aiBoltingDelay = 10 + aiUnBoltingDelay = 5 /obj/machinery/door/airlock/skrell name = "airlock" @@ -493,20 +574,19 @@ obj/machinery/door/airlock/glass_centcom/attackby(obj/item/I, mob/user) /* About the new airlock wires panel: -* An airlock wire dialog can be accessed by the normal way or by using wirecutters or a multitool on the door while the wire-panel is open. This would show the following wires, which you can either wirecut/mend or send a multitool pulse through. There are 9 wires. +* An airlock wire dialog can be accessed by the normal way or by using wirecutters or a multitool on the door while the wire-panel is open. This would show the following wires, which you can either wirecut/mend or send a multitool pulse through. There are 9 types of wires. * one wire from the ID scanner. Sending a pulse through this flashes the red light on the door (if the door has power). If you cut this wire, the door will stop recognizing valid IDs. (If the door has 0000 access, it still opens and closes, though) * two wires for power. Sending a pulse through either one causes a breaker to trip, disabling the door for 10 seconds if backup power is connected, or 1 minute if not (or until backup power comes back on, whichever is shorter). Cutting either one disables the main door power, but unless backup power is also cut, the backup power re-powers the door in 10 seconds. While unpowered, the door may be open, but bolts-raising will not work. Cutting these wires may electrocute the user. * one wire for door bolts. Sending a pulse through this drops door bolts (whether the door is powered or not) or raises them (if it is). Cutting this wire also drops the door bolts, and mending it does not raise them. If the wire is cut, trying to raise the door bolts will not work. * two wires for backup power. Sending a pulse through either one causes a breaker to trip, but this does not disable it unless main power is down too (in which case it is disabled for 1 minute or however long it takes main power to come back, whichever is shorter). Cutting either one disables the backup door power (allowing it to be crowbarred open, but disabling bolts-raising), but may electocute the user. * one wire for opening the door. Sending a pulse through this while the door has power makes it open the door if no access is required. -* one wire for AI control. Sending a pulse through this blocks AI control for a second or so (which is enough to see the AI control light on the panel dialog go off and back on again). Cutting this prevents the AI from controlling the door unless it has hacked the door through the power connection (which takes about a minute). If both main and backup power are cut, as well as this wire, then the AI cannot operate or hack the door at all. +* one wire for AI control. If allowed by the door setup, sending a pulse through this toggles whether AI can bolt the door (shows as green light in dialogue if it can bolt, orange if it can't, red when emagged and thus inoperable by AI). Cutting this prevents the AI from controlling the door unless it has hacked the door through the power connection (which takes about a minute). If both main and backup power are cut, as well as this wire, then the AI cannot operate or hack the door at all. * one wire for electrifying the door. Sending a pulse through this electrifies the door for 30 seconds. Cutting this wire electrifies the door, so that the next person to touch the door without insulated gloves gets electrocuted. (Currently it is also STAYING electrified until someone mends the wire) * one wire for controling door safetys. When active, door does not close on someone. When cut, door will ruin someone's shit. When pulsed, door will immedately ruin someone's shit. -* one wire for controlling door speed. When active, dor closes at normal rate. When cut, door does not close manually. When pulsed, door attempts to close every tick. +* one wire for controlling door speed. When active, door closes at normal rate. When cut, door does not close manually. When pulsed, door attempts to close every tick. */ - /obj/machinery/door/airlock/bumpopen(mob/living/user as mob) //Airlocks now zap you when you 'bump' them open when they're electrified. --NeoFite if(!issilicon(user)) if(src.isElectrified()) @@ -537,7 +617,7 @@ About the new airlock wires panel: /obj/machinery/door/airlock/proc/arePowerSystemsOn() if (stat & (NOPOWER|BROKEN)) - return 0 + return FALSE return (src.main_power_lost_until==0 || src.backup_power_lost_until==0) /obj/machinery/door/airlock/requiresID() @@ -545,10 +625,10 @@ About the new airlock wires panel: /obj/machinery/door/airlock/proc/isAllPowerLoss() if(stat & (NOPOWER|BROKEN)) - return 1 + return TRUE if(mainPowerCablesCut() && backupPowerCablesCut()) - return 1 - return 0 + return TRUE + return FALSE /obj/machinery/door/airlock/proc/mainPowerCablesCut() return src.isWireCut(AIRLOCK_WIRE_MAIN_POWER1) || src.isWireCut(AIRLOCK_WIRE_MAIN_POWER2) @@ -568,8 +648,10 @@ About the new airlock wires panel: backup_power_lost_at = world.time addtimer(CALLBACK(src, .proc/regainBackupPower), 10 SECONDS, TIMER_UNIQUE | TIMER_NO_HASH_WAIT) - // Disable electricity if required - if(electrified_until && isAllPowerLoss()) + if(!arePowerSystemsOn() && !isnull(aiActionTimer)) // AI action timer gets reset if any + deltimer(aiActionTimer) + aiActionTimer = null + if(isAllPowerLoss() && electrified_until) // Disable electricity if required electrify(0) /obj/machinery/door/airlock/proc/loseBackupPower() @@ -578,8 +660,10 @@ About the new airlock wires panel: if (backup_power_lost_until > 0) addtimer(CALLBACK(src, .proc/regainBackupPower), 60 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE | TIMER_NO_HASH_WAIT) - // Disable electricity if required - if(electrified_until && isAllPowerLoss()) + if(!arePowerSystemsOn() && !isnull(aiActionTimer)) // AI action timer gets reset if any + deltimer(aiActionTimer) + aiActionTimer = null + if(isAllPowerLoss() && electrified_until) // Disable electricity if required electrify(0) /obj/machinery/door/airlock/proc/regainMainPower() @@ -588,11 +672,13 @@ About the new airlock wires panel: // If backup power is currently active then disable, otherwise let it count down and disable itself later if(!backup_power_lost_until) backup_power_lost_until = -1 + update_icon() /obj/machinery/door/airlock/proc/regainBackupPower() if(!backupPowerCablesCut()) // Restore backup power only if main power is offline, otherwise permanently disable backup_power_lost_until = main_power_lost_until == 0 ? -1 : 0 + update_icon() /obj/machinery/door/airlock/proc/electrify(var/duration, var/feedback = 0) var/message = "" @@ -760,6 +846,8 @@ About the new airlock wires panel: /obj/machinery/door/airlock/vueui_data_change(list/data, mob/user, datum/vueui/ui) . = ..() data = . || data || list() + VUEUI_SET_IFNOTSET(data["doorArea"], loc.loc?.name, ., data) + VUEUI_SET_IFNOTSET(data["doorName"], name, ., data) VUEUI_SET_CHECK(data["open"], !density, ., data) VUEUI_SET_CHECK(data["plu_main"], main_power_lost_until, ., data) VUEUI_SET_CHECK(data["plua_main"], main_power_lost_at, ., data) @@ -767,10 +855,13 @@ About the new airlock wires panel: VUEUI_SET_CHECK(data["plua_back"], backup_power_lost_at, ., data) VUEUI_SET_CHECK(data["ele"], electrified_until, ., data) VUEUI_SET_CHECK(data["elea"], electrified_at, ., data) - var/isAI = issilicon(user) && !player_is_antag(user.mind) - VUEUI_SET_CHECK(data["isai"], isAI, ., data) + var/antag = player_is_antag(user.mind) + var/isAI = issilicon(user) && !antag var/isAdmin = check_rights(R_ADMIN, show_msg = FALSE) - VUEUI_SET_CHECK(data["isadmin"], isAdmin, ., data) + VUEUI_SET_CHECK(data["isai"], isAI, ., data) + VUEUI_SET_CHECK(data["aiCanBolt"], aiBolting, ., data) + VUEUI_SET_IFNOTSET(data["boltsOverride"], antag || isAdmin, ., data) + VUEUI_SET_IFNOTSET(data["isAdmin"], isAdmin, ., data) VUEUI_SET_CHECK(data["idscan"], !aiDisabledIdScanner, ., data) VUEUI_SET_CHECK(data["bolts"], !locked, ., data) @@ -964,6 +1055,8 @@ About the new airlock wires panel: src.unlock(TRUE) //force it /obj/machinery/door/airlock/CanUseTopic(var/mob/user) + if(check_rights(R_ADMIN, show_msg = FALSE)) + return ..() if(emagged == 1) to_chat(user, SPAN_WARNING("Unable to interface: Internal error.")) return STATUS_CLOSE @@ -983,7 +1076,9 @@ About the new airlock wires panel: if(..()) return 1 + var/isAdmin = check_rights(R_ADMIN, show_msg = FALSE) var/activate = text2num(href_list["activate"]) + var/antag = player_is_antag(usr.mind) switch (href_list["command"]) if("idscan") set_idscan(activate, 1) @@ -994,27 +1089,52 @@ About the new airlock wires panel: if(!backup_power_lost_until) src.loseBackupPower() if("bolts") - if(src.isWireCut(AIRLOCK_WIRE_DOOR_BOLTS)) + if(src.isWireCut(AIRLOCK_WIRE_DOOR_BOLTS)) // cut wire is noop to_chat(usr, SPAN_WARNING("The door bolt control wire is cut - Door bolts permanently dropped.")) - else if(activate) - if(issilicon(usr) && !player_is_antag(usr.mind)) - to_chat(usr, SPAN_WARNING("Your programming prevents you from lowering the door bolts.")) - else if(lock()) - to_chat(usr, SPAN_NOTICE("The door bolts have been dropped.")) - else if(!activate) - if(issilicon(usr) && !player_is_antag(usr.mind)) - to_chat(usr, SPAN_NOTICE("The door bolts will raise in five seconds.")) - src.visible_message("[icon2html(src.icon, viewers(get_turf(src)))] [src] announces, \"Bolts set to raise in FIVE SECONDS.\"") - addtimer(CALLBACK(src, .proc/unlock), 50) - else if(unlock()) - to_chat(usr, SPAN_NOTICE("The door bolts have been raised.")) + else if(isAdmin || issilicon(usr)) // controls for silicons, "stealthy" antag silicons and "stealthy" admins + if(src.aiBolting && src.aiBoltingSetup != AIRLOCK_AI_BOLTING_NEVER) + if(!src.arePowerSystemsOn()) // cannot queue actions or "speak" from unpowered doors + to_chat(usr, SPAN_WARNING("The door is unpowered - Cannot [activate ? "drop" : "raise"] bolts.")) + else if(!isnull(src.aiActionTimer)) + to_chat(usr, SPAN_WARNING("An action is already queued. Please wait for it to complete.")) + else if(activate) + to_chat(usr, SPAN_NOTICE("The door bolts should drop in [src.aiBoltingDelay] seconds.")) + src.audible_message("[icon2html(src.icon, viewers(get_turf(src)))] [src] announces, \"Bolts set to drop in [src.aiBoltingDelay] seconds.\"") + src.aiActionTimer = addtimer(CALLBACK(src, .proc/lock), src.aiBoltingDelay SECONDS, TIMER_UNIQUE|TIMER_NO_HASH_WAIT|TIMER_STOPPABLE) + else + to_chat(usr, SPAN_NOTICE("The door bolts should raise in [src.aiUnBoltingDelay] seconds.")) + src.audible_message("[icon2html(src.icon, viewers(get_turf(src)))] [src] announces, \"Bolts set to raise in [src.aiUnBoltingDelay] seconds.\"") + src.aiActionTimer = addtimer(CALLBACK(src, .proc/unlock), src.aiUnBoltingDelay SECONDS, TIMER_UNIQUE|TIMER_NO_HASH_WAIT|TIMER_STOPPABLE) + else + to_chat(usr, SPAN_WARNING("The door is configured not to allow remote bolt operation.")) + else // everyone else + if(activate) + if(src.lock()) + to_chat(usr, SPAN_NOTICE("The door bolts have been dropped.")) + else + if(src.unlock()) + to_chat(usr, SPAN_NOTICE("The door bolts have been raised.")) + if("bolts_override") + if(isAdmin || (issilicon(usr) && antag)) // admin and silicon antag can override + if(!isAdmin && src.isWireCut(AIRLOCK_WIRE_DOOR_BOLTS)) // cut wire is noop, except for admins + to_chat(usr, SPAN_WARNING("The door bolt control wire is cut - Door bolts permanently dropped.")) + else if(!isAdmin && !src.arePowerSystemsOn()) // door must be powered - display friendly message if not (admins can magically skip this) + to_chat(usr, SPAN_WARNING("The door is unpowered - Cannot [activate ? "drop" : "raise"] bolts.")) + else if (!isAdmin && src.aiBoltingSetup == AIRLOCK_AI_BOLTING_NEVER) // not even antags can operate door bolts here + to_chat(usr, SPAN_WARNING("The door is configured not to allow remote bolt operation.")) + else if(activate) + if(src.lock()) + to_chat(usr, SPAN_NOTICE("The door bolts have been dropped.")) + else + if(src.unlock()) + to_chat(usr, SPAN_NOTICE("The door bolts have been raised.")) if("electrify_temporary") - if(issilicon(usr) && !player_is_antag(usr.mind)) + if(!isAdmin && issilicon(usr) && !antag) to_chat(usr, SPAN_WARNING("Your programming prevents you from electrifying the door.")) else electrify(30 * activate, 1) if("electrify_permanently") - if(issilicon(usr) && !player_is_antag(usr.mind)) + if(!isAdmin && issilicon(usr) && !antag) to_chat(usr, SPAN_WARNING("Your programming prevents you from electrifying the door.")) else electrify(-1 * activate, 1) @@ -1023,6 +1143,8 @@ About the new airlock wires panel: to_chat(usr, SPAN_WARNING("The airlock has been welded shut!")) else if(src.locked) to_chat(usr, SPAN_WARNING("The door bolts are down!")) + else if(!src.arePowerSystemsOn() && issilicon(usr)) // AIs get a nice notice that the door is unpowered + to_chat(usr, SPAN_WARNING("The door is unpowered, its motors do not respond to your commands.")) else if(activate && density) open() if (isAI(usr)) @@ -1030,7 +1152,7 @@ About the new airlock wires panel: else if(!activate && !density) close() if("safeties") - if(safe && issilicon(usr) && !player_is_antag(usr.mind)) + if(!isAdmin && safe && issilicon(usr) && !antag) to_chat(usr, SPAN_WARNING("Your programming prevents you from disabling the door safeties.")) else set_safeties(!activate, 1) @@ -1406,6 +1528,9 @@ About the new airlock wires panel: ..() /obj/machinery/door/airlock/proc/lock(var/forced=0) + if(!isnull(src.aiActionTimer)) // reset AI action timer no matter if it finished + deltimer(src.aiActionTimer) + src.aiActionTimer = null if(locked) return 0 if (operating && !forced) return 0 @@ -1416,6 +1541,9 @@ About the new airlock wires panel: return 1 /obj/machinery/door/airlock/proc/unlock(var/forced=0) + if(!isnull(src.aiActionTimer)) // reset AI action timer no matter if it finished + deltimer(src.aiActionTimer) + src.aiActionTimer = null if(!src.locked) return if (!forced) @@ -1430,63 +1558,6 @@ About the new airlock wires panel: return 0 return ..(M) -/obj/machinery/door/airlock/Initialize(mapload, obj/structure/door_assembly/assembly = null) - var/on_admin_z = FALSE - //wires & hatch - this needs to be done up here so the hatch isn't generated by the parent Initialize(). - if(loc && isAdminLevel(z)) - on_admin_z = TRUE - hashatch = FALSE - - . = ..() - - //if assembly is given, create the new door from the assembly - if (istype(assembly)) - assembly_type = assembly.type - - electronics = assembly.electronics - electronics.forceMove(src) - - //update the door's access to match the electronics' - secured_wires = electronics.secure - if(electronics.one_access) - req_access = null - req_one_access = electronics.conf_access - else - req_one_access = null - req_access = electronics.conf_access - - //get the name from the assembly - if(assembly.created_name) - name = assembly.created_name - else - name = "[istext(assembly.glass) ? "[assembly.glass] airlock" : assembly.base_name]" - - //get the dir from the assembly - set_dir(assembly.dir) - - unres_dir = electronics.unres_dir - - if (on_admin_z) - secured_wires = TRUE - - if (secured_wires) - wires = new/datum/wires/airlock/secure(src) - else - wires = new/datum/wires/airlock(src) - - if(mapload && src.closeOtherId != null) - for (var/obj/machinery/door/airlock/A in SSmachinery.processing_machines) - if(A.closeOtherId == src.closeOtherId && A != src) - src.closeOther = A - break - -/obj/machinery/door/airlock/Destroy() - qdel(wires) - wires = null - qdel(wifi_receiver) - wifi_receiver = null - return ..() - // Most doors will never be deconstructed over the course of a round, // so as an optimization defer the creation of electronics until // the airlock is deconstructed diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 1883321a306..80c50155270 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -16,6 +16,7 @@ var/list/ai_verbs_default = list( /mob/living/silicon/ai/proc/ai_statuschange, /mob/living/silicon/ai/proc/ai_store_location, /mob/living/silicon/ai/proc/ai_checklaws, + /mob/living/silicon/ai/proc/ai_help, /mob/living/silicon/ai/proc/control_integrated_radio, /mob/living/silicon/ai/proc/core, /mob/living/silicon/ai/proc/pick_icon, @@ -211,22 +212,8 @@ var/list/ai_verbs_default = list( return ..() /mob/living/silicon/ai/proc/on_mob_init() - to_chat(src, "You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras).") - to_chat(src, "To look at other parts of the station, click on yourself to get a camera menu.") - to_chat(src, "While observing through a camera, you can use most (networked) devices which you can see, such as computers, APCs, intercoms, doors, etc.") - to_chat(src, "To use something, simply click on it.") - to_chat(src, "Use say [get_language_prefix()]b to speak to your cyborgs through binary. Use say :h to speak from an active holopad.") - to_chat(src, "For department channels, use the following say commands:") - - var/radio_text = "" - for(var/i = 1 to common_radio.channels.len) - var/channel = common_radio.channels[i] - var/key = get_radio_key_from_channel(channel) - radio_text += "[key] - [channel]" - if(i != common_radio.channels.len) - radio_text += ", " - - to_chat(src, radio_text) + to_chat(src, "

You are playing the station's AI.

") + to_chat(src, "\[View help\] (or use OOC command AI-Help at any time)
") if(malf && !(mind in malf.current_antagonists)) show_laws() @@ -364,6 +351,34 @@ var/list/ai_verbs_default = list( /mob/living/silicon/ai/rejuvenate() return // TODO: Implement AI rejuvination +/mob/living/silicon/ai/proc/ai_help() + set category = "OOC" + set name = "AI Help" + + var/radio_keys = jointext(src.get_radio_keys(), "
") + var/dat = "\ +

AI Basics

\ +

You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras).

\ +

Familiarize yourself with the GUI buttons in world view. They are shortcuts to running commands for camera tracking, displaying alerts, moving up and down and such.

\ +

While observing through a camera, you can use most (networked) devices which you can see, such as computers, APCs, intercoms, doors, etc. \ + To use something, simply click on it.

\ +

OOC Notes

\ +

Please remember that as an AI you can heavily skew the game in your (and thus usually the crew's) favour. \ + You are extremely effective at identifying and stopping threats, which unfortunately also means that \ + you are potentially very effective at ruining everyone's fun.

\ +

Don't forget to give all players some leeway; you don't have to report every crime as it happens with complete accuracy, \ + call out every suspect or provide unfallible evidence, especially when nobody is asking for it.

\ +

Your role is supposed to be supportive and assistive, not assertive. \ + Try to enhance the game for other players, don't mindlessly shut it down.

\ +

Built-in radio

\ +

You have a built-in radio. The following channels are available:\ +

[radio_keys]
\ + Use these like any other radio with the Say command. \ + Recall the channel list at any time by calling Radio-Settings under AI Commands.\ +

\ + " + usr << browse(enable_ui_theme(usr, dat), "window=aihelp,size=520x700") + /mob/living/silicon/ai/proc/pick_icon() set category = "AI Commands" set name = "Set AI Core Display" @@ -492,6 +507,8 @@ var/list/ai_verbs_default = list( return if(..()) return + if(href_list["view_ai_help"]) + src.ai_help() if (href_list["mach_close"]) if (href_list["mach_close"] == "aialerts") view_alerts = 0 @@ -760,6 +777,20 @@ var/list/ai_verbs_default = list( else return ..() +/mob/living/silicon/ai/proc/get_radio_keys() + var/list/L = list() + var/datum/language/binary/b = new + L += "[get_language_prefix()][b.key] - [b.name] (speak to cyborgs in binary)" + qdel(b) + var/holopad_key = get_radio_key_from_channel("department") + L += "[holopad_key] - Speak from an active Holopad" + for(var/i = 1 to src.common_radio.channels.len) + var/channel = src.common_radio.channels[i] + var/key = get_radio_key_from_channel(channel) + L += "[key] - [channel]" + + return L + /mob/living/silicon/ai/proc/control_integrated_radio() set name = "Radio Settings" set desc = "Allows you to change settings of your radio." @@ -770,6 +801,8 @@ var/list/ai_verbs_default = list( to_chat(src, "Accessing Subspace Transceiver control...") if (src.ai_radio) + var/radio_keys = jointext(src.get_radio_keys(), ", ") + to_chat(src, "Radio keys: [radio_keys]") src.ai_radio.interact(src) /mob/living/silicon/ai/proc/sensor_mode() diff --git a/html/changelogs/amunak-airlocks.yml b/html/changelogs/amunak-airlocks.yml new file mode 100755 index 00000000000..0c6ec6e8f66 --- /dev/null +++ b/html/changelogs/amunak-airlocks.yml @@ -0,0 +1,14 @@ +author: Amunak +delete-after: True +changes: + - rscadd: "Starting message when joining as an AI has been moved to a separate pop-up window and expanded. It is now also available as a verb 'AI-Help'. This prevents message spam when they start while providing more information to new players. It also contains OOC info noting that players should be mindful of not ruining fun for others." + - rscadd: "Airlocks hacking UI now shows pretty colors for the values. Also tweaked some messages and display of the UI. Most importantly, when power is off the message is now simplified to 'all the lights are off'." + - rscadd: "Whether AIs can or cannot bolt airlocks is now determined by new states of the 'AI control' wire. It is no longer just 'off' (or cut or emagged) and 'on'. It can be 'off' (cut), 'red' (emagged), 'orange' (AI can control doors but not bolt them) or 'green' (AI can bolt or unbolt the doors). Pulsing the control wire toggles between green and orange, cutting and emagging still works the same. Some airlocks are 'locked' (can be mapped or changed by subtype; currently high security doors do this) and pulsing the control wire does nothing." + - rscadd: "The 'AI control' state of most airlocks defaults to whether the airlock is locked (mappers and airlock subtypes can override this). This means that most airlocks are by default 'orange', so AIs cannot drop (or raise!) bolts on the vast majority of airlocks. The exception are secure airlocks, hatches (like on tcommsat) and external airlocks whose bolts can be controlled by the AI (in case of external airlocks most are bolted anyway, so AI would have bolt control regardless)." + - rscadd: "There is now an announcement and a delay of 8 seconds for raising and 4 seconds for dropping the bolts by AIs (traitor AIs are not affected by this, though they can choose to 'fake' it). High security doors have even higher delays (10/5) and vault has the highest (12/8). It is also no longer possible to spam the message - only one action can be done at a time and AIs must wait for it to complete." + - tweak: "Tweaked some AI airlock action messages to facilitate the above changes. Additionally the bolt announcement message is now an audible instead of visual." + - tweak: "AIs, when using 'Radio Settings', now see their radio channel keys. They are also displayed in 'AI-Help'." + - bugfix: "Door now properly updates icon when it regains power." + - backend: "Moved code that gathers radio channels for AI to a separate proc." + - admin: "Fixes admin check in airlock VueUI. Also allows admins to actually toggle stuff like bolts, safeties and electrification. De-admin yourself if you want to actually play!" + - refactor: "Refactored some of the airlock code, especially stuff regarding displaying messages and toggling bolts. Some of it was necessary for these changes, other parts I think I just made more readable." diff --git a/maps/runtime/runtime-2.dmm b/maps/runtime/runtime-2.dmm index 8e73b6a4174..91d44bd23dd 100644 --- a/maps/runtime/runtime-2.dmm +++ b/maps/runtime/runtime-2.dmm @@ -107,6 +107,7 @@ opacity = 0 }, /obj/machinery/door/airlock/highsecurity{ + locked = 1; name = "AI Core"; req_access = list(16) }, diff --git a/vueui/src/components/view/misc/doors.vue b/vueui/src/components/view/misc/doors.vue index 0b693ea48d2..3f6ec8b42e6 100644 --- a/vueui/src/components/view/misc/doors.vue +++ b/vueui/src/components/view/misc/doors.vue @@ -1,8 +1,11 @@