Files
Bubberstation/code/modules/power/apc/apc_malf.dm
Kubisopplay a6f20468f9 Biddler Malf (#91895)
## About The Pull Request
AI now gains its malf points over time, based on the number of APCs
hacked. I also added a simple way to lock malf modules from being bought
if you haven't hacked enough APCs, to avoid cheese with delta or robot
factory.
## Why It's Good For The Game
APCs hacked are a constant risk of being found, and instantly targeted,
yet they give one time bonus, that is fairly small. This incentivises
not using antag powers, and using natural AI tools, which blurs the line
for the crew between malf and subverted AIs, and makes fighting it
easier and more boring. With constant gain from the APCs the risk of
being found is rewarded, and malf that hacks a big amount of APCs and
avoids being found can use its powers fairly liberally, at a cost of
having to manage the detection chance.
2025-07-04 12:56:22 -05:00

145 lines
6.2 KiB
Plaintext

/obj/machinery/power/apc/proc/get_malf_status(mob/living/silicon/ai/malf)
if(!istype(malf) || !malf.malf_picker)
return APC_AI_NO_MALF
if(malfai != malf)
return APC_AI_NO_HACK
if(occupier == malf)
return APC_AI_HACK_SHUNT_HERE
if(istype(malf.loc, /obj/machinery/power/apc))
return APC_AI_HACK_SHUNT_ANOTHER
return APC_AI_HACK_NO_SHUNT
/obj/machinery/power/apc/proc/malfhack(mob/living/silicon/ai/malf)
if(!istype(malf))
return
if(get_malf_status(malf) != APC_AI_NO_HACK)
return
if(malf.malfhacking)
to_chat(malf, span_warning("You are already hacking an APC!"))
return
to_chat(malf, span_notice("Beginning override of APC systems. This takes some time, and you cannot perform other actions during the process."))
malf.malfhack = src
malf.malfhacking = addtimer(CALLBACK(malf, TYPE_PROC_REF(/mob/living/silicon/ai/, malfhacked), src), 30 SECONDS + 10*malf.hacked_apcs.len SECONDS, TIMER_STOPPABLE)
var/atom/movable/screen/alert/hackingapc/hacking_apc
hacking_apc = malf.throw_alert(ALERT_HACKING_APC, /atom/movable/screen/alert/hackingapc)
hacking_apc.target = src
/obj/machinery/power/apc/proc/malfoccupy(mob/living/silicon/ai/malf)
if(!istype(malf))
return
if(istype(malf.loc, /obj/machinery/power/apc)) // Already in an APC
to_chat(malf, span_warning("You must evacuate your current APC first!"))
return
if(!malf.can_shunt)
to_chat(malf, span_warning("You cannot shunt!"))
return
if(!is_station_level(z))
return
INVOKE_ASYNC(src, PROC_REF(malfshunt), malf)
/obj/machinery/power/apc/proc/malfshunt(mob/living/silicon/ai/malf)
var/confirm = tgui_alert(malf, "Are you sure that you want to shunt? This will take you out of your core!", "Shunt to [name]?", list("Yes", "No"))
if(confirm != "Yes")
return
malf.ShutOffDoomsdayDevice()
occupier = malf
if (isturf(malf.loc)) // create a deactivated AI core if the AI isn't coming from an emergency mech shunt
malf.linked_core = new /obj/structure/ai_core/deactivated(malf.loc)
malf.linked_core.remote_ai = malf // note that we do not set the deactivated core's core_mmi.brainmob
malf.forceMove(src) // move INTO the APC, not to its tile
if(!findtext(occupier.name, "APC Copy"))
occupier.name = "[malf.name] APC Copy"
malf.shunted = TRUE
occupier.eyeobj.name = "[occupier.name] (AI Eye)"
occupier.eyeobj.forceMove(src.loc)
for(var/obj/item/pinpointer/nuke/disk_pinpointers in GLOB.pinpointer_list)
disk_pinpointers.switch_mode_to(TRACK_MALF_AI) //Pinpointer will track the shunted AI
var/datum/action/innate/core_return/return_action = new
return_action.Grant(occupier)
SEND_SIGNAL(src, COMSIG_SILICON_AI_OCCUPY_APC, occupier)
SEND_SIGNAL(occupier, COMSIG_SILICON_AI_OCCUPY_APC, occupier)
occupier.cancel_camera()
/obj/machinery/power/apc/proc/malfvacate(forced)
if(!occupier)
return
SEND_SIGNAL(occupier, COMSIG_SILICON_AI_VACATE_APC, occupier)
SEND_SIGNAL(src, COMSIG_SILICON_AI_VACATE_APC, occupier)
if(forced)
occupier.forceMove(drop_location())
INVOKE_ASYNC(occupier, TYPE_PROC_REF(/mob/living, death))
occupier.gib(DROP_ALL_REMAINS)
occupier = null
return
if(occupier.linked_core)
occupier.shunted = FALSE
occupier.forceMove(occupier.linked_core.loc)
qdel(occupier.linked_core)
occupier.cancel_camera()
occupier = null
else
stack_trace("An AI: [occupier] has vacated an APC with no linked core and without being gibbed.")
if(!occupier.nuking) //Pinpointers go back to tracking the nuke disk, as long as the AI (somehow) isn't mid-nuking.
for(var/obj/item/pinpointer/nuke/disk_pinpointers in GLOB.pinpointer_list)
disk_pinpointers.switch_mode_to(TRACK_NUKE_DISK)
disk_pinpointers.alert = FALSE
/obj/machinery/power/apc/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card)
. = ..()
if(!.)
return
if(card.AI)
to_chat(user, span_warning("[card] is already occupied!"))
return FALSE
if(!occupier)
to_chat(user, span_warning("There's nothing in [src] to transfer!"))
return FALSE
if(!occupier.mind || !occupier.client)
to_chat(user, span_warning("[occupier] is either inactive or destroyed!"))
return FALSE
if(occupier.linked_core) //if they have an active linked_core, they can't be transferred from an APC
to_chat(user, span_warning("[occupier] is refusing all attempts at transfer!") )
return FALSE
if(transfer_in_progress)
to_chat(user, span_warning("There's already a transfer in progress!"))
return FALSE
if(interaction != AI_TRANS_TO_CARD || occupier.stat)
return FALSE
var/turf/user_turf = get_turf(user)
if(!user_turf)
return FALSE
transfer_in_progress = TRUE
user.visible_message(span_notice("[user] slots [card] into [src]..."), span_notice("Transfer process initiated. Sending request for AI approval..."))
playsound(src, 'sound/machines/click.ogg', 50, TRUE)
SEND_SOUND(occupier, sound('sound/announcer/notice/notice2.ogg')) //To alert the AI that someone's trying to card them if they're tabbed out
if(tgui_alert(occupier, "[user] is attempting to transfer you to \a [card.name]. Do you consent to this?", "APC Transfer", list("Yes - Transfer Me", "No - Keep Me Here")) == "No - Keep Me Here")
to_chat(user, span_danger("AI denied transfer request. Process terminated."))
playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 50, TRUE)
transfer_in_progress = FALSE
return FALSE
if(user.loc != user_turf)
to_chat(user, span_danger("Location changed. Process terminated."))
to_chat(occupier, span_warning("[user] moved away! Transfer canceled."))
transfer_in_progress = FALSE
return FALSE
to_chat(user, span_notice("AI accepted request. Transferring stored intelligence to [card]..."))
to_chat(occupier, span_notice("Transfer starting. You will be moved to [card] shortly."))
if(!do_after(user, 5 SECONDS, target = src))
to_chat(occupier, span_warning("[user] was interrupted! Transfer canceled."))
transfer_in_progress = FALSE
return FALSE
if(!occupier || !card)
transfer_in_progress = FALSE
return FALSE
user.visible_message(span_notice("[user] transfers [occupier] to [card]!"), span_notice("Transfer complete! [occupier] is now stored in [card]."))
to_chat(occupier, span_notice("Transfer complete! You've been stored in [user]'s [card.name]."))
occupier.forceMove(card)
card.AI = occupier
occupier.shunted = FALSE
occupier.cancel_camera()
occupier = null
transfer_in_progress = FALSE
return TRUE