diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm index d77923f9c94..516b771c247 100644 --- a/code/_onclick/cyborg.dm +++ b/code/_onclick/cyborg.dm @@ -109,7 +109,7 @@ CtrlShiftClick(user) /obj/machinery/door/airlock/BorgCtrlShiftClick(mob/living/silicon/robot/user) // Sets/Unsets Emergency Access Override Forwards to AI code. - if(get_dist(src, user) <= user.interaction_range) + if(get_dist(src, user) <= user.interaction_range && !(user.control_disabled)) AICtrlShiftClick(user) else ..() @@ -118,7 +118,7 @@ ShiftClick(user) /obj/machinery/door/airlock/BorgShiftClick(mob/living/silicon/robot/user) // Opens and closes doors! Forwards to AI code. - if(get_dist(src, user) <= user.interaction_range) + if(get_dist(src, user) <= user.interaction_range && !(user.control_disabled)) AIShiftClick(user) else ..() @@ -128,44 +128,44 @@ CtrlClick(user) /obj/machinery/door/airlock/BorgCtrlClick(mob/living/silicon/robot/user) // Bolts doors. Forwards to AI code. - if(get_dist(src, user) <= user.interaction_range) + if(get_dist(src, user) <= user.interaction_range && !(user.control_disabled)) AICtrlClick(user) else ..() /obj/machinery/power/apc/BorgCtrlClick(mob/living/silicon/robot/user) // turns off/on APCs. Forwards to AI code. - if(get_dist(src, user) <= user.interaction_range) + if(get_dist(src, user) <= user.interaction_range && !(user.control_disabled)) AICtrlClick(user) else ..() /obj/machinery/power/apc/BorgCtrlShiftClick(mob/living/silicon/robot/user) - if(get_dist(src, user) <= user.interaction_range) + if(get_dist(src, user) <= user.interaction_range && !(user.control_disabled)) AICtrlShiftClick(user) else ..() /obj/machinery/power/apc/BorgShiftClick(mob/living/silicon/robot/user) - if(get_dist(src, user) <= user.interaction_range) + if(get_dist(src, user) <= user.interaction_range && !(user.control_disabled)) AIShiftClick(user) else ..() /obj/machinery/power/apc/BorgAltClick(mob/living/silicon/robot/user) - if(get_dist(src, user) <= user.interaction_range) + if(get_dist(src, user) <= user.interaction_range && !(user.control_disabled)) AIAltClick(user) else ..() /obj/machinery/power/apc/attack_robot_secondary(mob/living/silicon/user, list/modifiers) - if(get_dist(src, user) <= user.interaction_range) + if(get_dist(src, user) <= user.interaction_range && !(user.control_disabled)) return attack_ai_secondary(user, modifiers) else ..() /obj/machinery/turretid/BorgCtrlClick(mob/living/silicon/robot/user) //turret control on/off. Forwards to AI code. - if(get_dist(src, user) <= user.interaction_range) + if(get_dist(src, user) <= user.interaction_range && !(user.control_disabled)) AICtrlClick(user) else ..() @@ -175,13 +175,13 @@ return /obj/machinery/door/airlock/BorgAltClick(mob/living/silicon/robot/user) // Eletrifies doors. Forwards to AI code. - if(get_dist(src, user) <= user.interaction_range) + if(get_dist(src, user) <= user.interaction_range && !(user.control_disabled)) AIAltClick(user) else ..() /obj/machinery/turretid/BorgAltClick(mob/living/silicon/robot/user) //turret lethal on/off. Forwards to AI code. - if(get_dist(src, user) <= user.interaction_range) + if(get_dist(src, user) <= user.interaction_range && !(user.control_disabled)) AIAltClick(user) else ..() diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm index e4ff928985d..680a7a837d5 100644 --- a/code/game/machinery/firealarm.dm +++ b/code/game/machinery/firealarm.dm @@ -471,7 +471,7 @@ // Allows Silicons to disable thermal sensor /obj/machinery/firealarm/BorgCtrlClick(mob/living/silicon/robot/user) - if(get_dist(src,user) <= user.interaction_range) + if(get_dist(src,user) <= user.interaction_range && !(user.control_disabled)) AICtrlClick(user) return return ..() diff --git a/code/game/objects/items/devices/aicard.dm b/code/game/objects/items/devices/aicard.dm index c748d70e6fc..4b03fd8013a 100644 --- a/code/game/objects/items/devices/aicard.dm +++ b/code/game/objects/items/devices/aicard.dm @@ -18,6 +18,88 @@ . = ..() ADD_TRAIT(src, TRAIT_CASTABLE_LOC, INNATE_TRAIT) +/obj/item/computer_disk/syndie_ai_upgrade + name = "AI interaction range upgrade" + desc = "A NT data chip containing information that a syndiCard AI can utilize to improve its wireless interfacing abilities. Simply slap it on top of an intelliCard, MODsuit, or AI core and watch it do its work! It's rumoured that there's something 'pretty awful' in it." + icon = 'icons/obj/antags/syndicate_tools.dmi' + icon_state = "something_awful" + max_capacity = 1000 + w_class = WEIGHT_CLASS_NORMAL + +/obj/item/computer_disk/syndie_ai_upgrade/pre_attack(atom/A, mob/living/user, params) + var/mob/living/silicon/ai/AI + if(isAI(A)) + AI = A + else + AI = locate() in A + if(!AI || AI.interaction_range == INFINITY) + playsound(src,'sound/machines/buzz-sigh.ogg',50,FALSE) + to_chat(user, span_notice("Error! Incompatible object!")) + return ..() + AI.interaction_range += 2 + if(AI.interaction_range > 7) + AI.interaction_range = INFINITY + playsound(src,'sound/machines/twobeep.ogg',50,FALSE) + to_chat(user, span_notice("You insert [src] into [AI]'s compartment, and it beeps as it processes the data.")) + to_chat(AI, span_notice("You process [src], and find yourself able to manipulate electronics from up to [AI.interaction_range] meters!")) + qdel(src) + +/obj/item/aicard/syndie + name = "syndiCard" + desc = "A storage device for AIs. Nanotrasen forgot to make the patent, so the Syndicate made their own version!" + icon = 'icons/obj/aicards.dmi' + icon_state = "syndicard" + base_icon_state = "syndicard" + item_flags = null + force = 7 + +/obj/item/aicard/syndie/loaded + var/being_or_was_used = FALSE + +/obj/item/aicard/syndie/loaded/examine(mob/user) + . = ..() + + . += span_notice("This one has a little S.E.L.F. insignia on the back, and a label next to it that says 'Activate for one FREE aligned AI! Please attempt uplink reintegration or ask your employers for reimbursal if AI is unavailable or belligerent.") + +/obj/item/aicard/syndie/loaded/attack_self(mob/user, modifiers) + if(AI || being_or_was_used) + return ..() + being_or_was_used = TRUE + to_chat(user, span_notice("Connecting to S.E.L.F. dispatch...")) + being_or_was_used = procure_ai(user) + +/obj/item/aicard/syndie/loaded/proc/procure_ai(mob/user) + var/datum/antagonist/nukeop/creator_op = user.mind?.has_antag_datum(/datum/antagonist/nukeop,TRUE) + if(!creator_op) + return FALSE + var/list/nuke_candidates = poll_ghost_candidates("Do you want to play as a syndicate artifical intelligence inside an intelliCard?", ROLE_OPERATIVE, ROLE_OPERATIVE, 150, POLL_IGNORE_SYNDICATE) + if(QDELETED(src)) + return FALSE + if(!LAZYLEN(nuke_candidates)) + to_chat(user, span_warning("Unable to connect to S.E.L.F. dispatch. Please wait and try again later or use the intelliCard on your uplink to get your points refunded.")) + return FALSE + // pick ghost, create AI and transfer + var/mob/dead/observer/ghos = pick(nuke_candidates) + var/mob/living/silicon/ai/weak_syndie/new_ai = new /mob/living/silicon/ai/weak_syndie(get_turf(src), null, ghos) // wow so cool i love how laws go before the mob to insert for no reason this definitely didnt delay this pr for weeks + new_ai.key = ghos.key + // create and apply syndie datum + var/datum/antagonist/nukeop/nuke_datum = new() + nuke_datum.send_to_spawnpoint = FALSE + new_ai.mind.add_antag_datum(nuke_datum, creator_op.nuke_team) + new_ai.mind.special_role = "Syndicate AI" + new_ai.faction |= ROLE_SYNDICATE + // Make it look evil!!! + new_ai.hologram_appearance = mutable_appearance('icons/mob/silicon/ai.dmi',"xeno_queen") //good enough + new_ai.icon_state = resolve_ai_icon("hades") // evli + pre_attack(new_ai, user) // i love shitcode! + AI.control_disabled = FALSE // re-enable wireless activity + AI.radio_enabled = TRUE // ditto + var/obj/structure/ai_core/deactivated/detritus = locate() in get_turf(src) + qdel(detritus) + do_sparks(4, TRUE, src) + playsound(src, 'sound/machines/chime.ogg', 25, TRUE) + return TRUE + /obj/item/aicard/Destroy(force) if(AI) AI.ghostize(can_reenter_corpse = FALSE) @@ -142,10 +224,6 @@ . = TRUE if("wireless") AI.control_disabled = !AI.control_disabled - if(!AI.control_disabled) - AI.interaction_range = INFINITY - else - AI.interaction_range = 0 to_chat(AI, span_warning("[src]'s wireless port has been [AI.control_disabled ? "disabled" : "enabled"]!")) . = TRUE if("radio") diff --git a/code/game/objects/items/devices/radio/encryptionkey.dm b/code/game/objects/items/devices/radio/encryptionkey.dm index d3f26b13f34..0e124aafd01 100644 --- a/code/game/objects/items/devices/radio/encryptionkey.dm +++ b/code/game/objects/items/devices/radio/encryptionkey.dm @@ -190,5 +190,13 @@ /obj/item/encryptionkey/ai //ported from NT, this goes 'inside' the AI. channels = list(RADIO_CHANNEL_COMMAND = 1, RADIO_CHANNEL_SECURITY = 1, RADIO_CHANNEL_ENGINEERING = 1, RADIO_CHANNEL_SCIENCE = 1, RADIO_CHANNEL_MEDICAL = 1, RADIO_CHANNEL_SUPPLY = 1, RADIO_CHANNEL_SERVICE = 1, RADIO_CHANNEL_AI_PRIVATE = 1) +/obj/item/encryptionkey/ai/evil //ported from NT, this goes 'inside' the AI. + name = "syndicate binary encryption key" + icon_state = "cypherkey_syndicate" + channels = list(RADIO_CHANNEL_SYNDICATE = 1) + syndie = TRUE + greyscale_config = /datum/greyscale_config/encryptionkey_syndicate + greyscale_colors = "#171717#990000" + /obj/item/encryptionkey/secbot channels = list(RADIO_CHANNEL_AI_PRIVATE = 1, RADIO_CHANNEL_SECURITY = 1) diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index 2b1337f44ee..4623916338b 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -356,6 +356,15 @@ GLOBAL_LIST_INIT(channel_tokens, list( keyslot2 = new /obj/item/encryptionkey/ai command = TRUE +/obj/item/radio/headset/silicon/ai/evil + name = "\proper Evil Integrated Subspace Transceiver " + keyslot2 = new /obj/item/encryptionkey/ai/evil + command = FALSE + +/obj/item/radio/headset/silicon/ai/evil/Initialize(mapload) + . = ..() + make_syndie() + /obj/item/radio/headset/screwdriver_act(mob/living/user, obj/item/tool) user.set_machine(src) if(keyslot || keyslot2) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index b27f56567f8..ba60806133f 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -52,7 +52,6 @@ var/can_dominate_mechs = FALSE var/shunted = FALSE //1 if the AI is currently shunted. Used to differentiate between shunted and ghosted/braindead var/obj/machinery/ai_voicechanger/ai_voicechanger = null // reference to machine that holds the voicechanger - var/control_disabled = FALSE // Set to 1 to stop AI from interacting via Click() var/malfhacking = FALSE // More or less a copy of the above var, so that malf AIs can hack and still get new cyborgs -- NeoFite var/malf_cooldown = 0 //Cooldown var for malf modules, stores a worldtime + cooldown @@ -79,7 +78,7 @@ var/mob/camera/ai_eye/eyeobj var/sprint = 10 var/cooldown = 0 - var/acceleration = 1 + var/acceleration = TRUE var/obj/structure/ai_core/deactivated/linked_core //For exosuit control var/mob/living/silicon/robot/deployed_shell = null //For shell control @@ -200,6 +199,16 @@ RegisterSignal(alert_control.listener, COMSIG_ALARM_LISTENER_TRIGGERED, PROC_REF(alarm_triggered)) RegisterSignal(alert_control.listener, COMSIG_ALARM_LISTENER_CLEARED, PROC_REF(alarm_cleared)) +/mob/living/silicon/ai/weak_syndie + radio = /obj/item/radio/headset/silicon/ai/evil + radio_enabled = TRUE + interaction_range = 1 + sprint = 5 + +/mob/living/silicon/ai/weak_syndie/Initialize(mapload, datum/ai_laws/L, mob/target_ai) + . = ..() + laws = new /datum/ai_laws/syndicate_override + /mob/living/silicon/ai/key_down(_key, client/user) if(findtext(_key, "numpad")) //if it's a numpad number, we can convert it to just the number _key = _key[7] //strings, lists, same thing really @@ -870,7 +879,6 @@ new_core.circuit.battery = battery ai_restore_power()//So the AI initially has power. control_disabled = TRUE //Can't control things remotely if you're stuck in a card! - interaction_range = 0 radio_enabled = FALSE //No talking on the built-in radio for you either! forceMove(card) card.AI = src @@ -990,7 +998,7 @@ update_sight() if(client.eye != src) var/atom/AT = client.eye - AT.get_remote_view_fullscreens(src) + AT?.get_remote_view_fullscreens(src) else clear_fullscreen("remote_view", 0) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 1b20b5d4b15..fbe9986c3ca 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -49,6 +49,7 @@ var/emagged = FALSE var/hack_software = FALSE //Will be able to use hacking actions interaction_range = 7 //wireless control range + var/control_disabled = FALSE // Set to 1 to stop AI from interacting via Click() var/obj/item/modular_computer/pda/silicon/modularInterface diff --git a/code/modules/uplink/uplink_items/nukeops.dm b/code/modules/uplink/uplink_items/nukeops.dm index 1be40c8501b..f5af968993d 100644 --- a/code/modules/uplink/uplink_items/nukeops.dm +++ b/code/modules/uplink/uplink_items/nukeops.dm @@ -597,6 +597,24 @@ cost = 2 purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS +/datum/uplink_item/suits/syndi_intellicard + name = "Pre-Loaded Syndicate Intellicard" + desc = "A syndicate intellicard that can be activated to download a captured Nanotrasen AI, modified with the standard syndicate lawset. You can slot it into your modsuit for a conversational partner! It can additionally control the MODsuit's modules at will, and move your body around even if you're in critical condition or dead. \ + However, due to failsafes activated during the extraction process, the AI is unable to interact with electronics from anywhere but direct proximity..." + item = /obj/item/aicard/syndie/loaded + cost = 12 + purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS + refundable = TRUE + +/datum/uplink_item/suits/synd_ai_upgrade + name = "Syndicate AI Upgrade" + desc = "...unless you buy the Syndicate Upgrade! This data chip allows the captured AI to increase its interaction range by two tiles per application. The Syndicate recommends three or four purchases at most, for a total of seven or infinite meters of range." + item = /obj/item/computer_disk/syndie_ai_upgrade + cost = 4 + purchasable_from = UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS + cant_discount = TRUE + refundable = TRUE + // Devices /datum/uplink_item/device_tools/assault_pod diff --git a/icons/obj/aicards.dmi b/icons/obj/aicards.dmi index 86501e4b43b..2de7da5f6c8 100644 Binary files a/icons/obj/aicards.dmi and b/icons/obj/aicards.dmi differ