From e75d6b6a793b6ea09ee6445a19f10a2d4d317822 Mon Sep 17 00:00:00 2001 From: gurfan <51427771+gurfan@users.noreply.github.com> Date: Mon, 21 Sep 2020 17:58:59 -0500 Subject: [PATCH] AI Cyborg-Shells (#27708) * deployment pt 1 * deploying, redeploying, action buttons, gibbin * locking and unlocking, deletion, action fixes * grant it there too * compile, fix runtimes * runtimes, fixes * dumb chat spam * progress * gib fix * emaggin blowups, fix bugs * give the action button to both the ai and the shell * compile * i said compile! * smash em * uglify * SMASH * click to shell * tummy * make shells their own chilkd instead of defining them with a var * handle death, carding, other things * compile, more undeployments * fix * shell of [name] * fixes, custom title, improvements * fix errors, show the AI's custom core icon * define these * god DAMMIT * runtimes * fix robo console, fix showing laws * malf AIs wont trigger faction_defeated when they pop in a shell * comment out * shunt fixes, hack feedback * typo * add a proc, hopefully fix soc * oopsie * shells wont get the nuke spell * yeah * make sure soc works with the AI too * oops * green eyes * white eye glow, some sleeps to prevent button mashing * add one complete exoskeleton to the maps * Revert "add one complete exoskeleton to the maps" This reverts commit 34d32c9319763cf252404f405910c242eeb1eb2b. * Update job_controller.dm * make exoskeleton->cyborg shell creation a proc, allow AI control toggle for cyborg shells * var changed * exploit * lower sleep times * dead shells can be swapped, wont auto-gib on death * let AI know why the shell their clicking on wont be created * improve formar Co-authored-by: jknpj * improve format Co-authored-by: jknpj * remove comment Co-authored-by: jknpj * better emaggin * remove src check from eyes * maybe fix login src check...? * revert * Revert "improve format" This reverts commit fd99140972765a94b1faaa29286d10a2035e0009. * format, adjust color * do this differently, tbc * better format * whoops * handle login without src check, fix runtimes, add more feedback * turn off eye glow when not deployed, grey shade of eye glow, handle malf executions properly * fix indentation * remove src checks * fix Co-authored-by: jknpj --- __DEFINES/_macros.dm | 2 + .../dynamic/dynamic_rulesets_midround.dm | 11 +- code/datums/gamemode/factions/malf.dm | 9 +- code/game/jobs/job_controller.dm | 10 ++ code/game/machinery/computer/ai_core.dm | 10 +- code/game/machinery/computer/robot.dm | 6 +- .../game/objects/items/devices/radio/radio.dm | 3 + code/game/objects/items/robot/robot_parts.dm | 158 ++++++++++++++---- .../living/carbon/alien/special/facehugger.dm | 1 + code/modules/mob/living/silicon/ai/ai.dm | 114 ++++++++++++- code/modules/mob/living/silicon/ai/death.dm | 4 + code/modules/mob/living/silicon/ai/login.dm | 23 +-- .../modules/mob/living/silicon/robot/death.dm | 4 + code/modules/mob/living/silicon/robot/laws.dm | 16 +- .../modules/mob/living/silicon/robot/login.dm | 15 +- .../modules/mob/living/silicon/robot/robot.dm | 133 ++++++++++++++- code/modules/power/apc.dm | 8 +- code/modules/projectiles/projectile/change.dm | 11 ++ 18 files changed, 470 insertions(+), 68 deletions(-) diff --git a/__DEFINES/_macros.dm b/__DEFINES/_macros.dm index 6a520a6544f..9e02da5b204 100644 --- a/__DEFINES/_macros.dm +++ b/__DEFINES/_macros.dm @@ -74,6 +74,8 @@ #define isrobot(A) istype(A, /mob/living/silicon/robot) +#define isshell(A) istype(A, /mob/living/silicon/robot/shell) + #define isanimal(A) istype(A, /mob/living/simple_animal) #define iscorgi(A) istype(A, /mob/living/simple_animal/corgi) diff --git a/code/datums/gamemode/dynamic/dynamic_rulesets_midround.dm b/code/datums/gamemode/dynamic/dynamic_rulesets_midround.dm index 3777c0f1cbf..d5ff8333683 100644 --- a/code/datums/gamemode/dynamic/dynamic_rulesets_midround.dm +++ b/code/datums/gamemode/dynamic/dynamic_rulesets_midround.dm @@ -241,7 +241,7 @@ ..() candidates = candidates[CURRENT_LIVING_PLAYERS] for(var/mob/living/player in candidates) - if(!isAI(player)) + if(!isAI(player) && !isshell(player)) candidates -= player continue if(player.z == map.zCentcomm) @@ -256,7 +256,14 @@ unction = ticker.mode.CreateFaction(/datum/faction/malf, null, 1) if(!candidates || !candidates.len) return 0 - var/mob/living/silicon/ai/M = pick(candidates) + var/mob/living/silicon/P = pick(candidates) + var/mob/living/silicon/ai/M + if(isshell(P)) + var/mob/living/silicon/robot/shell/S = P + S.undeploy() + M = S.mainframe + else + M = P assigned += M candidates -= M var/datum/role/malfAI/malf = unction.HandleNewMind(M.mind) diff --git a/code/datums/gamemode/factions/malf.dm b/code/datums/gamemode/factions/malf.dm index 2e60e73fea7..0463c5ec3f7 100644 --- a/code/datums/gamemode/factions/malf.dm +++ b/code/datums/gamemode/factions/malf.dm @@ -39,7 +39,7 @@ for (var/datum/role/R in members) if(!R.antag.current) continue - if(isAI(R.antag.current) && !R.antag.current.isDead()) + if((isAI(R.antag.current) || isshell(R.antag.current)) && !R.antag.current.isDead()) living_ais++ if(!living_ais && stageCongratulations! The station is now under your exclusive control.
You may decide to blow up the station. You have 60 seconds to choose.
You should now be able to use your Explode spell to interface with the nuclear fission device."}) - malfAI.antag.current.add_spell(new /spell/targeted/ai_win, "malf_spell_ready", /obj/abstract/screen/movable/spell_master/malf) - + if(isshell(malfAI.antag.current)) + var/mob/living/silicon/robot/shell/S = malfAI.antag.current + S.mainframe.add_spell(new /spell/targeted/ai_win, "malf_spell_ready", /obj/abstract/screen/movable/spell_master/malf) + else + malfAI.antag.current.add_spell(new /spell/targeted/ai_win, "malf_spell_ready", /obj/abstract/screen/movable/spell_master/malf) return /datum/faction/malf/get_statpanel_addition() diff --git a/code/game/jobs/job_controller.dm b/code/game/jobs/job_controller.dm index 1e585948c26..c17c339fba0 100644 --- a/code/game/jobs/job_controller.dm +++ b/code/game/jobs/job_controller.dm @@ -312,6 +312,16 @@ var/global/datum/controller/occupations/job_master var/datum/job/master_assistant = GetJob("Assistant") count = (officer.current_positions + warden.current_positions + hos.current_positions) + var/datum/job/borg = job_master.GetJob("Cyborg") //spawn a completed cyborg shell if no borgs readied up + var/borg_count = borg.current_positions + if(!borg_count) + for(var/obj/effect/landmark/start/sloc in landmarks_list) + if(sloc.name != "Cyborg") + continue + else + new /obj/item/robot_parts/robot_suit/mapped(sloc.loc) + break + // For those who wanted to be assistant if their preferences were filled, here you go. for(var/mob/new_player/player in unassigned) if(player.client.prefs.alternate_option == BE_ASSISTANT) diff --git a/code/game/machinery/computer/ai_core.dm b/code/game/machinery/computer/ai_core.dm index eddded165e6..0764edf128a 100644 --- a/code/game/machinery/computer/ai_core.dm +++ b/code/game/machinery/computer/ai_core.dm @@ -170,7 +170,9 @@ That prevents a few funky behaviors. if(C.contents.len)//If there is an AI on card. to_chat(U, "Transfer failed: Existing AI found on this terminal. Remove existing AI to install a new one.") else - if(T.mind.GetRole(MALF)) + if(T.deployed) //send them back! + T.shell.undeploy() + if(T.mind?.GetRole(MALF)) to_chat(U, "ERROR: Remote transfer interface disabled.")//Do ho ho ho~ return new /obj/structure/AIcore/deactivated(T.loc)//Spawns a deactivated terminal at AI location. @@ -197,6 +199,8 @@ That prevents a few funky behaviors. var/obj/item/device/aicard/C = src var/mob/living/silicon/ai/A = locate() in C//I love locate(). Best proc ever. if(A)//If AI exists on the card. Else nothing since both are empty. + if(A.deployed) //send them back! + A.shell.undeploy() A.control_disabled = 0 A.forceMove(T.loc)//To replace the terminal. A.update_icon() @@ -219,6 +223,8 @@ That prevents a few funky behaviors. else for(var/mob/living/silicon/ai/A in C) + if(A.deployed) //send them back! + A.shell.undeploy() C.icon_state = "aicard" C.name = "inteliCard" C.overlays.len = 0 @@ -270,6 +276,8 @@ That prevents a few funky behaviors. else for(var/mob/living/silicon/ai/A in C) + if(A.deployed) //send them back! + A.shell.undeploy() C.icon_state = "aicard" C.name = "inteliCard" C.overlays.len = 0 diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm index c1c6abc8c21..c88cde24c37 100644 --- a/code/game/machinery/computer/robot.dm +++ b/code/game/machinery/computer/robot.dm @@ -254,7 +254,11 @@ // message_admins("[key_name_admin(usr)] emagged [R.name] using robotic console!") log_game("[key_name(usr)] emagged [R.name] using robotic console!") R.SetEmagged(TRUE) - to_chat(usr, "Hack successful. [R.name] now has access to illegal technology.") + var/mob/living/silicon/ai/A = usr + if(A.deployed) + to_chat(A.shell, "Hack successful. [R.name] now has access to illegal technology.") + else + to_chat(A, "Hack successful. [R.name] now has access to illegal technology.") if(R.mind.special_role) R.verbs += /mob/living/silicon/robot/proc/ResetSecurityCodes hacking = 0 diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index 265008a803e..28bb7de7cbd 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -280,6 +280,9 @@ else if (isAI(speech.speaker)) speech.job = "AI" + else if (isshell(speech.speaker)) + speech.job = "AI Shell" + // --- Cyborg --- else if (isrobot(speech.speaker)) speech.job = "Cyborg" diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm index 892a397990d..29abf3bb874 100644 --- a/code/game/objects/items/robot/robot_parts.dm +++ b/code/game/objects/items/robot/robot_parts.dm @@ -44,6 +44,10 @@ var/obj/item/weapon/cell/cell = null var/extension = null //For making borgs start with pre-installed better components. Make the var the end of the path including the "/". +/obj/item/robot_parts/chest/complete + cell = new /obj/item/weapon/cell/high + wires = 1.0 + /obj/item/robot_parts/chest/get_cell() return cell @@ -59,6 +63,10 @@ var/obj/item/device/flash/flash1 = null var/obj/item/device/flash/flash2 = null +/obj/item/robot_parts/head/complete + flash1 = new /obj/item/device/flash + flash2 = new /obj/item/device/flash + /obj/item/robot_parts/robot_suit name = "robot endoskeleton" desc = "A complex metal backbone with standard limb sockets and pseudomuscle anchors." @@ -70,6 +78,15 @@ var/obj/item/robot_parts/chest/chest = null var/obj/item/robot_parts/head/head = null var/created_name = "" + var/ai_control = 1 + +/obj/item/robot_parts/robot_suit/mapped + l_arm = new /obj/item/robot_parts/l_arm + r_arm = new /obj/item/robot_parts/r_arm + l_leg = new /obj/item/robot_parts/l_leg + r_leg = new /obj/item/robot_parts/r_leg + chest = new /obj/item/robot_parts/chest/complete + head = new /obj/item/robot_parts/head/complete /obj/item/robot_parts/robot_suit/New() ..() @@ -112,7 +129,7 @@ feedback_inc("cyborg_frames_built",1) return 1 return 0 - + /obj/item/robot_parts/robot_suit/attackby(obj/item/W as obj, mob/user as mob) ..() if(istype(W, /obj/item/stack/sheet/metal) && !l_arm && !r_arm && !l_leg && !r_leg && !chest && !head) @@ -209,47 +226,15 @@ if(!user.drop_item(W)) return - var/mob/living/silicon/robot/O = new /mob/living/silicon/robot(get_turf(loc)) - for(var/P in M.mommi_assembly_parts) //Let's give back all those mommi creation components for(var/obj/item/L in M.contents) if(L == P) L.forceMove(T) M.contents -= L + + create_robot(M) - if(!O) - return - - O.mmi = W - O.invisibility = 0 - O.custom_name = created_name - O.updatename("Default") - - M.brainmob.mind.transfer_to(O) - - if(O.mind && O.mind.special_role) - O.mind.store_memory("In case you look at this after being borged, the objectives are only here until I find a way to make them not show up for you, as I can't simply delete them without screwing up round-end reporting. --NeoFite") - - O.job = "Cyborg" - if(chest.extension) - O.component_extension = chest.extension - O.upgrade_components() - O.cell = chest.cell - O.cell.forceMove(O) - W.forceMove(O) //Should fix cybros run time erroring when blown up. It got deleted before, along with the frame. - - // Since we "magically" installed a cell, we also have to update the correct component. - if(O.cell) - var/datum/robot_component/cell_component = O.components["power cell"] - cell_component.wrapped = O.cell - cell_component.installed = 1 - - feedback_inc("cyborg_birth",1) - - spawn() - O.Namepick() - - qdel(src) + else to_chat(user, "The MMI must go in after everything else!") @@ -262,8 +247,109 @@ src.created_name = t + if(W.force >= 15) //Smash those skeletons + smash() + return +/obj/item/robot_parts/robot_suit/attack_hand(mob/user) + if(ai_control) + to_chat(user, "You disable AI control on the cyborg exoskeleton.") + ai_control = 0 + else + to_chat(user, "You enable AI control on the cyborg exoskeleton.") + ai_control = 1 + +/obj/item/robot_parts/robot_suit/attack_ai(mob/user) + if(!isAI(user)) + return + if(!check_completion()) + return + if(!ai_control) + to_chat(user, "AI control is disabled on that exoskeleton.") + return + var/mob/living/silicon/ai/A = user + if(!A.shell) + A.create_shell(src) + else + to_chat(user, "You already have a shell. Destroy it to create a new one.") + +/obj/item/robot_parts/robot_suit/bullet_act(var/obj/item/projectile/P) + if(P.damage > 10) + smash() + + +/obj/item/robot_parts/robot_suit/proc/smash() + robogibs(loc) + visible_message("[src] blows apart!") + var/turf/T = get_turf(src) + if(l_arm) + l_arm.forceMove(T) + l_arm = null + if(r_arm) + r_arm.forceMove(T) + r_arm = null + if(l_leg) + l_leg.forceMove(T) + l_leg = null + if(r_leg) + r_leg.forceMove(T) + r_leg = null + if(chest) + chest.forceMove(T) + chest = null + if(head) + head.forceMove(T) + head = null + qdel(src) + +/obj/item/robot_parts/robot_suit/proc/create_robot(var/obj/item/device/mmi/M, is_shell = FALSE) + var/robottype + if(is_shell) + robottype = /mob/living/silicon/robot/shell + else + robottype = /mob/living/silicon/robot + var/mob/living/silicon/robot/O = new robottype(get_turf(loc)) + if(!O) + return + if(M) + O.mmi = M + O.invisibility = 0 + O.custom_name = created_name + O.updatename("Default") + + if(M) + M.brainmob.mind.transfer_to(O) + + if(O.mind && O.mind.special_role) + O.mind.store_memory("In case you look at this after being borged, the objectives are only here until I find a way to make them not show up for you, as I can't simply delete them without screwing up round-end reporting. --NeoFite") + + O.job = "Cyborg" + if(chest.extension) + O.component_extension = chest.extension + O.upgrade_components() + O.cell = chest.cell + O.cell.forceMove(O) + if(M) + M.forceMove(O) //Should fix cybros run time erroring when blown up. It got deleted before, along with the frame. + + // Since we "magically" installed a cell, we also have to update the correct component. + if(O.cell) + var/datum/robot_component/cell_component = O.components["power cell"] + cell_component.wrapped = O.cell + cell_component.installed = 1 + + feedback_inc("cyborg_birth",1) + + spawn() + if(M) + O.Namepick() + + qdel(src) + + return O + + /obj/item/robot_parts/chest/attackby(obj/item/W as obj, mob/user as mob) ..() if(istype(W, /obj/item/weapon/cell)) diff --git a/code/modules/mob/living/carbon/alien/special/facehugger.dm b/code/modules/mob/living/carbon/alien/special/facehugger.dm index 65a843842c6..1361d843f3c 100644 --- a/code/modules/mob/living/carbon/alien/special/facehugger.dm +++ b/code/modules/mob/living/carbon/alien/special/facehugger.dm @@ -534,6 +534,7 @@ if (sterile) to_chat(user, "It looks like \the [src]'s has been de-beaked.") return + /obj/item/clothing/mask/facehugger/headcrab/Attach(mob/living/L) if(escaping) return FALSE diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index f325e9d910f..d8af6dec75e 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -41,6 +41,13 @@ var/list/ai_list = list() var/mentions_on = FALSE var/list/holopadoverlays = list() + //Shell stuff + var/mob/living/silicon/robot/shell/shell = null //The shell the AI currently owns + var/datum/action/deploy_shell/deploy_action = new + var/datum/action/detonate/destroy_action = new + var/deployed = 0 //Is the AI currently controlling a borg + var/greeted = 0 //Shitty fix for being repeatedly told the AI greeting + // See VOX_AVAILABLE_VOICES for available values var/vox_voice = "fem"; var/vox_corrupted = FALSE @@ -108,6 +115,10 @@ var/list/ai_list = list() proc_holder_list = new() + //Action Buttons + deploy_action.Grant(src) + destroy_action.Grant(src) + //Determine the AI's lawset if(L && istype(L,/datum/ai_laws)) src.laws = L @@ -152,7 +163,7 @@ var/list/ai_list = list() spawn(1) mind.store_memory("Frequencies list:
Command: [COMM_FREQ]
Security: [SEC_FREQ]
Medical: [MED_FREQ]
Science: [SCI_FREQ]
Engineering: [ENG_FREQ]
Service: [SER_FREQ] Cargo: [SUP_FREQ]
AI private: [AIPRIV_FREQ]
") stored_freqs = 1 - + greeted = 1 job = "AI" ai_list += src ..() @@ -848,6 +859,107 @@ var/list/ai_list = list() station_holomap.toggleHolomap(src,1) +/mob/living/silicon/ai/verb/deploy_to_shell() //Pop into a shell if you own one + set category = "AI Commands" + set name = "Deploy to Shell" + + if(incapacitated()) + to_chat(src, "Not while you're incapacitated.") + return + if(control_disabled) + to_chat(src, "Wireless networking module is offline.") + return + if(istype(loc, /obj/machinery/power/apc)) + to_chat(src, "You can't control a shell while shunted.") + return + + if(shell) //If the silicon already has a linked shell, go to that one! + sleep(1) //spamming the verb breaks things + if (shell.deployed || shell.mainframe != src) + return + if(mind) + to_chat(src, "Taking control of cyborg shell...") + deployed = 1 + mind.transfer_to(shell) + shell.deploy_init(src) + + else //Otherwise, lets see if we can create a new shell + var/list/potential_shells = list() + for(var/obj/item/robot_parts/robot_suit/emptyborg in world) //Looping through the world might not be good + if(!cameranet.checkCameraVis(emptyborg)) //Must be visible + continue + if(!emptyborg.check_completion()) //Must be ready to have a posi/MMI inserted + continue + if(!emptyborg.ai_control) //AI control was disabled + continue + potential_shells.Add(emptyborg) + if(potential_shells.len == 0) + to_chat(src, "No potential cyborg shells available.") + return + var/list/options = list() + for(var/obj/item/robot_parts/robot_suit/S in potential_shells) + options["Exoskeleton #[potential_shells.Find(S)] in [get_area(S)]"] = S + var/choice = input(src, "Which exoskeleton to control?") as null|anything in options + if(shell) //no exploits allowed + to_chat(src, "You already have a shell.") + if(choice) + create_shell(options[choice]) + + +/mob/living/silicon/ai/proc/create_shell(var/obj/item/robot_parts/robot_suit/suit) + if(mind && !shell) + deployed = 1 + to_chat(src, "Taking control of cyborg shell...") + var/mob/living/silicon/robot/shell/R = suit.create_robot(is_shell = 1) + shell = R + mind.transfer_to(R) + R.deploy_init(src) + +/datum/action/deploy_shell + name = "Deploy to AI Shell" + desc = "Wirelessly control your personal cyborg shell or create a new one from an empty exoskeleton." + icon_icon = 'icons/mob/robots.dmi' + button_icon_state = "robot" + +/datum/action/deploy_shell/Trigger() + var/mob/living/silicon/ai/AI = owner + if(!AI) + return + AI.deploy_to_shell() + +/datum/action/detonate/ + name = "Destroy shell" + desc = "Destroy your current shell and make room for a new one." + icon_icon = 'icons/mob/robots.dmi' + button_icon_state = "gibup" + +/datum/action/detonate/Trigger() + if(istype(owner, /mob/living/silicon/robot/shell)) //Pressing the button as a shell + var/mob/living/silicon/robot/shell/R = owner + R.mainframe.shell = null + R.gib() + return TRUE + + else if(istype(owner, /mob/living/silicon/ai)) //Pressing the button as an AI + var/mob/living/silicon/ai/R = owner + if(R.incapacitated()) + to_chat(src, "Not while you're incapacitated.") + return FALSE + if(R.control_disabled) + to_chat(src, "Wireless networking module is offline.") + return FALSE + if(R.shell) + R.shell.gib() + return TRUE + else + to_chat("You have no shell.") + return FALSE + else + to_chat("You can't do that.") + return FALSE + + + //AI_CAMERA_LUMINOSITY /mob/living/silicon/ai/proc/light_cameras() diff --git a/code/modules/mob/living/silicon/ai/death.dm b/code/modules/mob/living/silicon/ai/death.dm index d3758b821aa..746713aff46 100644 --- a/code/modules/mob/living/silicon/ai/death.dm +++ b/code/modules/mob/living/silicon/ai/death.dm @@ -1,4 +1,8 @@ /mob/living/silicon/ai/death(gibbed) + if(deployed) + shell.undeploy() + shell.mainframe = null + shell.gib() if(stat == DEAD) return if(!gibbed) diff --git a/code/modules/mob/living/silicon/ai/login.dm b/code/modules/mob/living/silicon/ai/login.dm index a72b75a6672..99074a4abba 100644 --- a/code/modules/mob/living/silicon/ai/login.dm +++ b/code/modules/mob/living/silicon/ai/login.dm @@ -1,14 +1,15 @@ /mob/living/silicon/ai/Login() //ThisIsDumb(TM) TODO: tidy this up ¬_¬ ~Carn ..() - - 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 ":b to speak to your cyborgs through binary."}) - show_laws() - if(ismalf(src)) - to_chat(src, "These laws may be changed by other players, but you are not required to follow any of them.") + + if(!greeted) + 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 ":b to speak to your cyborgs through binary."}) + show_laws() + if(ismalf(src)) + to_chat(src, "These laws may be changed by other players, but you are not required to follow any of them.") var/datum/runeset/rune_set = global_runesets["blood_cult"] for(var/obj/effect/rune/rune in rune_set.rune_list) //HOLY FUCK WHO THOUGHT LOOPING THROUGH THE WORLD WAS A GOOD IDEA client.images += rune.blood_image @@ -22,9 +23,11 @@ O.mode = 1 O.emotion = "Neutral" view_core() - if (mind && !stored_freqs) + if (mind && !stored_freqs && !greeted) to_chat(src, "The various frequencies used by the crew to communicate have been stored in your mind. Use the verb Notes to access them.") spawn(1) mind.store_memory("Frequencies list:
Command: [COMM_FREQ]
Security: [SEC_FREQ]
Medical: [MED_FREQ]
Science: [SCI_FREQ]
Engineering: [ENG_FREQ]
Service: [SER_FREQ] Cargo: [SUP_FREQ]
AI private: [AIPRIV_FREQ]
") stored_freqs = 1 + if(!greeted) + greeted = 1 client.CAN_MOVE_DIAGONALLY = TRUE diff --git a/code/modules/mob/living/silicon/robot/death.dm b/code/modules/mob/living/silicon/robot/death.dm index bee73a3ec73..3f636f996a9 100644 --- a/code/modules/mob/living/silicon/robot/death.dm +++ b/code/modules/mob/living/silicon/robot/death.dm @@ -89,3 +89,7 @@ ghostize() //Somehow their MMI has no brainmob or something even worse happened. Let's just save their soul from this hell. mmi = null ..() + +/mob/living/silicon/robot/shell/Destroy() + close_connection() + ..() diff --git a/code/modules/mob/living/silicon/robot/laws.dm b/code/modules/mob/living/silicon/robot/laws.dm index 1e41f9edce4..dfe29753d9e 100644 --- a/code/modules/mob/living/silicon/robot/laws.dm +++ b/code/modules/mob/living/silicon/robot/laws.dm @@ -38,6 +38,18 @@ if(ticker.current_state == GAME_STATE_PLAYING) //Only tell them this if the game has started. We might find an AI master for them before it starts if it hasn't. to_chat(who, "Remember, you are not bound to any AI, you are not required to listen to them.") +/mob/living/silicon/robot/shell/show_laws(var/everyone = 0) + laws_sanity_check() + var/who + if (everyone) + who = world + else + who = src + + to_chat(who, "Obey these laws:") + laws.show_laws(who) + + /mob/living/silicon/robot/proc/lawsync() laws_sanity_check() var/datum/ai_laws/master = connected_ai ? connected_ai.laws : null @@ -69,7 +81,9 @@ laws.supplied[index] = temp if(mind) - var/datum/role/mastermalf = connected_ai.mind.GetRole(MALF) + var/datum/role/mastermalf //Workaround for shelled AIs causing runtimes + mastermalf = connected_ai.deployed ? connected_ai.shell.mind.GetRole(MALF) : connected_ai.mind.GetRole(MALF) + if(mastermalf) var/datum/faction/my_new_faction = mastermalf.faction my_new_faction.HandleRecruitedMind(mind) diff --git a/code/modules/mob/living/silicon/robot/login.dm b/code/modules/mob/living/silicon/robot/login.dm index 19cef3c8f1c..50270a537be 100644 --- a/code/modules/mob/living/silicon/robot/login.dm +++ b/code/modules/mob/living/silicon/robot/login.dm @@ -1,13 +1,14 @@ -/mob/living/silicon/robot/Login() +/mob/living/silicon/robot/Login(var/showlaw_override = 0) ..() regenerate_icons() - show_laws(0) + if(!showlaw_override) + show_laws(0) if(module) - module.UpdateModuleHolder(src) - if (mind && !stored_freqs) + module.UpdateModuleHolder(src) + if (mind && !stored_freqs && !showlaw_override) spawn(1) mind.store_memory("Frequencies list:
Command: [COMM_FREQ]
Security: [SEC_FREQ]
Medical: [MED_FREQ]
Science: [SCI_FREQ]
Engineering: [ENG_FREQ]
Service: [SER_FREQ] Cargo: [SUP_FREQ]
AI private: [AIPRIV_FREQ]
") stored_freqs = 1 - /*if(mind) - ticker.mode.remove_revolutionary(mind) - */ + +/mob/living/silicon/robot/shell/Login() + ..(1) diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 1c3bef305bd..c3b9c23c4d2 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -557,13 +557,13 @@ var/list/cyborg_list = list() to_chat(user, "The wires get in your way.") else if(prob(50)) + to_chat(user, "You emag [src]'s interface") + message_admins("[key_name_admin(user)] emagged cyborg [key_name_admin(src)].") sleep(6) - SetEmagged(TRUE) + SetEmagged(TRUE) SetLockdown(TRUE) lawupdate = FALSE disconnect_AI() - to_chat(user, "You emag [src]'s interface") - message_admins("[key_name_admin(user)] emagged cyborg [key_name_admin(src)]. Laws overidden.") log_game("[key_name(user)] emagged cyborg [key_name(src)]. Laws overridden.") clear_supplied_laws() clear_inherent_laws() @@ -1006,8 +1006,10 @@ var/list/cyborg_list = list() overlays.Cut() update_fire() if(!stat && cell != null) - eyes = image(icon,"eyes-[icon_state]", overlay_layer) + var/icon/eyesicon = icon(icon,"eyes-[icon_state]", overlay_layer) + eyes = image(eyesicon,"eyes-[icon_state]", overlay_layer) eyes.plane = overlay_plane + overlays += eyes if(opened) @@ -1342,3 +1344,126 @@ var/list/cyborg_list = list() //Currently only used for borg movement, to avoid awkward situations where borgs with RTG or basic cells are always slowed down /mob/living/silicon/robot/proc/get_percentage_power_for_movement() return clamp(round(cell.maxcharge/4), 0, SILI_LOW_TRIGGER) + + +//AI Shell Control + +/mob/living/silicon/robot/shell + var/deployed = FALSE //Is the shell being controlled right now + var/mob/living/silicon/ai/mainframe = null //The AI the shell belongs to. + var/datum/action/undeployment/undeployment_action = new + var/datum/action/detonate/destroy_action = new + braintype = "AI Shell" + + +/mob/living/silicon/robot/shell/proc/deploy_init(mob/living/silicon/ai/AI) //called right after the AI pops into the shell. + deployed = TRUE + connected_ai = AI + real_name = "Shell of [AI.name]" + name = real_name + mainframe = AI + mainframe.connected_robots |= src + lawupdate = TRUE + lawsync() + if(radio && AI.radio) + radio.channels = AI.radio.channels + radio.subspace_transmission = TRUE + undeployment_action.Grant(src) + destroy_action.Grant(src) + updateicon() + +/datum/action/undeployment + name = "Disconnect from shell" + desc = "Stop controlling your shell and resume normal core operations." + icon_icon = 'icons/mob/AI.dmi' + button_icon_state = "ai" + +/datum/action/undeployment/UpdateButtonIcon() + var/mob/living/silicon/robot/shell/R = owner + if(R.mainframe) + button_icon_state = "[R.mainframe.icon_state]" + else + button_icon_state = "ai" + ..() + +/datum/action/undeployment/Trigger() + if(!..()) + return FALSE + var/mob/living/silicon/robot/shell/R = owner + + R.undeploy() + return TRUE + +/mob/living/silicon/robot/shell/proc/undeploy() + if(!deployed || !mind || !mainframe) + return + sleep(1) //spamming this breaks things + to_chat(src,"Releasing control of cyborg shell...") + mind.transfer_to(mainframe) + mainframe.deployed = 0 + deployed = FALSE + undeployment_action.Remove(src) + destroy_action.Remove(src) + if(radio) //Recalculate the radio channel + radio.recalculateChannels() + if(mainframe.eyeobj) + mainframe.eyeobj.forceMove(loc) + updateicon() + +/mob/living/silicon/robot/shell/proc/close_connection() + if(deployed) + undeploy() + if(mainframe) + mainframe.shell = null + mainframe = null + +/mob/living/silicon/robot/shell/connect_AI() + to_chat(mainframe, "Notice: Connection to cyborg shell re-established." ) + SetLockdown(FALSE) + +/mob/living/silicon/robot/shell/disconnect_AI() + to_chat(src, "Notice: Connection to cyborg shell has been cut.") + SetLockdown(TRUE) + +/mob/living/silicon/robot/shell/emag_act(mob/user as mob) + if(user != src) + spark(src, 5, FALSE) + if(!opened) + if(locked) + if(prob(90)) + to_chat(user, "You emag the cover lock.") + locked = FALSE + else + to_chat(user, "You fail to emag the cover lock.") + if(prob(25)) + to_chat(src, "Hack attempt detected.") + else + to_chat(user, "The cover is already open.") + else + if(emagged) + return TRUE + if(wiresexposed) + to_chat(user, "The wires get in your way.") + else + if(prob(50)) + to_chat(user, "You emag [src]'s interface") + message_admins("[key_name_admin(user)] emagged AI-Cyborg shell [key_name_admin(src)] and destroyed it.") + sleep(6) + gib() + else + to_chat(user, "You fail to unlock [src]'s interface.") + if(prob(25)) + to_chat(src, "Hack attempt detected.") + return TRUE + +/mob/living/silicon/robot/shell/updateicon(var/overlay_layer = ABOVE_LIGHTING_LAYER, var/overlay_plane = LIGHTING_PLANE) + ..(overlay_layer, overlay_plane) + overlays.Cut() + if(!stat && cell != null && deployed) + var/icon/eyesicon = icon(icon,"eyes-[icon_state]", overlay_layer) + eyesicon.Blend(rgb(255,255,255), ICON_ADD) + eyesicon.Blend(rgb(65,65,65), ICON_SUBTRACT) + eyes = image(eyesicon,"eyes-[icon_state]", overlay_layer) + eyes.plane = overlay_plane + + overlays += eyes \ No newline at end of file diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index 6baf50baa6e..e3a083567e7 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -946,8 +946,12 @@ src.malfai = usr:parent else src.malfai = usr - to_chat(malfai, "Hack complete. The APC is now under your exclusive control. [STATION_Z == z?"You now have [M.apcs] under your control.":"As this APC is not located on the station, it is not contributing to your control of it."]") - malfai.handle_regular_hud_updates() + + var/mob/target_malf + target_malf = malfai.deployed ? malfai.shell : malfai + to_chat(target_malf, "Hack complete. The APC is now under your exclusive control. [STATION_Z == z?"You now have [M.apcs] under your control.":"As this APC is not located on the station, it is not contributing to your control of it."]") + target_malf.handle_regular_hud_updates() + update_icon() else if (href_list["occupyapc"]) diff --git a/code/modules/projectiles/projectile/change.dm b/code/modules/projectiles/projectile/change.dm index f47eb65e3a7..5ab0643c400 100644 --- a/code/modules/projectiles/projectile/change.dm +++ b/code/modules/projectiles/projectile/change.dm @@ -19,6 +19,17 @@ if(ismanifested(M) || iscluwnebanned(M)) visible_message("The bolt of change doesn't seem to affect [M] in any way.") return + if(isshell(M)) //Kick out the AI if its a shell + var/mob/living/silicon/robot/shell/R = M + R.close_connection() + + if(isAI(M)) //Force the AI back if its in a shell + var/mob/living/silicon/ai/R = M + if(R.deployed) + R.shell.undeploy() + R.shell.gib() //Destroy the shell, they wont be needing it anymore + + var/mob/living/new_mob // Random chance of fucking up if(type!=null && prob(10))