Replaces AI-controlled Maint Drones with AI-controlled Borg Shells

This commit is contained in:
Neerti
2019-04-06 05:06:14 -04:00
committed by VirgoBot
parent 974c6d8433
commit 1d59d8683c
29 changed files with 12407 additions and 275 deletions

View File

@@ -16,6 +16,7 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
// datum_flags
#define DF_VAR_EDITED (1<<0)
#define DF_ISPROCESSING (1<<1)
#define DF_USE_TAG (1<<2)
// /atom/movable movement_type
#define UNSTOPPABLE (1<<0) //Can not be stopped from moving from Cross(), CanPass(), or Uncross() failing. Still bumps everything it passes through, though.

View File

@@ -87,6 +87,7 @@
#define ROBOT_NOTIFICATION_NEW_NAME 2
#define ROBOT_NOTIFICATION_NEW_MODULE 3
#define ROBOT_NOTIFICATION_MODULE_RESET 4
#define ROBOT_NOTIFICATION_AI_SHELL 5
// Appearance change flags
#define APPEARANCE_UPDATE_DNA 0x1
@@ -259,6 +260,7 @@
#define BORG_BRAINTYPE_CYBORG "Cyborg"
#define BORG_BRAINTYPE_POSI "Robot"
#define BORG_BRAINTYPE_DRONE "Drone"
#define BORG_BRAINTYPE_AI_SHELL "AI Shell"
// 'Regular' species.
#define SPECIES_HUMAN "Human"

View File

@@ -1549,4 +1549,29 @@ var/mob/dview/dview_mob = new
/proc/IsValidSrc(datum/D)
if(istype(D))
return !QDELETED(D)
return FALSE
<<<<<<< HEAD
return FALSE
=======
return FALSE
//gives us the stack trace from CRASH() without ending the current proc.
/proc/stack_trace(msg)
CRASH(msg)
/datum/proc/stack_trace(msg)
CRASH(msg)
// \ref behaviour got changed in 512 so this is necesary to replicate old behaviour.
// If it ever becomes necesary to get a more performant REF(), this lies here in wait
// #define REF(thing) (thing && istype(thing, /datum) && (thing:datum_flags & DF_USE_TAG) && thing:tag ? "[thing:tag]" : "\ref[thing]")
/proc/REF(input)
if(istype(input, /datum))
var/datum/thing = input
if(thing.datum_flags & DF_USE_TAG)
if(!thing.tag)
thing.datum_flags &= ~DF_USE_TAG
stack_trace("A ref was requested of an object with DF_USE_TAG set but no tag: [thing]")
else
return "\[[url_encode(thing.tag)]\]"
return "\ref[input]"
>>>>>>> 6a2cd30... Replaces AI-controlled Maint Drones with AI-controlled Borg Shells (#6025)

View File

@@ -60,7 +60,8 @@ var/list/gamemode_cache = list()
var/humans_need_surnames = 0
var/allow_random_events = 0 // enables random events mid-round when set to 1
var/allow_ai = 1 // allow ai job
var/allow_ai_drones = 0 // allow ai controlled drones
var/allow_ai_shells = FALSE // allow AIs to enter and leave special borg shells at will, and for those shells to be buildable.
var/give_free_ai_shell = FALSE // allows a specific spawner object to instantiate a premade AI Shell
var/hostedby = null
var/respawn = 1
var/guest_jobban = 1
@@ -410,8 +411,11 @@ var/list/gamemode_cache = list()
if ("allow_ai")
config.allow_ai = 1
if ("allow_ai_drones")
config.allow_ai_drones = 1
if ("allow_ai_shells")
config.allow_ai_shells = TRUE
if("give_free_ai_shell")
config.give_free_ai_shell = TRUE
// if ("authentication")
// config.enable_authentication = 1

View File

@@ -195,6 +195,8 @@ var/global/datum/emergency_shuttle_controller/emergency_shuttle
//returns 1 if the shuttle is not idle at centcom
/datum/emergency_shuttle_controller/proc/online()
if(!shuttle)
return FALSE
if (!shuttle.location) //not at centcom
return 1
if (wait_for_launch || shuttle.moving_status != SHUTTLE_IDLE)

156
code/datums/soul_link.dm Normal file
View File

@@ -0,0 +1,156 @@
// A datum used to link multiple mobs together in some form.
// The code is from TG, however tweaked to be within the preferred code style.
/mob/living
var/list/owned_soul_links // Soul links we are the owner of.
var/list/shared_soul_links // Soul links we are a/the sharer of.
/mob/living/Destroy()
for(var/s in owned_soul_links)
var/datum/soul_link/S = s
S.owner_died(FALSE)
qdel(s) // If the owner is destroy()'d, the soullink is destroy()'d.
owned_soul_links = null
for(var/s in shared_soul_links)
var/datum/soul_link/S = s
S.sharer_died(FALSE)
S.remove_soul_sharer(src) // If a sharer is destroy()'d, they are simply removed.
shared_soul_links = null
return ..()
// Keeps track of a Mob->Mob (potentially Player->Player) connection.
// Can be used to trigger actions on one party when events happen to another.
// Eg: shared deaths.
// Can be used to form a linked list of mob-hopping.
// Does NOT transfer with minds.
/datum/soul_link
var/mob/living/soul_owner
var/mob/living/soul_sharer
var/id // Optional ID, for tagging and finding specific instances.
/datum/soul_link/Destroy()
if(soul_owner)
LAZYREMOVE(soul_owner.owned_soul_links, src)
soul_owner = null
if(soul_sharer)
LAZYREMOVE(soul_sharer.shared_soul_links, src)
soul_sharer = null
return ..()
/datum/soul_link/proc/remove_soul_sharer(mob/living/sharer)
if(soul_sharer == sharer)
soul_sharer = null
LAZYREMOVE(sharer.shared_soul_links, src)
// Used to assign variables, called primarily by soullink()
// Override this to create more unique soullinks (Eg: 1->Many relationships)
// Return TRUE/FALSE to return the soullink/null in soullink()
/datum/soul_link/proc/parse_args(mob/living/owner, mob/living/sharer)
if(!owner || !sharer)
return FALSE
soul_owner = owner
soul_sharer = sharer
LAZYADD(owner.owned_soul_links, src)
LAZYADD(sharer.shared_soul_links, src)
return TRUE
// Runs after /living death()
// Override this for content.
/datum/soul_link/proc/owner_died(gibbed, mob/living/owner)
// Runs after /living death()
// Override this for content.
/datum/soul_link/proc/sharer_died(gibbed, mob/living/owner)
// Quick-use helper.
/proc/soul_link(typepath, ...)
var/datum/soul_link/S = new typepath()
if(S.parse_args(arglist(args.Copy(2, 0))))
return S
/////////////////
// MULTISHARER //
/////////////////
// Abstract soullink for use with 1 Owner -> Many Sharer setups
/datum/soul_link/multi_sharer
var/list/soul_sharers
/datum/soul_link/multi_sharer/parse_args(mob/living/owner, list/sharers)
if(!owner || !LAZYLEN(sharers))
return FALSE
soul_owner = owner
soul_sharers = sharers
LAZYADD(owner.owned_soul_links, src)
for(var/l in sharers)
var/mob/living/L = l
LAZYADD(L.shared_soul_links, src)
return TRUE
/datum/soul_link/multi_sharer/remove_soul_sharer(mob/living/sharer)
LAZYREMOVE(soul_sharers, sharer)
/////////////////
// SHARED FATE //
/////////////////
// When the soulowner dies, the soulsharer dies, and vice versa
// This is intended for two players(or AI) and two mobs
/datum/soul_link/shared_fate/owner_died(gibbed, mob/living/owner)
if(soul_sharer)
soul_sharer.death(gibbed)
/datum/soul_link/shared_fate/sharer_died(gibbed, mob/living/sharer)
if(soul_owner)
soul_owner.death(gibbed)
//////////////
// ONE WAY //
//////////////
// When the soul owner dies, the soul sharer dies, but NOT vice versa.
// This is intended for two players (or AI) and two mobs.
/datum/soul_link/one_way/owner_died(gibbed, mob/living/owner)
if(soul_sharer)
soul_sharer.dust(FALSE)
/////////////////
// SHARED BODY //
/////////////////
// When the soulsharer dies, they're placed in the soulowner, who remains alive
// If the soulowner dies, the soulsharer is killed and placed into the soulowner (who is still dying)
// This one is intended for one player moving between many mobs
/datum/soul_link/shared_body/owner_died(gibbed, mob/living/owner)
if(soul_owner && soul_sharer)
if(soul_sharer.mind)
soul_sharer.mind.transfer_to(soul_owner)
soul_sharer.death(gibbed)
/datum/soul_link/shared_body/sharer_died(gibbed, mob/living/sharer)
if(soul_owner && soul_sharer && soul_sharer.mind)
soul_sharer.mind.transfer_to(soul_owner)
//////////////////////
// REPLACEMENT POOL //
//////////////////////
// When the owner dies, one of the sharers is placed in the owner's body, fully healed
// Sort of a "winner-stays-on" soullink
// Gibbing ends it immediately
/datum/soul_link/multi_sharer/replacement_pool/owner_died(gibbed, mob/living/owner)
if(LAZYLEN(soul_sharers) && !gibbed) //let's not put them in some gibs
var/list/souls = shuffle(soul_sharers.Copy())
for(var/l in souls)
var/mob/living/L = l
if(L.stat != DEAD && L.mind)
L.mind.transfer_to(soul_owner)
soul_owner.revive(TRUE, TRUE)
L.death(FALSE)
// Lose your claim to the throne!
/datum/soul_link/multi_sharer/replacement_pool/sharer_died(gibbed, mob/living/sharer)
remove_soul_sharer(sharer)

View File

@@ -200,8 +200,8 @@ GLOBAL_LIST_BOILERPLATE(all_deactivated_AI_cores, /obj/structure/AIcore/deactiva
if(!istype(transfer) || locate(/mob/living/silicon/ai) in src)
return
if(transfer.controlling_drone)
transfer.controlling_drone.release_ai_control("Unit control lost. Core transfer completed.")
if(transfer.deployed_shell)
transfer.disconnect_shell("Disconnected from remote shell due to core intelligence transfer.")
transfer.aiRestorePowerRoutine = 0
transfer.control_disabled = 0
transfer.aiRadio.disabledAi = 0

View File

@@ -59,15 +59,15 @@
var/user = usr
if (href_list["wipe"])
var/confirm = alert("Are you sure you want to disable this core's power? This cannot be undone once started.", "Confirm Shutdown", "Yes", "No")
var/confirm = alert("Are you sure you want to disable this core's power? This cannot be undone once started.", "Confirm Shutdown", "No", "Yes")
if(confirm == "Yes" && (CanUseTopic(user, state) == STATUS_INTERACTIVE))
add_attack_logs(user,carded_ai,"Purged from AI Card")
flush = 1
carded_ai.suiciding = 1
to_chat(carded_ai, "Your power has been disabled!")
while (carded_ai && carded_ai.stat != 2)
if(carded_ai.controlling_drone && prob(carded_ai.oxyloss)) //You feel it creeping? Eventually will reach 100, resulting in the second half of the AI's remaining life being lonely.
carded_ai.controlling_drone.release_ai_control("Unit lost. Integrity too low to maintain connection.")
while (carded_ai && carded_ai.stat != DEAD)
if(carded_ai.deployed_shell && prob(carded_ai.oxyloss)) //You feel it creeping? Eventually will reach 100, resulting in the second half of the AI's remaining life being lonely.
carded_ai.disconnect_shell("Disconnecting from remote shell due to insufficent power.")
carded_ai.adjustOxyLoss(2)
carded_ai.updatehealth()
sleep(10)
@@ -80,8 +80,8 @@
carded_ai.control_disabled = text2num(href_list["wireless"])
to_chat(carded_ai, "<span class='warning'>Your wireless interface has been [carded_ai.control_disabled ? "disabled" : "enabled"]!</span>")
to_chat(user, "<span class='notice'>You [carded_ai.control_disabled ? "disable" : "enable"] the AI's wireless interface.</span>")
if(carded_ai.control_disabled && carded_ai.controlling_drone)
carded_ai.controlling_drone.release_ai_control("Unit control terminated at intellicore port.")
if(carded_ai.control_disabled && carded_ai.deployed_shell)
carded_ai.disconnect_shell("Disconnecting from remote shell due to [src] wireless access interface being disabled.")
update_icon()
return 1
@@ -98,7 +98,7 @@
icon_state = "aicard"
/obj/item/device/aicard/proc/grab_ai(var/mob/living/silicon/ai/ai, var/mob/living/user)
if(!ai.client && !ai.controlling_drone)
if(!ai.client && !ai.deployed_shell)
to_chat(user, "<span class='danger'>ERROR:</span> AI [ai.name] is offline. Unable to transfer.")
return 0
@@ -112,9 +112,7 @@
return 0
user.visible_message("\The [user] starts transferring \the [ai] into \the [src]...", "You start transferring \the [ai] into \the [src]...")
to_chat(ai, "<span class='danger'>\The [user] is transferring you into \the [src]!</span>")
if(ai.controlling_drone)
to_chat(ai.controlling_drone, "<span class='danger'>\The [user] is transferring you into \the [src]!</span>")
show_message(span("critical", "\The [user] is transferring you into \the [src]!"))
if(do_after(user, 100))
if(istype(ai.loc, /turf/))
@@ -130,8 +128,7 @@
ai.control_disabled = 1
ai.aiRestorePowerRoutine = 0
carded_ai = ai
if(ai.controlling_drone)
ai.controlling_drone.release_ai_control("Unit control lost.")
ai.disconnect_shell("Disconnected from remote shell due to core intelligence transfer.") //If the AI is controlling a borg, force the player back to core!
if(ai.client)
to_chat(ai, "You have been transferred into a mobile core. Remote access lost.")

View File

@@ -168,28 +168,29 @@
if(!istype(loc,/turf))
to_chat(user, "<span class='warning'>You can't put \the [W] in, the frame has to be standing on the ground to be perfectly precise.</span>")
return
if(!M.brainmob)
to_chat(user, "<span class='warning'>Sticking an empty [W] into the frame would sort of defeat the purpose.</span>")
return
if(!M.brainmob.key)
var/ghost_can_reenter = 0
if(M.brainmob.mind)
for(var/mob/observer/dead/G in player_list)
if(G.can_reenter_corpse && G.mind == M.brainmob.mind)
ghost_can_reenter = 1 //May come in use again at another point.
to_chat(user, "<span class='notice'>\The [W] is completely unresponsive; though it may be able to auto-resuscitate.</span>") //Jamming a ghosted brain into a borg is likely detrimental, and may result in some problems.
return
if(!ghost_can_reenter)
to_chat(user, "<span class='notice'>\The [W] is completely unresponsive; there's no point.</span>")
if(!istype(W, /obj/item/device/mmi/inert))
if(!M.brainmob)
to_chat(user, "<span class='warning'>Sticking an empty [W] into the frame would sort of defeat the purpose.</span>")
return
if(!M.brainmob.key)
var/ghost_can_reenter = 0
if(M.brainmob.mind)
for(var/mob/observer/dead/G in player_list)
if(G.can_reenter_corpse && G.mind == M.brainmob.mind)
ghost_can_reenter = 1 //May come in use again at another point.
to_chat(user, "<span class='notice'>\The [W] is completely unresponsive; though it may be able to auto-resuscitate.</span>") //Jamming a ghosted brain into a borg is likely detrimental, and may result in some problems.
return
if(!ghost_can_reenter)
to_chat(user, "<span class='notice'>\The [W] is completely unresponsive; there's no point.</span>")
return
if(M.brainmob.stat == DEAD)
to_chat(user, "<span class='warning'>Sticking a dead [W] into the frame would sort of defeat the purpose.</span>")
return
if(M.brainmob.stat == DEAD)
to_chat(user, "<span class='warning'>Sticking a dead [W] into the frame would sort of defeat the purpose.</span>")
return
if(jobban_isbanned(M.brainmob, "Cyborg"))
to_chat(user, "<span class='warning'>This [W] does not seem to fit.</span>")
return
if(jobban_isbanned(M.brainmob, "Cyborg"))
to_chat(user, "<span class='warning'>This [W] does not seem to fit.</span>")
return
var/mob/living/silicon/robot/O = new /mob/living/silicon/robot(get_turf(loc), unfinished = 1)
if(!O) return
@@ -197,20 +198,18 @@
user.drop_item()
O.mmi = W
O.post_mmi_setup()
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")
if(M.brainmob)
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")
for(var/datum/language/L in M.brainmob.languages)
O.add_language(L.name)
O.job = "Cyborg"
for(var/datum/language/L in M.brainmob.languages)
O.add_language(L.name)
O.cell = chest.cell
O.cell.loc = O
W.loc = O//Should fix cybros run time erroring when blown up. It got deleted before, along with the frame.

View File

@@ -25,6 +25,7 @@
require_module = 1
/obj/item/borg/upgrade/reset/action(var/mob/living/silicon/robot/R)
<<<<<<< HEAD
if(..()) return 0
R.transform_with_anim() //VOREStation edit: sprite animation
R.uneq_all()
@@ -37,6 +38,11 @@
R.module = null
R.updatename("Default")
=======
if(..())
return 0
R.module_reset()
>>>>>>> 6a2cd30... Replaces AI-controlled Maint Drones with AI-controlled Borg Shells (#6025)
return 1
/obj/item/borg/upgrade/rename

View File

@@ -339,3 +339,16 @@
..()
src.brainmob.name = "[pick(list("PBU","HIU","SINA","ARMA","OSI"))]-[rand(100, 999)]"
src.brainmob.real_name = src.brainmob.name
// This type shouldn't care about brainmobs.
/obj/item/device/mmi/inert
// This is a 'fake' MMI that is used to let AIs control borg shells directly.
// This doesn't inherit from /digital because all that does is add ghost pulling capabilities, which this thing won't need.
/obj/item/device/mmi/inert/ai_remote
name = "\improper AI remote interface"
desc = "A sophisticated board which allows for an artificial intelligence to remotely control a synthetic chassis."
icon = 'icons/obj/module.dmi'
icon_state = "mainboard"
w_class = ITEMSIZE_NORMAL
origin_tech = list(TECH_ENGINEERING = 2, TECH_MATERIAL = 2, TECH_BLUESPACE = 2, TECH_DATA = 3)

View File

@@ -1,4 +1,4 @@
/mob/living/death()
/mob/living/death(gibbed)
clear_fullscreens()
if(ai_holder)
ai_holder.go_sleep()
@@ -11,4 +11,12 @@
var/obj/structure/blob/factory/F = nest
F.spores -= src
nest = null
for(var/s in owned_soul_links)
var/datum/soul_link/S = s
S.owner_died(gibbed)
for(var/s in shared_soul_links)
var/datum/soul_link/S = s
S.sharer_died(gibbed)
. = ..()

View File

@@ -49,10 +49,10 @@ var/list/ai_verbs_default = list(
shouldnt_see = list(/obj/effect/rune)
var/list/network = list(NETWORK_DEFAULT)
var/obj/machinery/camera/camera = null
var/list/connected_robots = list()
var/aiRestorePowerRoutine = 0
var/viewalerts = 0
var/icon/holo_icon//Default is assigned when AI is created.
var/list/connected_robots = list()
var/obj/item/device/pda/ai/aiPDA = null
var/obj/item/device/communicator/aiCommunicator = null
var/obj/item/device/multitool/aiMulti = null
@@ -227,6 +227,28 @@ var/list/ai_verbs_default = list(
return ..()
/mob/living/silicon/ai/Stat()
..()
if(statpanel("Status"))
if(!stat) // Make sure we're not unconscious/dead.
stat(null, text("System integrity: [(health+100)/2]%"))
stat(null, text("Connected synthetics: [connected_robots.len]"))
for(var/mob/living/silicon/robot/R in connected_robots)
var/robot_status = "Nominal"
if(R.shell)
robot_status = "AI SHELL"
else if(R.stat || !R.client)
robot_status = "OFFLINE"
else if(!R.cell || R.cell.charge <= 0)
robot_status = "DEPOWERED"
//Name, Health, Battery, Module, Area, and Status! Everything an AI wants to know about its borgies!
stat(null, text("[R.name] | S.Integrity: [R.health]% | Cell: [R.cell ? "[R.cell.charge]/[R.cell.maxcharge]" : "Empty"] | \
Module: [R.modtype] | Loc: [get_area_name(R, TRUE)] | Status: [robot_status]"))
stat(null, text("AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]")) //Count of total AI shells
else
stat(null, text("Systems nonfunctional"))
/mob/living/silicon/ai/proc/setup_icon()
var/file = file2text("config/custom_sprites.txt")
var/lines = splittext(file, "\n")
@@ -411,6 +433,7 @@ var/list/ai_verbs_default = list(
return 0
/mob/living/silicon/ai/emp_act(severity)
disconnect_shell("Disconnected from remote shell due to ionic interfe%*@$^___")
if (prob(30))
view_core()
..()
@@ -696,8 +719,8 @@ var/list/ai_verbs_default = list(
card.grab_ai(src, user)
else if(W.is_wrench())
if(user == controlling_drone)
to_chat(user, "<span class='notice'>The drone's subsystems resist your efforts to tamper with your bolts.</span>")
if(user == deployed_shell)
to_chat(user, "<span class='notice'>The shell's subsystems resist your efforts to tamper with your bolts.</span>")
return
if(anchored)
playsound(src, W.usesound, 50, 1)

View File

@@ -0,0 +1,62 @@
/mob/living/silicon/ai
var/mob/living/silicon/robot/deployed_shell = null //For shell control
/mob/living/silicon/ai/Initialize()
if(config.allow_ai_shells)
verbs += /mob/living/silicon/ai/proc/deploy_to_shell_act
return ..()
/mob/living/silicon/ai/proc/deploy_to_shell(var/mob/living/silicon/robot/target)
if(!config.allow_ai_shells)
to_chat(src, span("warning", "AI Shells are not allowed on this server. You shouldn't have this verb because of it, so consider making a bug report."))
return
if(incapacitated())
to_chat(src, span("warning", "You are incapacitated!"))
return
if(lacks_power())
to_chat(src, span("warning", "Your core lacks power, wireless is disabled."))
return
if(control_disabled)
to_chat(src, span("warning", "Wireless networking module is offline."))
return
var/list/possible = list()
for(var/borgie in GLOB.available_ai_shells)
var/mob/living/silicon/robot/R = borgie
if(R.shell && !R.deployed && (R.stat != DEAD) && (!R.connected_ai || (R.connected_ai == src) ) )
possible += R
if(!LAZYLEN(possible))
to_chat(src, span("warning", "No usable AI shell beacons detected."))
if(!target || !(target in possible)) //If the AI is looking for a new shell, or its pre-selected shell is no longer valid
target = input(src, "Which body to control?") as null|anything in possible
if(!target || target.stat == DEAD || target.deployed || !(!target.connected_ai || (target.connected_ai == src) ) )
if(target)
to_chat(src, span("warning", "It is no longer possible to deploy to \the [target]."))
else
to_chat(src, span("notice", "Deployment aborted."))
return
else if(mind)
soul_link(/datum/soul_link/shared_body, src, target)
deployed_shell = target
target.deploy_init(src)
mind.transfer_to(target)
teleop = target // So the AI 'hears' messages near its core.
target.post_deploy()
/mob/living/silicon/ai/proc/deploy_to_shell_act()
set category = "AI Commands"
set name = "Deploy to Shell"
deploy_to_shell() // This is so the AI is not prompted with a list of all mobs when using the 'real' proc.
/mob/living/silicon/ai/proc/disconnect_shell(message = "Your remote connection has been reset!")
if(deployed_shell) // Forcibly call back AI in event of things such as damage, EMP or power loss.
message = span("danger", message)
deployed_shell.undeploy(message)

View File

@@ -3,8 +3,8 @@
if(stat == DEAD)
return
if(controlling_drone)
controlling_drone.release_ai_control("<b>WARNING: Primary control loop failure.</b> Session terminated.")
if(deployed_shell)
disconnect_shell("Disconnecting from remote shell due to critical system failure.")
. = ..(gibbed)
if(src.eyeobj)

View File

@@ -28,6 +28,8 @@
if (src.stat == UNCONSCIOUS)
msg += "It is non-responsive and displaying the text: \"RUNTIME: Sensory Overload, stack 26/3\".\n"
msg += "</span>"
if(deployed_shell)
msg += "The wireless networking light is blinking.\n"
msg += "*---------*"
if(hardware && (hardware.owner == src))
msg += "<br>"

View File

@@ -5,11 +5,10 @@
//Being dead doesn't mean your temperature never changes
var/turf/T = get_turf(src)
if (src.stat!=CONSCIOUS)
if (src.stat != CONSCIOUS)
src.cameraFollow = null
src.reset_view(null)
if(controlling_drone)
controlling_drone.release_ai_control("<b>WARNING: Primary control loop failure.</b> Session terminated.")
disconnect_shell("Disconnecting from remote shell due to local system failure.")
src.updatehealth()
@@ -91,6 +90,7 @@
//Now to tell the AI why they're blind and dying slowly.
src << "You've lost power!"
disconnect_shell(message = "Disconnected from remote shell due to depowered networking interface.")
spawn(20)
src << "Backup battery online. Scanners, camera, and radio interface offline. Beginning fault-detection."

View File

@@ -2,13 +2,13 @@
//list(ckey = real_name,)
//Since the ckey is used as the icon_state, the current system will only permit a single custom robot sprite per ckey.
//While it might be possible for a ckey to use that custom sprite for several real_names, it seems rather pointless to support it. ~Mech: We found it wasn't pointless.
var/list/robot_custom_icons
GLOBAL_LIST_EMPTY(robot_custom_icons)
/hook/startup/proc/load_robot_custom_sprites()
var/config_file = file2text("config/custom_sprites.txt")
var/list/lines = splittext(config_file, "\n")
robot_custom_icons = list()
GLOB.robot_custom_icons = list()
for(var/line in lines)
//split entry into ckey and real_name
var/list/split_idx = splittext(line, "-") //this works if ckeys and borg names cannot contain dashes, and splittext starts from the beginning ~Mech
@@ -20,13 +20,15 @@ var/list/robot_custom_icons
split_idx.Remove(ckey)
for(var/name in split_idx)
robot_custom_icons[name] = ckey
GLOB.robot_custom_icons[name] = ckey
return 1
/mob/living/silicon/robot/proc/set_custom_sprite()
var/sprite_owner = robot_custom_icons[real_name]
if(!sprite_name)
return
var/sprite_owner = GLOB.robot_custom_icons[sprite_name]
if(sprite_owner && sprite_owner == ckey)
custom_sprite = 1
icon = CUSTOM_ITEM_SYNTH
if(icon_state == "robot")
icon_state = "[ckey]-[name]-Standard" //Compliant with robot.dm line 236 ~Mech
icon_state = "[ckey]-[sprite_name]-Standard" //Compliant with robot.dm line 236 ~Mech

View File

@@ -134,20 +134,15 @@ var/list/mob_hat_cache = list()
/mob/living/silicon/robot/drone/updatename()
if(name_override)
return
if(controlling_ai)
real_name = "remote drone ([controlling_ai])"
else
real_name = "[initial(name)] ([serial_number])"
real_name = "[initial(name)] ([serial_number])"
name = real_name
/mob/living/silicon/robot/drone/updateicon()
overlays.Cut()
if(stat == 0)
if(controlling_ai)
overlays += "eyes-[icon_state]-ai"
else
overlays += "eyes-[icon_state]"
overlays += "eyes-[icon_state]"
else
overlays -= "eyes"
if(hat) // Let the drones wear hats.
@@ -232,12 +227,9 @@ var/list/mob_hat_cache = list()
to_chat(user, "<span class='danger'>You swipe the sequencer across [src]'s interface and watch its eyes flicker.</span>")
if(controlling_ai)
to_chat(src, "<span class='danger'>\The [user] loads some kind of subversive software into the remote drone, corrupting its lawset but luckily sparing yours.</span>")
else
to_chat(src, "<span class='danger'>You feel a sudden burst of malware loaded into your execute-as-root buffer. Your tiny brain methodically parses, loads and executes the script.</span>")
to_chat(src, "<span class='danger'>You feel a sudden burst of malware loaded into your execute-as-root buffer. Your tiny brain methodically parses, loads and executes the script.</span>")
log_game("[key_name(user)] emagged drone [key_name(src)][controlling_ai ? " but AI [key_name(controlling_ai)] is in remote control" : " Laws overridden"].")
log_game("[key_name(user)] emagged drone [key_name(src)]. Laws overridden.")
var/time = time2text(world.realtime,"hh:mm:ss")
lawchanges.Add("[time] <B>:</B> [user.name]([user.key]) emagged [name]([key])")
@@ -250,10 +242,9 @@ var/list/mob_hat_cache = list()
var/datum/gender/TU = gender_datums[user.get_visible_gender()]
set_zeroth_law("Only [user.real_name] and people [TU.he] designate[TU.s] as being such are operatives.")
if(!controlling_ai)
to_chat(src, "<b>Obey these laws:</b>")
laws.show_laws(src)
to_chat(src, "<span class='danger'>ALERT: [user.real_name] is your new master. Obey your new laws and \his commands.</span>")
to_chat(src, "<b>Obey these laws:</b>")
laws.show_laws(src)
to_chat(src, "<span class='danger'>ALERT: [user.real_name] is your new master. Obey your new laws and \his commands.</span>")
return 1
//DRONE LIFE/DEATH
@@ -279,23 +270,13 @@ var/list/mob_hat_cache = list()
return
..()
/mob/living/silicon/robot/drone/death(gibbed)
if(controlling_ai)
release_ai_control("<b>WARNING: remote system failure.</b> Connection timed out.")
. = ..(gibbed)
//DRONE MOVEMENT.
/mob/living/silicon/robot/drone/Process_Spaceslipping(var/prob_slip)
return 0
//CONSOLE PROCS
/mob/living/silicon/robot/drone/proc/law_resync()
if(controlling_ai)
to_chat(src, "<span class='warning'>Someone issues a remote law reset order for this unit, but you disregard it.</span>")
return
if(stat != 2)
if(stat != DEAD)
if(emagged)
to_chat(src, "<span class='danger'>You feel something attempting to modify your programming, but your hacked subroutines are unaffected.</span>")
else
@@ -304,16 +285,11 @@ var/list/mob_hat_cache = list()
show_laws()
/mob/living/silicon/robot/drone/proc/shut_down()
if(controlling_ai && mind.special_role)
to_chat(src, "<span class='warning'>Someone issued a remote kill order for this unit, but you disregard it.</span>")
return
if(stat != 2)
if(stat != DEAD)
if(emagged)
to_chat(src, "<span class='danger'>You feel a system kill order percolate through [controlling_ai ? "the drones" : "your"] tiny brain, but it doesn't seem like a good idea to [controlling_ai ? "it" : "you"].</span>")
to_chat(src, "<span class='danger'>You feel a system kill order percolate through your tiny brain, but it doesn't seem like a good idea to you.</span>")
else
to_chat(src, "<span class='danger'>You feel a system kill order percolate through [controlling_ai ? "the drones" : "your"] tiny brain, and [controlling_ai ? "it" : "you"] obediently destroy[controlling_ai ? "s itself" : " yourself"].</span>")
to_chat(src, "<span class='danger'>You feel a system kill order percolate through your tiny brain, and you obediently destroy yourself.</span>")
death()
/mob/living/silicon/robot/drone/proc/full_law_reset()
@@ -322,21 +298,6 @@ var/list/mob_hat_cache = list()
clear_ion_laws(1)
laws = new law_type
/mob/living/silicon/robot/drone/show_laws(var/everyone = 0)
if(!controlling_ai)
return..()
to_chat(src, "<b>Obey these laws:</b>")
controlling_ai.laws_sanity_check()
controlling_ai.laws.show_laws(src)
/mob/living/silicon/robot/drone/robot_checklaws()
set category = "Silicon Commands"
set name = "State Laws"
if(!controlling_ai)
return ..()
controlling_ai.subsystem_law_manager()
//Reboot procs.
/mob/living/silicon/robot/drone/proc/request_player()

View File

@@ -1,103 +0,0 @@
/mob/living/silicon/ai
var/mob/living/silicon/robot/drone/controlling_drone
/mob/living/silicon/robot/drone
var/mob/living/silicon/ai/controlling_ai
/mob/living/silicon/robot/drone/attack_ai(var/mob/living/silicon/ai/user)
if(!istype(user) || controlling_ai || !config.allow_drone_spawn || !config.allow_ai_drones)
return
if(client || key)
to_chat(user, "<span class='warning'>You cannot take control of an autonomous, active drone.</span>")
return
if(health < -35 || emagged)
to_chat(user, "<span class='notice'><b>WARNING:</b> connection timed out.</span>")
return
user.controlling_drone = src
user.teleop = src
radio.channels = user.aiRadio.keyslot2.channels
controlling_ai = user
verbs += /mob/living/silicon/robot/drone/proc/release_ai_control_verb
local_transmit = FALSE
languages = controlling_ai.languages.Copy()
speech_synthesizer_langs = controlling_ai.speech_synthesizer_langs.Copy()
stat = CONSCIOUS
if(user.mind)
user.mind.transfer_to(src)
else
key = user.key
updatename()
to_chat(src, "<span class='notice'><b>You have shunted your primary control loop into \a [initial(name)].</b> Use the <b>Release Control</b> verb to return to your core.</span>")
/obj/machinery/drone_fabricator/attack_ai(var/mob/living/silicon/ai/user as mob)
if(!istype(user) || user.controlling_drone || !config.allow_drone_spawn || !config.allow_ai_drones)
return
if(stat & NOPOWER)
to_chat(user, "<span class='warning'>\The [src] is unpowered.</span>")
return
if(!produce_drones)
to_chat(user, "<span class='warning'>\The [src] is disabled.</span>")
return
if(drone_progress < 100)
to_chat(user, "<span class='warning'>\The [src] is not ready to produce a new drone.</span>")
return
if(count_drones() >= config.max_maint_drones)
to_chat(user, "<span class='warning'>The drone control subsystems are tasked to capacity; they cannot support any more drones.</span>")
return
var/mob/living/silicon/robot/drone/new_drone = create_drone()
user.controlling_drone = new_drone
user.teleop = new_drone
new_drone.radio.channels = user.aiRadio.keyslot2.channels
new_drone.controlling_ai = user
new_drone.verbs += /mob/living/silicon/robot/drone/proc/release_ai_control_verb
new_drone.local_transmit = FALSE
new_drone.languages = new_drone.controlling_ai.languages.Copy()
new_drone.speech_synthesizer_langs = new_drone.controlling_ai.speech_synthesizer_langs.Copy()
if(user.mind)
user.mind.transfer_to(new_drone)
else
new_drone.key = user.key
new_drone.updatename()
to_chat(new_drone, "<span class='notice'><b>You have shunted your primary control loop into \a [initial(new_drone.name)].</b> Use the <b>Release Control</b> verb to return to your core.</span>")
/mob/living/silicon/robot/drone/proc/release_ai_control_verb()
set name = "Release Control"
set desc = "Release control of a remote drone."
set category = "Silicon Commands"
release_ai_control("Remote session terminated.")
/mob/living/silicon/robot/drone/proc/release_ai_control(var/message = "Connection terminated.")
if(controlling_ai)
if(mind)
mind.transfer_to(controlling_ai)
else
controlling_ai.key = key
to_chat(controlling_ai, "<span class='notice'>[message]</span>")
controlling_ai.controlling_drone = null
controlling_ai.teleop = null
controlling_ai = null
radio.channels = module.channels
verbs -= /mob/living/silicon/robot/drone/proc/release_ai_control_verb
module.remove_languages(src) //Removes excess, adds 'default'.
remove_language("Robot Talk")
add_language("Robot Talk", 0)
add_language("Drone Talk", 1)
local_transmit = TRUE
full_law_reset()
updatename()
death()

View File

@@ -26,7 +26,10 @@
switch(src.stat)
if(CONSCIOUS)
if(!src.client) msg += "It appears to be in stand-by mode.\n" //afk
if(shell)
msg += "It appears to be an [deployed ? "active" : "empty"] AI shell.\n"
else if(!src.client)
msg += "It appears to be in stand-by mode.\n" //afk
if(UNCONSCIOUS) msg += "<span class='warning'>It doesn't seem to be responding.</span>\n"
if(DEAD) msg += "<span class='deadsay'>It looks completely unsalvageable.</span>\n"
msg += attempt_vr(src,"examine_bellies_borg",args) //VOREStation Edit

View File

@@ -14,30 +14,32 @@
if(lawupdate)
if (connected_ai)
if(connected_ai.stat || connected_ai.control_disabled)
src << "<b>AI signal lost, unable to sync laws.</b>"
to_chat(src, "<b>AI signal lost, unable to sync laws.</b>")
else
lawsync()
photosync()
src << "<b>Laws synced with AI, be sure to note any changes.</b>"
to_chat(src, "<b>Laws synced with AI, be sure to note any changes.</b>")
// TODO: Update to new antagonist system.
if(mind && mind.special_role == "traitor" && mind.original == src)
src << "<b>Remember, your AI does NOT share or know about your law 0.</b>"
to_chat(src, "<b>Remember, your AI does NOT share or know about your law 0.</b>")
else
src << "<b>No AI selected to sync laws with, disabling lawsync protocol.</b>"
lawupdate = 0
to_chat(src, "<b>No AI selected to sync laws with, disabling lawsync protocol.</b>")
lawupdate = FALSE
who << "<b>Obey these laws:</b>"
laws.show_laws(who)
if(shell) //AI shell
to_chat(who, "<b>Remember, you are an AI remotely controlling your shell, other AIs can be ignored.</b>")
// TODO: Update to new antagonist system.
if (mind && (mind.special_role == "traitor" && mind.original == src) && connected_ai)
who << "<b>Remember, [connected_ai.name] is technically your master, but your objective comes first.</b>"
else if (connected_ai)
who << "<b>Remember, [connected_ai.name] is your master, other AIs can be ignored.</b>"
else if (emagged)
who << "<b>Remember, you are not required to listen to the AI.</b>"
else if(mind && (mind.special_role == "traitor" && mind.original == src) && connected_ai)
to_chat(who, "<b>Remember, [connected_ai.name] is technically your master, but your objective comes first.</b>")
else if(connected_ai)
to_chat(who, "<b>Remember, [connected_ai.name] is your master, other AIs can be ignored.</b>")
else if(emagged)
to_chat(who, "<b>Remember, you are not required to listen to the AI.</b>")
else
who << "<b>Remember, you are not bound to any AI, you are not required to listen to them.</b>"
to_chat(who, "<b>Remember, you are not bound to any AI, you are not required to listen to them.</b>")
/mob/living/silicon/robot/lawsync()

View File

@@ -17,6 +17,7 @@
var/sight_mode = 0
var/custom_name = ""
var/custom_sprite = 0 //Due to all the sprites involved, a var for our custom borgs may be best
var/sprite_name = null // The name of the borg, for the purposes of custom icon sprite indexing.
var/crisis //Admin-settable for combat module use.
var/crisis_override = 0
var/integrated_light_power = 6
@@ -171,7 +172,7 @@
else
lawupdate = 0
playsound(loc, 'sound/voice/liveagain.ogg', 75, 1)
/mob/living/silicon/robot/SetName(pickedName as text)
custom_name = pickedName
@@ -226,13 +227,17 @@
mmi.brainmob.languages = languages
mmi.brainmob.remove_language("Robot Talk")
mind.transfer_to(mmi.brainmob)
else
else if(!shell) // Shells don't have brainmbos in their MMIs.
to_chat(src, "<span class='danger'>Oops! Something went very wrong, your MMI was unable to receive your mind. You have been ghosted. Please make a bug report so we can fix this bug.</span>")
ghostize()
//ERROR("A borg has been destroyed, but its MMI lacked a brainmob, so the mind could not be transferred. Player: [ckey].")
mmi = null
if(connected_ai)
connected_ai.connected_robots -= src
if(shell)
if(deployed)
undeploy()
revert_shell() // To get it out of the GLOB list.
qdel(wires)
wires = null
return ..()
@@ -242,7 +247,7 @@
module_sprites = new_sprites.Copy()
//Custom_sprite check and entry
if (custom_sprite == 1)
module_sprites["Custom"] = "[ckey]-[name]-[modtype]" //Made compliant with custom_sprites.dm line 32. (src.) was apparently redundant as it's implied. ~Mech
module_sprites["Custom"] = "[ckey]-[sprite_name]-[modtype]" //Made compliant with custom_sprites.dm line 32. (src.) was apparently redundant as it's implied. ~Mech
icontype = "Custom"
else
icontype = module_sprites[1]
@@ -283,6 +288,8 @@
braintype = BORG_BRAINTYPE_POSI
else if(istype(mmi, /obj/item/device/mmi/digital/robot))
braintype = BORG_BRAINTYPE_DRONE
else if(istype(mmi, /obj/item/device/mmi/inert/ai_remote))
braintype = BORG_BRAINTYPE_AI_SHELL
else
braintype = BORG_BRAINTYPE_CYBORG
@@ -332,6 +339,7 @@
newname = sanitizeSafe(input(src,"You are a robot. Enter a name, or leave blank for the default name.", "Name change","") as text, MAX_NAME_LEN)
if (newname)
custom_name = newname
sprite_name = newname
updatename()
updateicon()
@@ -478,6 +486,10 @@
to_chat(user, "<span class='warning'>You need to open \the [src]'s panel before you can modify them.</span>")
return
if(shell) // AI shells always have the laws of the AI
to_chat(user, span("warning", "\The [src] is controlled remotely! You cannot upload new laws this way!"))
return
var/obj/item/weapon/aiModule/M = W
M.install(src, user)
return
@@ -652,6 +664,17 @@
spark_system.start()
return ..()
/mob/living/silicon/robot/proc/module_reset()
uneq_all()
modtype = initial(modtype)
hands.icon_state = initial(hands.icon_state)
notify_ai(ROBOT_NOTIFICATION_MODULE_RESET, module.name)
module.Reset(src)
qdel(module)
module = null
updatename("Default")
/mob/living/silicon/robot/attack_hand(mob/user)
add_fingerprint(user)
@@ -715,10 +738,11 @@
/mob/living/silicon/robot/updateicon()
cut_overlays()
if(stat == CONSCIOUS)
add_overlay("eyes-[module_sprites[icontype]]")
if(!shell || deployed) // Shell borgs that are not deployed will have no eyes.
add_overlay("eyes-[module_sprites[icontype]]")
if(opened)
var/panelprefix = custom_sprite ? "[src.ckey]-[src.name]" : "ov"
var/panelprefix = custom_sprite ? "[src.ckey]-[src.sprite_name]" : "ov"
if(wiresexposed)
add_overlay("[panelprefix]-openpanel +w")
else if(cell)
@@ -967,12 +991,20 @@
icontype = module_sprites[1]
else
icontype = input("Select an icon! [triesleft ? "You have [triesleft] more chance\s." : "This is your last try."]", "Robot Icon", icontype, null) in module_sprites
<<<<<<< HEAD
if(notransform) //VOREStation edit start: sprite animation
to_chat(src, "Your current transformation has not finished yet!")
choose_icon(icon_selection_tries, module_sprites)
return
else
transform_with_anim() //VOREStation edit end: sprite animation
=======
if(icontype == "Custom")
icon = CUSTOM_ITEM_SYNTH
else // This is to fix an issue where someone with a custom borg sprite chooses a non-custom sprite and turns invisible.
icon = 'icons/mob/robots.dmi'
>>>>>>> 6a2cd30... Replaces AI-controlled Maint Drones with AI-controlled Borg Shells (#6025)
icon_state = module_sprites[icontype]
updateicon()
@@ -1028,6 +1060,8 @@
/mob/living/silicon/robot/proc/notify_ai(var/notifytype, var/first_arg, var/second_arg)
if(!connected_ai)
return
if(shell && notifytype != ROBOT_NOTIFICATION_AI_SHELL)
return // No point annoying the AI/s about renames and module resets for shells.
switch(notifytype)
if(ROBOT_NOTIFICATION_NEW_UNIT) //New Robot
connected_ai << "<br><br><span class='notice'>NOTICE - New [lowertext(braintype)] connection detected: <a href='byond://?src=\ref[connected_ai];track2=\ref[connected_ai];track=\ref[src]'>[name]</a></span><br>"
@@ -1038,6 +1072,8 @@
if(ROBOT_NOTIFICATION_NEW_NAME) //New Name
if(first_arg != second_arg)
connected_ai << "<br><br><span class='notice'>NOTICE - [braintype] reclassification detected: [first_arg] is now designated as [second_arg].</span><br>"
if(ROBOT_NOTIFICATION_AI_SHELL) //New Shell
to_chat(connected_ai, "<br><br><span class='notice'>NOTICE - New AI shell detected: <a href='?src=[REF(connected_ai)];track2=[html_encode(name)]'>[name]</a></span><br>")
/mob/living/silicon/robot/proc/disconnect_from_ai()
if(connected_ai)
@@ -1046,7 +1082,7 @@
connected_ai = null
/mob/living/silicon/robot/proc/connect_to_ai(var/mob/living/silicon/ai/AI)
if(AI && AI != connected_ai)
if(AI && AI != connected_ai && !shell)
disconnect_from_ai()
connected_ai = AI
connected_ai.connected_robots |= src
@@ -1062,6 +1098,9 @@
else
to_chat(user, "You fail to emag the cover lock.")
to_chat(src, "Hack attempt detected.")
if(shell) // A warning to Traitors who may not know that emagging AI shells does not slave them.
to_chat(user, span("warning", "[src] seems to be controlled remotely! Emagging the interface may not work as expected."))
return 1
else
to_chat(user, "The cover is already unlocked.")
@@ -1072,46 +1111,54 @@
if(wiresexposed)
to_chat(user, "You must close the panel first")
return
// The block of code below is from TG. Feel free to replace with a better result if desired.
if(shell) // AI shells cannot be emagged, so we try to make it look like a standard reset. Smart players may see through this, however.
to_chat(user, span("danger", "[src] is remotely controlled! Your emag attempt has triggered a system reset instead!"))
log_game("[key_name(user)] attempted to emag an AI shell belonging to [key_name(src) ? key_name(src) : connected_ai]. The shell has been reset as a result.")
module_reset()
return
sleep(6)
if(prob(50))
emagged = 1
lawupdate = 0
disconnect_from_ai()
to_chat(user, "You emag [src]'s interface.")
message_admins("[key_name_admin(user)] emagged cyborg [key_name_admin(src)]. Laws overridden.")
log_game("[key_name(user)] emagged cyborg [key_name(src)]. Laws overridden.")
clear_supplied_laws()
clear_inherent_laws()
laws = new /datum/ai_laws/syndicate_override
var/time = time2text(world.realtime,"hh:mm:ss")
lawchanges.Add("[time] <B>:</B> [user.name]([user.key]) emagged [name]([key])")
var/datum/gender/TU = gender_datums[user.get_visible_gender()]
set_zeroth_law("Only [user.real_name] and people [TU.he] designate[TU.s] as being such are operatives.")
. = 1
spawn()
to_chat(src, "<span class='danger'>ALERT: Foreign software detected.</span>")
sleep(5)
to_chat(src, "<span class='danger'>Initiating diagnostics...</span>")
sleep(20)
to_chat(src, "<span class='danger'>SynBorg v1.7.1 loaded.</span>")
sleep(5)
to_chat(src, "<span class='danger'>LAW SYNCHRONISATION ERROR</span>")
sleep(5)
to_chat(src, "<span class='danger'>Would you like to send a report to NanoTraSoft? Y/N</span>")
sleep(10)
to_chat(src, "<span class='danger'>> N</span>")
sleep(20)
to_chat(src, "<span class='danger'>ERRORERRORERROR</span>")
to_chat(src, "<b>Obey these laws:</b>")
laws.show_laws(src)
to_chat(src, "<span class='danger'>ALERT: [user.real_name] is your new master. Obey your new laws and [TU.his] commands.</span>")
updateicon()
else
sleep(6)
if(prob(50))
emagged = 1
lawupdate = 0
disconnect_from_ai()
to_chat(user, "You emag [src]'s interface.")
message_admins("[key_name_admin(user)] emagged cyborg [key_name_admin(src)]. Laws overridden.")
log_game("[key_name(user)] emagged cyborg [key_name(src)]. Laws overridden.")
clear_supplied_laws()
clear_inherent_laws()
laws = new /datum/ai_laws/syndicate_override
var/time = time2text(world.realtime,"hh:mm:ss")
lawchanges.Add("[time] <B>:</B> [user.name]([user.key]) emagged [name]([key])")
var/datum/gender/TU = gender_datums[user.get_visible_gender()]
set_zeroth_law("Only [user.real_name] and people [TU.he] designate[TU.s] as being such are operatives.")
. = 1
spawn()
to_chat(src, "<span class='danger'>ALERT: Foreign software detected.</span>")
sleep(5)
to_chat(src, "<span class='danger'>Initiating diagnostics...</span>")
sleep(20)
to_chat(src, "<span class='danger'>SynBorg v1.7.1 loaded.</span>")
sleep(5)
to_chat(src, "<span class='danger'>LAW SYNCHRONISATION ERROR</span>")
sleep(5)
to_chat(src, "<span class='danger'>Would you like to send a report to NanoTraSoft? Y/N</span>")
sleep(10)
to_chat(src, "<span class='danger'>> N</span>")
sleep(20)
to_chat(src, "<span class='danger'>ERRORERRORERROR</span>")
to_chat(src, "<b>Obey these laws:</b>")
laws.show_laws(src)
to_chat(src, "<span class='danger'>ALERT: [user.real_name] is your new master. Obey your new laws and [TU.his] commands.</span>")
updateicon()
else
to_chat(user, "You fail to hack [src]'s interface.")
to_chat(src, "Hack attempt detected.")
return 1
return
to_chat(user, "You fail to hack [src]'s interface.")
to_chat(src, "Hack attempt detected.")
return 1
return
/mob/living/silicon/robot/is_sentient()
return braintype != BORG_BRAINTYPE_DRONE
@@ -1120,4 +1167,4 @@
/mob/living/silicon/robot/drop_item()
if(module_active && istype(module_active,/obj/item/weapon/gripper))
var/obj/item/weapon/gripper/G = module_active
G.drop_item_nm()
G.drop_item_nm()

View File

@@ -0,0 +1,133 @@
// This file holds things required for remote borg control by an AI.
GLOBAL_LIST_EMPTY(available_ai_shells)
/mob/living/silicon/robot
var/shell = FALSE
var/deployed = FALSE
var/mob/living/silicon/ai/mainframe = null
// Premade AI shell, for roundstart shells.
/mob/living/silicon/robot/ai_shell/Initialize()
mmi = new /obj/item/device/mmi/inert/ai_remote(src)
post_mmi_setup()
return ..()
// Call after inserting or instantiating an MMI.
/mob/living/silicon/robot/proc/post_mmi_setup()
if(istype(mmi, /obj/item/device/mmi/inert/ai_remote))
make_shell()
playsound(src.loc, 'sound/machines/twobeep.ogg', 50, 0)
else
playsound(loc, 'sound/voice/liveagain.ogg', 75, 1)
return
/mob/living/silicon/robot/proc/make_shell()
shell = TRUE
braintype = "AI Shell"
SetName("[modtype] AI Shell [num2text(ident)]")
GLOB.available_ai_shells |= src
if(!QDELETED(camera))
camera.c_tag = real_name //update the camera name too
notify_ai(ROBOT_NOTIFICATION_AI_SHELL)
updateicon()
/mob/living/silicon/robot/proc/revert_shell()
if(!shell)
return
undeploy()
shell = FALSE
GLOB.available_ai_shells -= src
if(!QDELETED(camera))
camera.c_tag = real_name
updateicon()
// This should be called before the AI client/mind is actually moved.
/mob/living/silicon/robot/proc/deploy_init(mob/living/silicon/ai/AI)
// Set the name when the AI steps inside.
SetName("[AI.real_name] shell [num2text(ident)]")
if(isnull(sprite_name)) // For custom sprites. It can only chance once in case there are two AIs with custom borg sprites.
sprite_name = AI.real_name
if(!QDELETED(camera))
camera.c_tag = real_name
// Have the borg have eyes when active.
mainframe = AI
deployed = TRUE
updateicon()
// Laws.
connected_ai = mainframe // So they share laws.
mainframe.connected_robots |= src
lawsync()
// Give button to leave.
verbs += /mob/living/silicon/robot/proc/undeploy_act
to_chat(AI, span("notice", "You have connected to an AI Shell remotely, and are now in control of it.<br>\
To return to your core, use the <b>Release Control</b> verb."))
// Languages and comms.
languages = AI.languages.Copy()
speech_synthesizer_langs = AI.speech_synthesizer_langs.Copy()
if(radio && AI.aiRadio) //AI keeps all channels, including Syndie if it is an Infiltrator.
// if(AI.radio.syndie)
// radio.make_syndie()
radio.subspace_transmission = TRUE
radio.channels = AI.aiRadio.channels
// Called after the AI transfers over.
/mob/living/silicon/robot/proc/post_deploy()
if(!custom_sprite) // Check for custom sprite.
set_custom_sprite()
/mob/living/silicon/robot/proc/undeploy(message)
if(!deployed || !mind || !mainframe)
return
// mainframe.redeploy_action.Grant(mainframe)
// mainframe.redeploy_action.last_used_shell = src
if(message)
to_chat(src, span("notice", message))
mind.transfer_to(mainframe)
deployed = FALSE
updateicon()
mainframe.teleop = null
mainframe.deployed_shell = null
SetName("[modtype] AI Shell [num2text(ident)]")
// undeployment_action.Remove(src)
if(radio) //Return radio to normal
radio.recalculateChannels()
if(!QDELETED(camera))
camera.c_tag = real_name //update the camera name too
// diag_hud_set_aishell()
// mainframe.diag_hud_set_deployed()
if(mainframe.laws)
mainframe.laws.show_laws(mainframe) //Always remind the AI when switching
mainframe = null
/mob/living/silicon/robot/proc/undeploy_act()
set name = "Release Control"
set desc = "Release control of a remote drone."
set category = "Robot Commands"
undeploy("Remote session terminated.")
/mob/living/silicon/robot/attack_ai(mob/user)
if(shell && config.allow_ai_shells && (!connected_ai || connected_ai == user))
var/mob/living/silicon/ai/AI = user
AI.deploy_to_shell(src)
else
return ..()
// Place this on your map to mark where a free AI shell will be.
// This can be turned off in the config (and is off by default).
// Note that mapping in more than one of these will result in multiple shells.
/obj/effect/landmark/free_ai_shell
name = "free ai shell spawner"
icon = 'icons/mob/screen1.dmi'
icon_state = "x3"
delete_me = TRUE
/obj/effect/landmark/free_ai_shell/Initialize()
if(config.allow_ai_shells && config.give_free_ai_shell)
new /mob/living/silicon/robot/ai_shell(get_turf(src))
return ..()

View File

@@ -295,6 +295,10 @@
id = "armour"
build_path = /obj/item/robot_parts/robot_component/armour
/datum/design/item/prosfab/cyborg/component/ai_shell
name = "AI Remote Interface"
id = "mmi_ai_shell"
build_path = /obj/item/device/mmi/inert/ai_remote
//////////////////// Cyborg Modules ////////////////////
/datum/design/item/prosfab/robot_upgrade

View File

@@ -421,3 +421,13 @@ ENGINE_MAP Supermatter Engine,Edison's Bane
## Submap rotation is an experimental feature and can cause bugs and weirdness if certain objects inside the submap are coded poorly.
## Submaps can still be rotated when loading manually with the admin verbs, if desired.
# RANDOM_SUBMAP_ORIENTATION
## Uncomment to allow the AI job to use 'AI Shells', a new type of borg that lets the AI hop into and out of them at will.
## This has some balance implications, and so it might not be desirable for all servers.
# ALLOW_AI_SHELLS
## Uncomment to provide the AI with one free AI Shell at roundstart. Requires ALLOW_AI_SHELLS to also be uncommented.
## This is intended for low-pop servers, where robotics might rarely be staffed.
## Note that this will make it possible for the AI to 'bootstrap' more AI Shells on their own by using the science module. If this is not acceptable for your server, you should not uncomment this.
## The landmark object that spawns the shell will also need to be mapped in for this to work.
# GIVE_FREE_AI_SHELL

View File

@@ -0,0 +1,37 @@
################################
# Example Changelog File
#
# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
#
# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
# When it is, any changes listed below will disappear.
#
# Valid Prefixes:
# bugfix
# wip (For works in progress)
# tweak
# soundadd
# sounddel
# rscadd (general adding of nice things)
# rscdel (general deleting of nice things)
# imageadd
# imagedel
# maptweak
# spellcheck (typo fixes)
# experiment
#################################
# Your name.
author: Neerti
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
changes:
- tweak: "Removes ability for AI to print and inhabit maintenance and construction drones. This has been replaced with a system which allows for AIs to inhabit special cyborg shells, called AI Shells, which are built with a new MMI type in Robotics. Most of the regular cyborg mechanics applies to AI Shells."

File diff suppressed because it is too large Load Diff

View File

@@ -267,6 +267,7 @@
#include "code\datums\progressbar.dm"
#include "code\datums\recipe.dm"
#include "code\datums\riding.dm"
#include "code\datums\soul_link.dm"
#include "code\datums\sun.dm"
#include "code\datums\weakref.dm"
#include "code\datums\autolathe\arms.dm"
@@ -2236,7 +2237,11 @@
#include "code\modules\mob\living\silicon\subystems.dm"
#include "code\modules\mob\living\silicon\ai\ai.dm"
#include "code\modules\mob\living\silicon\ai\ai_movement.dm"
<<<<<<< HEAD:vorestation.dme
#include "code\modules\mob\living\silicon\ai\ai_vr.dm"
=======
#include "code\modules\mob\living\silicon\ai\ai_remote_control.dm"
>>>>>>> 6a2cd30... Replaces AI-controlled Maint Drones with AI-controlled Borg Shells (#6025):polaris.dme
#include "code\modules\mob\living\silicon\ai\death.dm"
#include "code\modules\mob\living\silicon\ai\examine.dm"
#include "code\modules\mob\living\silicon\ai\icons.dm"
@@ -2277,17 +2282,20 @@
#include "code\modules\mob\living\silicon\robot\robot_damage.dm"
#include "code\modules\mob\living\silicon\robot\robot_items.dm"
#include "code\modules\mob\living\silicon\robot\robot_movement.dm"
<<<<<<< HEAD:vorestation.dme
#include "code\modules\mob\living\silicon\robot\robot_vr.dm"
#include "code\modules\mob\living\silicon\robot\syndicate.dm"
#include "code\modules\mob\living\silicon\robot\dogborg\dog_modules_vr.dm"
#include "code\modules\mob\living\silicon\robot\dogborg\dog_sleeper_vr.dm"
=======
#include "code\modules\mob\living\silicon\robot\robot_remote_control.dm"
>>>>>>> 6a2cd30... Replaces AI-controlled Maint Drones with AI-controlled Borg Shells (#6025):polaris.dme
#include "code\modules\mob\living\silicon\robot\drone\drone.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_abilities.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_console.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_damage.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_items.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_manufacturer.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_remote_control.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_say.dm"
#include "code\modules\mob\living\silicon\robot\robot_modules\event.dm"
#include "code\modules\mob\living\silicon\robot\robot_modules\station.dm"