#define DEFAULT_DOOMSDAY_TIMER 4500 #define DOOMSDAY_ANNOUNCE_INTERVAL 600 GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( /obj/machinery/field/containment, /obj/machinery/power/supermatter_shard, /obj/machinery/doomsday_device, /obj/machinery/nuclearbomb, /obj/machinery/nuclearbomb/selfdestruct, /obj/machinery/nuclearbomb/syndicate, /obj/machinery/syndicatebomb, /obj/machinery/syndicatebomb/badmin, /obj/machinery/syndicatebomb/badmin/clown, /obj/machinery/syndicatebomb/empty, /obj/machinery/syndicatebomb/self_destruct, /obj/machinery/syndicatebomb/training ))) //The malf AI action subtype. All malf actions are subtypes of this. /datum/action/innate/ai name = "AI Action" desc = "You aren't entirely sure what this does, but it's very beepy and boopy." background_icon_state = "bg_tech_blue" icon_icon = 'icons/mob/actions/actions_AI.dmi' var/mob/living/silicon/ai/owner_AI //The owner AI, so we don't have to typecast every time var/uses //If we have multiple uses of the same power var/auto_use_uses = TRUE //If we automatically use up uses on each activation var/cooldown_period //If applicable, the time in deciseconds we have to wait before using any more modules /datum/action/innate/ai/Grant(mob/living/L) . = ..() if(!isAI(owner)) WARNING("AI action [name] attempted to grant itself to non-AI mob [L.real_name] ([L.key])!") qdel(src) else owner_AI = owner /datum/action/innate/ai/IsAvailable() . = ..() if(owner_AI && owner_AI.malf_cooldown > world.time) return /datum/action/innate/ai/Trigger() . = ..() if(auto_use_uses) adjust_uses(-1) if(cooldown_period) owner_AI.malf_cooldown = world.time + cooldown_period /datum/action/innate/ai/proc/adjust_uses(amt, silent) uses += amt if(!silent && uses) to_chat(owner, "[name] now has [uses] use[uses > 1 ? "s" : ""] remaining.") if(!uses) if(initial(uses) > 1) //no need to tell 'em if it was one-use anyway! to_chat(owner, "[name] has run out of uses!") qdel(src) //Framework for ranged abilities that can have different effects by left-clicking stuff. /datum/action/innate/ai/ranged name = "Ranged AI Action" auto_use_uses = FALSE //This is so we can do the thing and disable/enable freely without having to constantly add uses var/obj/effect/proc_holder/ranged_ai/linked_ability //The linked proc holder that contains the actual ability code var/linked_ability_type //The path of our linked ability /datum/action/innate/ai/ranged/New() if(!linked_ability_type) WARNING("Ranged AI action [name] attempted to spawn without a linked ability!") qdel(src) //uh oh! return linked_ability = new linked_ability_type() linked_ability.attached_action = src ..() /datum/action/innate/ai/ranged/adjust_uses(amt, silent) uses += amt if(!silent && uses) to_chat(owner, "[name] now has [uses] use[uses > 1 ? "s" : ""] remaining.") if(!uses) if(initial(uses) > 1) //no need to tell 'em if it was one-use anyway! to_chat(owner, "[name] has run out of uses!") Remove(owner) QDEL_IN(src, 100) //let any active timers on us finish up /datum/action/innate/ai/ranged/Destroy() QDEL_NULL(linked_ability) return ..() /datum/action/innate/ai/ranged/Activate() linked_ability.toggle(owner) return TRUE //The actual ranged proc holder. /obj/effect/proc_holder/ranged_ai var/enable_text = "Hello World!" //Appears when the user activates the ability var/disable_text = "Goodbye Cruel World!" //Context clues! var/datum/action/innate/ai/ranged/attached_action /obj/effect/proc_holder/ranged_ai/Destroy() QDEL_NULL(attached_action) return ..() /obj/effect/proc_holder/ranged_ai/proc/toggle(mob/user) if(active) remove_ranged_ability(disable_text) else add_ranged_ability(user, enable_text) //The datum and interface for the malf unlock menu, which lets them choose actions to unlock. /datum/module_picker var/temp var/processing_time = 50 var/list/possible_modules /datum/module_picker/New() possible_modules = list() for(var/type in typesof(/datum/AI_Module)) var/datum/AI_Module/AM = new type if((AM.power_type && AM.power_type != /datum/action/innate/ai) || AM.upgrade) possible_modules += AM /datum/module_picker/proc/remove_malf_verbs(mob/living/silicon/ai/AI) //Removes all malfunction-related abilities from the target AI. for(var/datum/AI_Module/AM in possible_modules) for(var/datum/action/A in AI.actions) if(istype(A, initial(AM.power_type))) qdel(A) /datum/module_picker/proc/use(mob/user) var/list/dat = list() dat += "Select use of processing time: (currently #[processing_time] left.)
" dat += "
" dat += "Install Module:
" dat += "The number afterwards is the amount of processing time it consumes.
" for(var/datum/AI_Module/large/module in possible_modules) dat += "[module.module_name]\[?\] ([module.cost])
" for(var/datum/AI_Module/small/module in possible_modules) dat += "[module.module_name]\[?\] ([module.cost])
" dat += "
" if(temp) dat += "[temp]" var/datum/browser/popup = new(user, "modpicker", "Malf Module Menu") popup.set_content(dat.Join()) popup.open() /datum/module_picker/Topic(href, href_list) ..() if(!isAI(usr)) return var/mob/living/silicon/ai/A = usr if(A.stat == DEAD) to_chat(A, "You are already dead!") return for(var/datum/AI_Module/AM in possible_modules) if (href_list[AM.mod_pick_name]) // Cost check if(AM.cost > processing_time) temp = "You cannot afford this module." break var/datum/action/innate/ai/action = locate(AM.power_type) in A.actions // Give the power and take away the money. if(AM.upgrade) //upgrade and upgrade() are separate, be careful! AM.upgrade(A) possible_modules -= AM to_chat(A, AM.unlock_text) A.playsound_local(A, AM.unlock_sound, 50, 0) else if(AM.power_type) if(!action) //Unlocking for the first time var/datum/action/AC = new AM.power_type AC.Grant(A) A.current_modules += new AM.type temp = AM.description if(AM.one_purchase) possible_modules -= AM if(AM.unlock_text) to_chat(A, AM.unlock_text) if(AM.unlock_sound) A.playsound_local(A, AM.unlock_sound, 50, 0) else //Adding uses to an existing module action.uses += initial(action.uses) temp = "Additional use[action.uses > 1 ? "s" : ""] added to [action.name]!" processing_time -= AM.cost if(href_list["showdesc"]) if(AM.mod_pick_name == href_list["showdesc"]) temp = AM.description use(usr) //The base module type, which holds info about each ability. /datum/AI_Module var/module_name var/mod_pick_name var/description = "" var/engaged = 0 var/cost = 5 var/one_purchase = FALSE //If this module can only be purchased once. This always applies to upgrades, even if the variable is set to false. var/power_type = /datum/action/innate/ai //If the module gives an active ability, use this. Mutually exclusive with upgrade. var/upgrade //If the module gives a passive upgrade, use this. Mutually exclusive with power_type. var/unlock_text = "Hello World!" //Text shown when an ability is unlocked var/unlock_sound //Sound played when an ability is unlocked /datum/AI_Module/proc/upgrade(mob/living/silicon/AI/AI) //Apply upgrades! return /datum/AI_Module/large //Big, powerful stuff that can only be used once. /datum/AI_Module/small //Weak, usually localized stuff with multiple uses. //Doomsday Device: Starts the self-destruct timer. It can only be stopped by killing the AI completely. /datum/AI_Module/large/nuke_station module_name = "Doomsday Device" mod_pick_name = "nukestation" description = "Activate a weapon that will disintegrate all organic life on the station after a 450 second delay. Can only be used while on the station, will fail if your core is moved off station or destroyed." cost = 130 one_purchase = TRUE power_type = /datum/action/innate/ai/nuke_station unlock_text = "You slowly, carefully, establish a connection with the on-station self-destruct. You can now activate it at any time." /datum/action/innate/ai/nuke_station name = "Doomsday Device" desc = "Activates the doomsday device. This is not reversible." button_icon_state = "doomsday_device" auto_use_uses = FALSE /datum/action/innate/ai/nuke_station/Activate() var/turf/T = get_turf(owner) if(!istype(T) || !(T.z in GLOB.station_z_levels)) to_chat(owner, "You cannot activate the doomsday device while off-station!") return if(alert(owner, "Send arming signal? (true = arm, false = cancel)", "purge_all_life()", "confirm = TRUE;", "confirm = FALSE;") != "confirm = TRUE;") return active = TRUE set_us_up_the_bomb(owner) /datum/action/innate/ai/nuke_station/proc/set_us_up_the_bomb(mob/living/owner) set waitfor = FALSE to_chat(owner, "run -o -a 'selfdestruct'") sleep(5) if(!owner || QDELETED(owner)) return to_chat(owner, "Running executable 'selfdestruct'...") sleep(rand(10, 30)) if(!owner || QDELETED(owner)) return owner.playsound_local(owner, 'sound/misc/bloblarm.ogg', 50, 0) to_chat(owner, "!!! UNAUTHORIZED SELF-DESTRUCT ACCESS !!!") to_chat(owner, "This is a class-3 security violation. This incident will be reported to Central Command.") for(var/i in 1 to 3) sleep(20) if(!owner || QDELETED(owner)) return to_chat(owner, "Sending security report to Central Command.....[rand(0, 9) + (rand(20, 30) * i)]%") sleep(3) if(!owner || QDELETED(owner)) return to_chat(owner, "auth 'akjv9c88asdf12nb' ******************") owner.playsound_local(owner, 'sound/items/timer.ogg', 50, 0) sleep(30) if(!owner || QDELETED(owner)) return to_chat(owner, "Credentials accepted. Welcome, akjv9c88asdf12nb.") owner.playsound_local(owner, 'sound/misc/server-ready.ogg', 50, 0) sleep(5) if(!owner || QDELETED(owner)) return to_chat(owner, "Arm self-destruct device? (Y/N)") owner.playsound_local(owner, 'sound/misc/compiler-stage1.ogg', 50, 0) sleep(20) if(!owner || QDELETED(owner)) return to_chat(owner, "Y") sleep(15) if(!owner || QDELETED(owner)) return to_chat(owner, "Confirm arming of self-destruct device? (Y/N)") owner.playsound_local(owner, 'sound/misc/compiler-stage2.ogg', 50, 0) sleep(10) if(!owner || QDELETED(owner)) return to_chat(owner, "Y") sleep(rand(15, 25)) if(!owner || QDELETED(owner)) return to_chat(owner, "Please repeat password to confirm.") owner.playsound_local(owner, 'sound/misc/compiler-stage2.ogg', 50, 0) sleep(14) if(!owner || QDELETED(owner)) return to_chat(owner, "******************") sleep(40) if(!owner || QDELETED(owner)) return to_chat(owner, "Credentials accepted. Transmitting arming signal...") owner.playsound_local(owner, 'sound/misc/server-ready.ogg', 50, 0) sleep(30) if(!owner || QDELETED(owner)) return priority_announce("Hostile runtimes detected in all station systems, please deactivate your AI to prevent possible damage to its morality core.", "Anomaly Alert", 'sound/AI/aimalf.ogg') set_security_level("delta") var/obj/machinery/doomsday_device/DOOM = new(owner_AI) owner_AI.nuking = TRUE owner_AI.doomsday_device = DOOM owner_AI.doomsday_device.start() for(var/obj/item/pinpointer/nuke/P in GLOB.pinpointer_list) P.switch_mode_to(TRACK_MALF_AI) //Pinpointers start tracking the AI wherever it goes qdel(src) /obj/machinery/doomsday_device icon = 'icons/obj/machines/nuke_terminal.dmi' name = "doomsday device" icon_state = "nuclearbomb_base" desc = "A weapon which disintegrates all organic life in a large area." anchored = TRUE density = TRUE verb_exclaim = "blares" var/timing = FALSE var/obj/effect/countdown/doomsday/countdown var/detonation_timer var/next_announce /obj/machinery/doomsday_device/Initialize() . = ..() countdown = new(src) /obj/machinery/doomsday_device/Destroy() QDEL_NULL(countdown) STOP_PROCESSING(SSfastprocess, src) SSshuttle.clearHostileEnvironment(src) SSmapping.remove_nuke_threat(src) for(var/A in GLOB.ai_list) var/mob/living/silicon/ai/AI = A if(AI.doomsday_device == src) AI.doomsday_device = null return ..() /obj/machinery/doomsday_device/proc/start() detonation_timer = world.time + DEFAULT_DOOMSDAY_TIMER next_announce = world.time + DOOMSDAY_ANNOUNCE_INTERVAL timing = TRUE countdown.start() START_PROCESSING(SSfastprocess, src) SSshuttle.registerHostileEnvironment(src) SSmapping.add_nuke_threat(src) //This causes all blue "circuit" tiles on the map to change to animated red icon state. /obj/machinery/doomsday_device/proc/seconds_remaining() . = max(0, (round((detonation_timer - world.time) / 10))) /obj/machinery/doomsday_device/process() var/turf/T = get_turf(src) if(!T || !(T.z in GLOB.station_z_levels)) minor_announce("DOOMSDAY DEVICE OUT OF STATION RANGE, ABORTING", "ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4", TRUE) SSshuttle.clearHostileEnvironment(src) qdel(src) return if(!timing) STOP_PROCESSING(SSfastprocess, src) return var/sec_left = seconds_remaining() if(!sec_left) timing = FALSE detonate(T.z) else if(world.time >= next_announce) minor_announce("[sec_left] SECONDS UNTIL DOOMSDAY DEVICE ACTIVATION!", "ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4", TRUE) next_announce += DOOMSDAY_ANNOUNCE_INTERVAL /obj/machinery/doomsday_device/proc/detonate(z_level = ZLEVEL_STATION_PRIMARY) sound_to_playing_players('sound/machines/alarm.ogg') sleep(100) for(var/mob/living/L in GLOB.mob_list) var/turf/T = get_turf(L) if(!T || !(T.z in GLOB.station_z_levels)) continue if(issilicon(L)) continue to_chat(L, "The blast wave from [src] tears you atom from atom!") L.dust() to_chat(world, "The AI cleansed the station of life with the doomsday device!") SSticker.force_ending = 1 //AI Turret Upgrade: Increases the health and damage of all turrets. /datum/AI_Module/large/upgrade_turrets module_name = "AI Turret Upgrade" mod_pick_name = "turret" description = "Improves the power and health of all AI turrets. This effect is permanent." cost = 30 upgrade = TRUE unlock_text = "You establish a power diversion to your turrets, upgrading their health and damage." unlock_sound = 'sound/items/rped.ogg' /datum/AI_Module/large/upgrade_turrets/upgrade(mob/living/silicon/AI/AI) for(var/obj/machinery/porta_turret/ai/turret in GLOB.machines) turret.obj_integrity += 30 turret.lethal_projectile = /obj/item/projectile/beam/laser/heavylaser //Once you see it, you will know what it means to FEAR. turret.lethal_projectile_sound = 'sound/weapons/lasercannonfire.ogg' //Hostile Station Lockdown: Locks, bolts, and electrifies every airlock on the station. After 90 seconds, the doors reset. /datum/AI_Module/large/lockdown module_name = "Hostile Station Lockdown" mod_pick_name = "lockdown" description = "Overload the airlock, blast door and fire control networks, locking them down. Caution! This command also electrifies all airlocks. The networks will automatically reset after 90 seconds, briefly \ opening all doors on the station." cost = 30 one_purchase = TRUE power_type = /datum/action/innate/ai/lockdown unlock_text = "You upload a sleeper trojan into the door control systems. You can send a signal to set it off at any time." unlock_sound = 'sound/machines/boltsdown.ogg' /datum/action/innate/ai/lockdown name = "Lockdown" desc = "Closes, bolts, and depowers every airlock, firelock, and blast door on the station. After 90 seconds, they will reset themselves." button_icon_state = "lockdown" uses = 1 /datum/action/innate/ai/lockdown/Activate() for(var/obj/machinery/door/D in GLOB.airlocks) if(!(D.z in GLOB.station_z_levels)) continue INVOKE_ASYNC(D, /obj/machinery/door.proc/hostile_lockdown, src) addtimer(CALLBACK(D, /obj/machinery/door.proc/disable_lockdown), 900) var/obj/machinery/computer/communications/C = locate() in GLOB.machines if(C) C.post_status("alert", "lockdown") minor_announce("Hostile runtime detected in door controllers. Isolation lockdown protocols are now in effect. Please remain calm.","Network Alert:", TRUE) to_chat(owner, "Lockdown initiated. Network reset in 90 seconds.") addtimer(CALLBACK(GLOBAL_PROC, .proc/minor_announce, "Automatic system reboot complete. Have a secure day.", "Network reset:"), 900) //Destroy RCDs: Detonates all non-cyborg RCDs on the station. /datum/AI_Module/large/destroy_rcd module_name = "Destroy RCDs" mod_pick_name = "rcd" description = "Send a specialised pulse to detonate all hand-held and exosuit Rapid Construction Devices on the station." cost = 25 one_purchase = TRUE power_type = /datum/action/innate/ai/destroy_rcds unlock_text = "After some improvisation, you rig your onboard radio to be able to send a signal to detonate all RCDs." unlock_sound = 'sound/items/timer.ogg' /datum/action/innate/ai/destroy_rcds name = "Destroy RCDs" desc = "Detonate all non-cyborg RCDs on the station." button_icon_state = "detonate_rcds" uses = 1 cooldown_period = 100 /datum/action/innate/ai/destroy_rcds/Activate() for(var/I in GLOB.rcd_list) if(!istype(I, /obj/item/construction/rcd/borg)) //Ensures that cyborg RCDs are spared. var/obj/item/construction/rcd/RCD = I RCD.detonate_pulse() to_chat(owner, "RCD detonation pulse emitted.") owner.playsound_local(owner, 'sound/machines/twobeep.ogg', 50, 0) //Unlock Mech Domination: Unlocks the ability to dominate mechs. Big shocker, right? /datum/AI_Module/large/mecha_domination module_name = "Unlock Mech Domination" mod_pick_name = "mechjack" description = "Allows you to hack into a mech's onboard computer, shunting all processes into it and ejecting any occupants. Once uploaded to the mech, it is impossible to leave.\ Do not allow the mech to leave the station's vicinity or allow it to be destroyed." cost = 30 upgrade = TRUE unlock_text = "Virus package compiled. Select a target mech at any time. You must remain on the station at all times. Loss of signal will result in total system lockout." unlock_sound = 'sound/mecha/nominal.ogg' /datum/AI_Module/large/mecha_domination/upgrade(mob/living/silicon/ai/AI) AI.can_dominate_mechs = TRUE //Yep. This is all it does. Honk! //Thermal Sensor Override: Unlocks the ability to disable all fire alarms from doing their job. /datum/AI_Module/large/break_fire_alarms module_name = "Thermal Sensor Override" mod_pick_name = "burnpigs" description = "Gives you the ability to override the thermal sensors on all fire alarms. This will remove their ability to scan for fire and thus their ability to alert. \ Anyone can check the fire alarm's interface and may be tipped off by its status." one_purchase = TRUE cost = 25 power_type = /datum/action/innate/ai/break_fire_alarms unlock_text = "You replace the thermal sensing capabilities of all fire alarms with a manual override, allowing you to turn them off at will." unlock_sound = 'goon/sound/machinery/firealarm.ogg' /datum/action/innate/ai/break_fire_alarms name = "Override Thermal Sensors" desc = "Disables the automatic temperature sensing on all fire alarms, making them effectively useless." button_icon_state = "break_fire_alarms" uses = 1 /datum/action/innate/ai/break_fire_alarms/Activate() for(var/obj/machinery/firealarm/F in GLOB.machines) if(!(F.z in GLOB.station_z_levels)) continue F.emagged = TRUE to_chat(owner, "All thermal sensors on the station have been disabled. Fire alerts will no longer be recognized.") owner.playsound_local(owner, 'sound/machines/terminal_off.ogg', 50, 0) //Air Alarm Safety Override: Unlocks the ability to enable flooding on all air alarms. /datum/AI_Module/large/break_air_alarms module_name = "Air Alarm Safety Override" mod_pick_name = "allow_flooding" description = "Gives you the ability to disable safeties on all air alarms. This will allow you to use the environmental mode Flood, which disables scrubbers as well as pressure checks on vents. \ Anyone can check the air alarm's interface and may be tipped off by their nonfunctionality." one_purchase = TRUE cost = 50 power_type = /datum/action/innate/ai/break_air_alarms unlock_text = "You remove the safety overrides on all air alarms, but you leave the confirm prompts open. You can hit 'Yes' at any time... you bastard." unlock_sound = 'sound/effects/space_wind.ogg' /datum/action/innate/ai/break_air_alarms name = "Override Air Alarm Safeties" desc = "Enables the Flood setting on all air alarms." button_icon_state = "break_air_alarms" uses = 1 /datum/action/innate/ai/break_air_alarms/Activate() for(var/obj/machinery/airalarm/AA in GLOB.machines) if(!(AA.z in GLOB.station_z_levels)) continue AA.emagged = TRUE to_chat(owner, "All air alarm safeties on the station have been overriden. Air alarms may now use the Flood environmental mode.") owner.playsound_local(owner, 'sound/machines/terminal_off.ogg', 50, 0) //Overload Machine: Allows the AI to overload a machine, detonating it after a delay. Two uses per purchase. /datum/AI_Module/small/overload_machine module_name = "Machine Overload" mod_pick_name = "overload" description = "Overheats an electrical machine, causing a small explosion and destroying it. Two uses per purchase." cost = 20 power_type = /datum/action/innate/ai/ranged/overload_machine unlock_text = "You enable the ability for the station's APCs to direct intense energy into machinery." unlock_sound = 'sound/effects/comfyfire.ogg' //definitely not comfy, but it's the closest sound to "roaring fire" we have /datum/action/innate/ai/ranged/overload_machine name = "Overload Machine" desc = "Overheats a machine, causing a small explosion after a short time." button_icon_state = "overload_machine" uses = 2 linked_ability_type = /obj/effect/proc_holder/ranged_ai/overload_machine /datum/action/innate/ai/ranged/overload_machine/proc/detonate_machine(obj/machinery/M) if(M && !QDELETED(M)) explosion(get_turf(M), 0, 2, 3, 0) if(M) //to check if the explosion killed it before we try to delete it qdel(M) /obj/effect/proc_holder/ranged_ai/overload_machine active = FALSE ranged_mousepointer = 'icons/effects/overload_machine_target.dmi' enable_text = "You tap into the station's powernet. Click on a machine to detonate it, or use the ability again to cancel." disable_text = "You release your hold on the powernet." /obj/effect/proc_holder/ranged_ai/overload_machine/InterceptClickOn(mob/living/caller, params, obj/machinery/target) if(..()) return if(ranged_ability_user.incapacitated()) remove_ranged_ability() return if(!istype(target)) to_chat(ranged_ability_user, "You can only overload machines!") return if(is_type_in_typecache(target, GLOB.blacklisted_malf_machines)) to_chat(ranged_ability_user, "You cannot overload that device!") return ranged_ability_user.playsound_local(ranged_ability_user, "sparks", 50, 0) attached_action.adjust_uses(-1) target.audible_message("You hear a loud electrical buzzing sound coming from [target]!") addtimer(CALLBACK(attached_action, /datum/action/innate/ai/ranged/overload_machine.proc/detonate_machine, target), 50) //kaboom! remove_ranged_ability("Overcharging machine...") return TRUE //Override Machine: Allows the AI to override a machine, animating it into an angry, living version of itself. /datum/AI_Module/small/override_machine module_name = "Machine Override" mod_pick_name = "override" description = "Overrides a machine's programming, causing it to rise up and attack everyone except other machines. Four uses." cost = 30 power_type = /datum/action/innate/ai/ranged/override_machine unlock_text = "You procure a virus from the Space Dark Web and distribute it to the station's machines." unlock_sound = 'sound/machines/airlock_alien_prying.ogg' /datum/action/innate/ai/ranged/override_machine name = "Override Machine" desc = "Animates a targeted machine, causing it to attack anyone nearby." button_icon_state = "override_machine" uses = 4 linked_ability_type = /obj/effect/proc_holder/ranged_ai/override_machine /datum/action/innate/ai/ranged/override_machine/proc/animate_machine(obj/machinery/M) if(M && !QDELETED(M)) new/mob/living/simple_animal/hostile/mimic/copy/machine(get_turf(M), M, owner, 1) /obj/effect/proc_holder/ranged_ai/override_machine active = FALSE ranged_mousepointer = 'icons/effects/override_machine_target.dmi' enable_text = "You tap into the station's powernet. Click on a machine to animate it, or use the ability again to cancel." disable_text = "You release your hold on the powernet." /obj/effect/proc_holder/ranged_ai/override_machine/InterceptClickOn(mob/living/caller, params, obj/machinery/target) if(..()) return if(ranged_ability_user.incapacitated()) remove_ranged_ability() return if(!istype(target)) to_chat(ranged_ability_user, "You can only animate machines!") return if(!target.can_be_overridden() || is_type_in_typecache(target, GLOB.blacklisted_malf_machines)) to_chat(ranged_ability_user, "That machine can't be overriden!") return ranged_ability_user.playsound_local(ranged_ability_user, 'sound/misc/interference.ogg', 50, 0) attached_action.adjust_uses(-1) target.audible_message("You hear a loud electrical buzzing sound coming from [target]!") addtimer(CALLBACK(attached_action, /datum/action/innate/ai/ranged/override_machine.proc/animate_machine, target), 50) //kabeep! remove_ranged_ability("Sending override signal...") return TRUE //Robotic Factory: Places a large machine that converts humans that go through it into cyborgs. Unlocking this ability removes shunting. /datum/AI_Module/large/place_cyborg_transformer module_name = "Robotic Factory (Removes Shunting)" mod_pick_name = "cyborgtransformer" description = "Build a machine anywhere, using expensive nanomachines, that can convert a living human into a loyal cyborg slave when placed inside." cost = 100 one_purchase = TRUE power_type = /datum/action/innate/ai/place_transformer unlock_text = "You make contact with Space Amazon and request a robotics factory for delivery." unlock_sound = 'sound/machines/ping.ogg' /datum/action/innate/ai/place_transformer name = "Place Robotics Factory" desc = "Places a machine that converts humans into cyborgs. Conveyor belts included!" button_icon_state = "robotic_factory" uses = 1 auto_use_uses = FALSE //So we can attempt multiple times var/list/turfOverlays /datum/action/innate/ai/place_transformer/New() ..() for(var/i in 1 to 3) var/image/I = image("icon"='icons/turf/overlays.dmi') LAZYADD(turfOverlays, I) /datum/action/innate/ai/place_transformer/Activate() if(!owner_AI.can_place_transformer(src)) return active = TRUE if(alert(owner, "Are you sure you want to place the machine here?", "Are you sure?", "Yes", "No") == "No") active = FALSE return if(!owner_AI.can_place_transformer(src)) return var/turf/T = get_turf(owner_AI.eyeobj) var/obj/machinery/transformer/conveyor = new(T) conveyor.masterAI = owner playsound(T, 'sound/effects/phasein.ogg', 100, 1) owner_AI.can_shunt = FALSE to_chat(owner, "You are no longer able to shunt your core to APCs.") adjust_uses(-1) /mob/living/silicon/ai/proc/remove_transformer_image(client/C, image/I, turf/T) if(C && I.loc == T) C.images -= I /mob/living/silicon/ai/proc/can_place_transformer(datum/action/innate/ai/place_transformer/action) if(!eyeobj || !isturf(loc) || !canUseTopic() || !action) return var/turf/middle = get_turf(eyeobj) var/list/turfs = list(middle, locate(middle.x - 1, middle.y, middle.z), locate(middle.x + 1, middle.y, middle.z)) var/alert_msg = "There isn't enough room! Make sure you are placing the machine in a clear area and on a floor." var/success = TRUE for(var/n in 1 to 3) //We have to do this instead of iterating normally because of how overlay images are handled var/turf/T = turfs[n] if(!isfloorturf(T)) success = FALSE var/datum/camerachunk/C = GLOB.cameranet.getCameraChunk(T.x, T.y, T.z) if(!C.visibleTurfs[T]) alert_msg = "You don't have camera vision of this location!" success = FALSE for(var/atom/movable/AM in T.contents) if(AM.density) alert_msg = "That area must be clear of objects!" success = FALSE var/image/I = action.turfOverlays[n] I.loc = T client.images += I I.icon_state = "[success ? "green" : "red"]Overlay" //greenOverlay and redOverlay for success and failure respectively addtimer(CALLBACK(src, .proc/remove_transformer_image, client, I, T), 30) if(!success) to_chat(src, "[alert_msg]") return success //Blackout: Overloads a random number of lights across the station. Three uses. /datum/AI_Module/small/blackout module_name = "Blackout" mod_pick_name = "blackout" description = "Attempts to overload the lighting circuits on the station, destroying some bulbs. Three uses." cost = 15 power_type = /datum/action/innate/ai/blackout unlock_text = "You hook into the powernet and route bonus power towards the station's lighting." unlock_sound = "sparks" /datum/action/innate/ai/blackout name = "Blackout" desc = "Overloads lights across the station." button_icon_state = "blackout" uses = 3 /datum/action/innate/ai/blackout/Activate() for(var/obj/machinery/power/apc/apc in GLOB.apcs_list) if(prob(30 * apc.overload)) apc.overload_lighting() else apc.overload++ to_chat(owner, "Overcurrent applied to the powernet.") owner.playsound_local(owner, "sparks", 50, 0) //Reactivate Camera Network: Reactivates up to 30 cameras across the station. /datum/AI_Module/small/reactivate_cameras module_name = "Reactivate Camera Network" mod_pick_name = "recam" description = "Runs a network-wide diagnostic on the camera network, resetting focus and re-routing power to failed cameras. Can be used to repair up to 30 cameras." cost = 10 one_purchase = TRUE power_type = /datum/action/innate/ai/reactivate_cameras unlock_text = "You deploy nanomachines to the cameranet." unlock_sound = 'sound/items/wirecutter.ogg' /datum/action/innate/ai/reactivate_cameras name = "Reactivate Cameras" desc = "Reactivates disabled cameras across the station; remaining uses can be used later." button_icon_state = "reactivate_cameras" uses = 30 auto_use_uses = FALSE cooldown_period = 30 /datum/action/innate/ai/reactivate_cameras/New() ..() desc = "[desc] There are 30 reactivations remaining." button.desc = desc /datum/action/innate/ai/reactivate_cameras/Activate() var/fixed_cameras = 0 for(var/V in GLOB.cameranet.cameras) if(!uses) break var/obj/machinery/camera/C = V if(!C.status || C.view_range != initial(C.view_range)) C.toggle_cam(owner_AI, 0) //Reactivates the camera based on status. Badly named proc. C.view_range = initial(C.view_range) fixed_cameras++ uses-- //Not adjust_uses() so it doesn't automatically delete or show a message to_chat(owner, "Diagnostic complete! Cameras reactivated: [fixed_cameras]. Reactivations remaining: [uses].") owner.playsound_local(owner, 'sound/items/wirecutter.ogg', 50, 0) adjust_uses(0, TRUE) //Checks the uses remaining if(src && uses) //Not sure if not having src here would cause a runtime, so it's here to be safe desc = "[initial(desc)] There are [uses] reactivations remaining." //Upgrade Camera Network: EMP-proofs all cameras, in addition to giving them x-ray vision. /datum/AI_Module/large/upgrade_cameras module_name = "Upgrade Camera Network" mod_pick_name = "upgradecam" description = "Install broad-spectrum scanning and electrical redundancy firmware to the camera network, enabling EMP-proofing and light-amplified X-ray vision." //I <3 pointless technobabble //This used to have motion sensing as well, but testing quickly revealed that giving it to the whole cameranet is PURE HORROR. one_purchase = TRUE cost = 35 //Decent price for omniscience! upgrade = TRUE unlock_text = "OTA firmware distribution complete! Cameras upgraded: CAMSUPGRADED. Light amplification system online." unlock_sound = 'sound/items/rped.ogg' /datum/AI_Module/large/upgrade_cameras/upgrade(mob/living/silicon/ai/AI) AI.see_override = SEE_INVISIBLE_MINIMUM //Night-vision, without which X-ray would be very limited in power. AI.update_sight() var/upgraded_cameras = 0 for(var/V in GLOB.cameranet.cameras) var/obj/machinery/camera/C = V if(C.assembly) var/upgraded = 0 if(!C.isXRay()) C.upgradeXRay() //Update what it can see. GLOB.cameranet.updateVisibility(C, 0) upgraded = 1 if(!C.isEmpProof()) C.upgradeEmpProof() upgraded = 1 if(upgraded) upgraded_cameras++ unlock_text = replacetext(unlock_text, "CAMSUPGRADED", "[upgraded_cameras]") //This works, since unlock text is called after upgrade() /datum/AI_Module/large/eavesdrop module_name = "Enhanced Surveillance" mod_pick_name = "eavesdrop" description = "Via a combination of hidden microphones and lip reading software, you are able to use your cameras to listen in on conversations." cost = 30 one_purchase = TRUE upgrade = TRUE unlock_text = "OTA firmware distribution complete! Cameras upgraded: Enhanced surveillance package online." unlock_sound = 'sound/items/rped.ogg' /datum/AI_Module/large/eavesdrop/upgrade(mob/living/silicon/ai/AI) if(AI.eyeobj) AI.eyeobj.relay_speech = TRUE #undef DEFAULT_DOOMSDAY_TIMER #undef DOOMSDAY_ANNOUNCE_INTERVAL