Adds a syndicate AI card for nuke ops (#76546)

## About The Pull Request

Adds a syndicate AI card for nuke ops. Costs 12 TC, can be refunded.
Activating it in-hand opens up a ghost poll like normal reinforcements.
## Why It's Good For The Game

> Adds a syndicate AI card for nuke ops. Costs 12 TC, can be refunded.
Activating it in-hand opens up a ghost poll.

It'll be fun to have a lil modsuit pal with you who can maybe help if
you get stunned, in critical condition, or else. With an injector module
they can even save your life, if it's loaded with medicine! Unless they
can't actually inject, but oh well, out of scope.

12 TC seems fair to me, not so expensive that nobody's going to bother
with something that's not really useful in the vast majority of
circumstances, but not so cheap that everyone will get one.

There are also doubtless a huge amount of gimmicks that savvy nukies can
get up to, having a pocket AI.

**Since this needs two players to be fully tested, I wasn't able to do
so. Please testmerge this before merging so we can confirm it works
ingame!**
## Changelog
🆑
add: Adds a syndicate AI card for nuke ops. Costs 12 TC, can be
refunded. Activating it in-hand opens up a ghost poll like normal
reinforcements. Base interaction range for syndicate AIs is one, which
means they can handle electronics only in proximity.
add: Adds a syndicate AI interaction range upgrade for nuke ops, costs 4
TC and can be applied onto a syndicate AI (inside any container) to
increase its interaction range by two per. (Three purchases are
recommended for seven tiles of range!)
/🆑
This commit is contained in:
carlarctg
2023-08-26 16:17:21 -04:00
committed by GitHub
parent 8ab5bceddb
commit 1feffb747a
9 changed files with 142 additions and 20 deletions
+11 -11
View File
@@ -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
..()
+1 -1
View File
@@ -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 ..()
+82 -4
View File
@@ -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")
@@ -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)
@@ -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)
+12 -4
View File
@@ -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)
@@ -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
@@ -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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB