mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-29 23:17:02 +01:00
[READY FOR REVIEW] ports AI shells from Hispania and /tg/ (#32072)
* initial commit * dme include * add shell actions * debug round 1 * debug round 2 * cleanup * better comment * cleanup? * fixes * fixes 2 * cleanup 2 * cleanup 3 * adds comments from wich repo they were ported * updatehealth change * notify to define * tguification * spannening * BORIS fixes * BORIS fix final * better comments * icons * radio fix, tg lable, status order * PDA, syndie key fix * soullink port completion * possiblle uid fix * channel redundancy removed * upadtehealth fix * notification de-duplication * cleanup * null checks * more null checks, updatehealth fix, better destroy() * more spanenming * cell fix * null checks, better updatehealth, radio fix? * mech fixes * removed redundancy, fix else if chain * Apply suggestions from code review part 1 Co-authored-by: CRUNCH <143041327+CRUNCH-Borg@users.noreply.github.com> Signed-off-by: Kameň <167246105+StoneyFox29@users.noreply.github.com> * Code Review part 2 Co-authored-by: CRUNCH <143041327+CRUNCH-Borg@users.noreply.github.com> * I forgor Co-authored-by: CRUNCH <143041327+CRUNCH-Borg@users.noreply.github.com> * Post review runtime fixes, removed more annotations, bettered some comments * verb un-funkening, possible CI fix * null check order, comment * SPAN, minor fix * CI part 2 * CI part 3 * CI part 4, made on iphone * Apply suggestions from myself Co-authored-by: Kameň <167246105+StoneyFox29@users.noreply.github.com> Signed-off-by: Kameň <167246105+StoneyFox29@users.noreply.github.com> * soulinks, removing a verb, module reset changes, * better documentation, name changes * updatehealth improvement * spawn_shell fix * fixes shell_spawn fix * attack chain something, merge conflict * grammar, BORIS module improvement * shttle fix, early return * formatting * BORIS comment * Apply suggestions from code review Co-authored-by: CRUNCH <143041327+CRUNCH-Borg@users.noreply.github.com> Signed-off-by: Kameň <167246105+StoneyFox29@users.noreply.github.com> * name change * bug report fix * slightly better bug fix * prevent the dropping of the BORIS module on reset upgrade * typo fix * wire order fix * makes emagging a shell drop the BORIS again * moving boris to misc category * makes the BORIS show up on mechfab --------- Signed-off-by: Kameň <167246105+StoneyFox29@users.noreply.github.com> Co-authored-by: TobiasHC2009 <167246105+TobiasHC2009@users.noreply.github.com> Co-authored-by: CRUNCH <143041327+CRUNCH-Borg@users.noreply.github.com>
This commit is contained in:
co-authored by
CRUNCH
TobiasHC2009
parent
83ada1355f
commit
edee60476d
@@ -37,6 +37,13 @@
|
||||
#define HONK_BOT "Honkbot" // Honkbots
|
||||
#define GRIEF_BOT "Grief" // Griefsky
|
||||
|
||||
//AI notification defines
|
||||
#define NEW_BORG 1
|
||||
#define NEW_MODULE 2
|
||||
#define RENAME 3
|
||||
#define AI_SHELL 4
|
||||
#define DISCONNECT 5
|
||||
|
||||
//Sentience types
|
||||
#define SENTIENCE_ORGANIC 1
|
||||
#define SENTIENCE_ARTIFICIAL 2
|
||||
|
||||
@@ -280,7 +280,7 @@
|
||||
var/select
|
||||
var/list/borgs = list()
|
||||
for(var/mob/living/silicon/robot/A in GLOB.player_list)
|
||||
if(A.stat == DEAD || A.connected_ai || A.scrambledcodes || isdrone(A))
|
||||
if(A.stat == DEAD || A.connected_ai || A.scrambledcodes || isdrone(A) || A.shell)
|
||||
continue
|
||||
var/name = "[A.real_name] ([A.modtype] [A.braintype])"
|
||||
borgs[name] = A
|
||||
|
||||
@@ -210,7 +210,9 @@ SUBSYSTEM_DEF(shuttle)
|
||||
for(var/thing in GLOB.shuttle_caller_list)
|
||||
if(is_ai(thing))
|
||||
var/mob/living/silicon/ai/AI = thing
|
||||
if(AI.stat || !AI.client)
|
||||
if(AI.deployed_shell && !AI.deployed_shell.client)
|
||||
continue
|
||||
if(AI.stat || (!AI.client && !AI.deployed_shell))
|
||||
continue
|
||||
else if(istype(thing, /obj/machinery/computer/communications))
|
||||
var/obj/machinery/computer/communications/C = thing
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
|
||||
|
||||
/mob/living/silicon/proc/sync_zeroth(datum/ai_law/zeroth_law, datum/ai_law/zeroth_law_borg)
|
||||
if(!is_special_character(src) || !mind.is_original_mob(src))
|
||||
if(!is_special_character(src) || (mind && !mind.is_original_mob(src)))
|
||||
if(zeroth_law_borg)
|
||||
laws.set_zeroth_law(zeroth_law_borg.law)
|
||||
var/datum/atom_hud/data/human/malf_ai/H = GLOB.huds[DATA_HUD_MALF_AI]
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Keeps track of a Mob->Mob (potentially Player->Player) connection.
|
||||
* Can be used to trigger actions on one party when events happen to another
|
||||
* (e.g. shared deaths).
|
||||
* Can be used to form a linked list of mob-hopping.
|
||||
* Does NOT transfer with minds.
|
||||
*/
|
||||
/datum/soullink
|
||||
var/mob/living/soulowner
|
||||
var/mob/living/soulsharer
|
||||
/// Optional ID, for tagging and finding specific instances.
|
||||
var/id
|
||||
|
||||
/datum/soullink/Destroy()
|
||||
if(soulowner)
|
||||
LAZYREMOVE(soulowner.ownedSoullinks, src)
|
||||
soulowner = null
|
||||
if(soulsharer)
|
||||
LAZYREMOVE(soulsharer.sharedSoullinks, src)
|
||||
soulsharer = null
|
||||
return ..()
|
||||
|
||||
/datum/soullink/proc/removeSoulsharer(mob/living/sharer)
|
||||
if(soulsharer == sharer)
|
||||
soulsharer = null
|
||||
LAZYREMOVE(sharer.sharedSoullinks, src)
|
||||
|
||||
/**
|
||||
* Used to assign variables, called primarily by `soullink()`.
|
||||
* Override this to create more unique soul links (Eg: 1->Many relationships).
|
||||
* Return `TRUE`/`FALSE` to return the soul link/null in `soullink()`.
|
||||
*/
|
||||
/datum/soullink/proc/parseArgs(mob/living/owner, mob/living/sharer)
|
||||
if(!owner || !sharer)
|
||||
return FALSE
|
||||
soulowner = owner
|
||||
soulsharer = sharer
|
||||
LAZYADD(owner.ownedSoullinks, src)
|
||||
LAZYADD(sharer.sharedSoullinks, src)
|
||||
return TRUE
|
||||
|
||||
/// Runs after `mob/living` `death()`. Override this for content.
|
||||
/datum/soullink/proc/ownerDies(gibbed, mob/living/owner)
|
||||
return
|
||||
|
||||
/// Runs after `mob/living` `death()`. Override this for content.
|
||||
/datum/soullink/proc/sharerDies(gibbed, mob/living/owner)
|
||||
return
|
||||
|
||||
/// Runs after `mob/living` `update_revive()`. Override this for content.
|
||||
/datum/soullink/proc/ownerRevives(mob/living/owner)
|
||||
return
|
||||
|
||||
/// Runs after `mob/living` `update_revive()`. Override this for content.
|
||||
/datum/soullink/proc/sharerRevives(mob/living/owner)
|
||||
return
|
||||
|
||||
//Quick-use helper
|
||||
/proc/soullink(typepath, ...)
|
||||
var/datum/soullink/S = new typepath()
|
||||
if(S.parseArgs(arglist(args.Copy(2, 0))))
|
||||
return S
|
||||
|
||||
// MARK: Multi Sharer
|
||||
/// Abstract soul link for use with 1 Owner -> Many Sharer setups.
|
||||
/datum/soullink/multisharer
|
||||
var/list/soulsharers
|
||||
|
||||
/datum/soullink/multisharer/parseArgs(mob/living/owner, list/sharers)
|
||||
if(!owner || !LAZYLEN(sharers))
|
||||
return FALSE
|
||||
soulowner = owner
|
||||
soulsharers = sharers
|
||||
LAZYADD(owner.ownedSoullinks, src)
|
||||
for(var/l in sharers)
|
||||
var/mob/living/L = l
|
||||
LAZYADD(L.sharedSoullinks, src)
|
||||
return TRUE
|
||||
|
||||
/datum/soullink/multisharer/removeSoulsharer(mob/living/sharer)
|
||||
LAZYREMOVE(soulsharers, sharer)
|
||||
|
||||
// MARK: Shared Fate
|
||||
/// When the soul owner dies, the soul sharer dies, and vice versa. This is intended for two players (or AI) and two mobs.
|
||||
/datum/soullink/sharedfate/ownerDies(gibbed, mob/living/owner)
|
||||
if(soulsharer)
|
||||
soulsharer.death(gibbed)
|
||||
|
||||
/datum/soullink/sharedfate/sharerDies(gibbed, mob/living/sharer)
|
||||
if(soulowner)
|
||||
soulowner.death(gibbed)
|
||||
|
||||
// MARK: Demon Bind
|
||||
/// When the soulowner dies, the soulsharer dies, but NOT vice versa. This is intended for two players(or AI) and two mobs.
|
||||
/datum/soullink/oneway/ownerDies(gibbed, mob/living/owner)
|
||||
if(soulsharer)
|
||||
soulsharer.dust(FALSE)
|
||||
|
||||
/datum/soullink/oneway/devilfriend
|
||||
|
||||
// MARK: 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 is intended for one player moving between many mobs.
|
||||
/datum/soullink/sharedbody/ownerDies(gibbed, mob/living/owner)
|
||||
if(soulowner && soulsharer)
|
||||
if(soulsharer.mind)
|
||||
soulsharer.mind.transfer_to(soulowner)
|
||||
soulsharer.death(gibbed)
|
||||
|
||||
/datum/soullink/sharedbody/sharerDies(gibbed, mob/living/sharer)
|
||||
if(soulowner && soulsharer && soulsharer.mind)
|
||||
soulsharer.mind.transfer_to(soulowner)
|
||||
|
||||
// MARK: 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/soullink/multisharer/replacementpool/ownerDies(gibbed, mob/living/owner)
|
||||
if(LAZYLEN(soulsharers) && !gibbed) //let's not put them in some gibs
|
||||
var/list/souls = shuffle(soulsharers.Copy())
|
||||
for(var/l in souls)
|
||||
var/mob/living/L = l
|
||||
if(L.stat != DEAD && L.mind)
|
||||
L.mind.transfer_to(soulowner)
|
||||
soulowner.revive(TRUE, TRUE)
|
||||
L.death(FALSE)
|
||||
|
||||
//Lose your claim to the throne!
|
||||
/datum/soullink/multisharer/replacementpool/sharerDies(gibbed, mob/living/sharer)
|
||||
removeSoulsharer(sharer)
|
||||
@@ -25,6 +25,8 @@
|
||||
to_chat(R, "LawSync protocol engaged.")
|
||||
R.lawsync()
|
||||
R.show_laws()
|
||||
if(!R.deployed)
|
||||
R.lawupdate = FALSE
|
||||
else
|
||||
if(!R.lawupdate && !R.emagged)
|
||||
R.lawupdate = TRUE
|
||||
@@ -32,6 +34,9 @@
|
||||
if(WIRE_AI_CONTROL) //Cut the AI wire to reset AI control
|
||||
if(!mend)
|
||||
if(R.connected_ai)
|
||||
R.notify_ai(DISCONNECT)
|
||||
if(R.shell)
|
||||
R.undeploy() //Forced disconnect of an AI should this body be a shell.
|
||||
R.disconnect_from_ai()
|
||||
|
||||
if(WIRE_BORG_CAMERA)
|
||||
|
||||
@@ -608,3 +608,27 @@
|
||||
if(!R.fields["comments"])
|
||||
R.fields["comments"] = list()
|
||||
R.fields["comments"] += list(comment_text)
|
||||
|
||||
/// Borg-AI shell tracking.
|
||||
/// Shows tracking beacons on the mech.
|
||||
/mob/living/silicon/robot/proc/diag_hud_set_aishell()
|
||||
var/image/holder = hud_list[DIAG_TRACK_HUD]
|
||||
var/icon/I = icon(icon, icon_state, dir)
|
||||
holder.pixel_y = I.Height() - world.icon_size
|
||||
if(!shell) //Not an AI shell
|
||||
holder.icon_state = null
|
||||
else if(deployed) //AI shell in use by an AI
|
||||
holder.icon_state = "hudtrackingai"
|
||||
else //Empty AI shell
|
||||
holder.icon_state = "hudtracking"
|
||||
|
||||
/// AI side tracking of AI shell control.
|
||||
/// Shows tracking beacons on the mech.
|
||||
/mob/living/silicon/ai/proc/diag_hud_set_deployed()
|
||||
var/image/holder = hud_list[DIAG_TRACK_HUD]
|
||||
var/icon/I = icon(icon, icon_state, dir)
|
||||
holder.pixel_y = I.Height() - world.icon_size
|
||||
if(!deployed_shell)
|
||||
holder.icon_state = null
|
||||
else //AI is currently controlling a shell
|
||||
holder.icon_state = "hudtrackingai"
|
||||
|
||||
@@ -870,8 +870,9 @@
|
||||
log_game("[key_name(usr)] purchased combat upgrades for all cyborgs.")
|
||||
message_admins(SPAN_NOTICE("[key_name_admin(usr)] purchased combat upgrades for all cyborgs!"))
|
||||
for(var/mob/living/silicon/robot/R in AI.connected_robots)
|
||||
R.module.malfhacked = TRUE
|
||||
R.module.rebuild_modules()
|
||||
if(R.module)
|
||||
R.module.malfhacked = TRUE
|
||||
R.module.rebuild_modules()
|
||||
to_chat(R, SPAN_NOTICE("New firmware downloaded. Combat upgrades are now online."))
|
||||
|
||||
/datum/ai_module/repair_cyborg
|
||||
|
||||
@@ -254,9 +254,10 @@
|
||||
log_game("[key_name(usr)] emagged [key_name(R)] using robotic console!")
|
||||
message_admins(SPAN_NOTICE("[key_name_admin(usr)] emagged [key_name_admin(R)] using robotic console!"))
|
||||
R.emagged = TRUE
|
||||
R.module.emag_act(usr)
|
||||
R.module.module_type = "Malf"
|
||||
R.update_module_icon()
|
||||
R.module.rebuild_modules()
|
||||
if(R.module)
|
||||
R.module.emag_act(usr)
|
||||
R.module.module_type = "Malf"
|
||||
R.update_module_icon()
|
||||
R.module.rebuild_modules()
|
||||
to_chat(R, SPAN_NOTICE("Failsafe protocols overridden. New tools available."))
|
||||
. = TRUE
|
||||
|
||||
@@ -1364,6 +1364,8 @@
|
||||
to_chat(AI, SPAN_USERDANGER("Inactive core destroyed. Unable to return."))
|
||||
AI.linked_core = null
|
||||
return
|
||||
AI.deploy_action.Grant(AI)
|
||||
AI.redeploy_action.Grant(AI)
|
||||
to_chat(AI, SPAN_NOTICE("Returning to core..."))
|
||||
AI.controlled_mech = null
|
||||
if(istype(AI.eyeobj))
|
||||
|
||||
@@ -515,4 +515,12 @@
|
||||
qdel(keyslot1)
|
||||
keyslot1 = new /obj/item/encryptionkey/syndicate
|
||||
syndiekey = keyslot1
|
||||
syndie = TRUE
|
||||
recalculateChannels()
|
||||
|
||||
/obj/item/radio/borg/proc/make_syndie()
|
||||
qdel(keyslot)
|
||||
keyslot = new /obj/item/encryptionkey/syndicate
|
||||
syndiekey = keyslot
|
||||
syndie = TRUE
|
||||
recalculateChannels()
|
||||
|
||||
@@ -61,6 +61,8 @@ GLOBAL_LIST_EMPTY(deadsay_radio_systems)
|
||||
var/obj/item/encryptionkey/syndicate/syndiekey = null
|
||||
/// How many times this is disabled by EMPs
|
||||
var/disable_timer = 0
|
||||
/// Is the radio a syndie one?
|
||||
var/syndie = FALSE
|
||||
/// Areas in which this radio cannot send messages
|
||||
var/static/list/blacklisted_areas = list(/area/adminconstruction, /area/tdome, /area/ruin/space/bubblegum_arena)
|
||||
|
||||
|
||||
@@ -343,6 +343,40 @@
|
||||
to_chat(user, SPAN_WARNING("The MMI must go in after everything else!"))
|
||||
return ITEM_INTERACT_COMPLETE
|
||||
|
||||
if(istype(used, /obj/item/borg/upgrade/ai))
|
||||
var/obj/item/borg/upgrade/ai/M = used
|
||||
if(check_completion())
|
||||
if(!isturf(loc))
|
||||
to_chat(user, SPAN_WARNING("You cannot install [M], the frame has to be standing on the ground to be perfectly precise!"))
|
||||
return ITEM_INTERACT_COMPLETE
|
||||
if(!user.drop_item())
|
||||
to_chat(user, SPAN_WARNING("[M] is stuck to your hand!"))
|
||||
return ITEM_INTERACT_COMPLETE
|
||||
qdel(M)
|
||||
var/mob/living/silicon/robot/O = new /mob/living/silicon/robot/shell(get_turf(src))
|
||||
|
||||
if(!aisync)
|
||||
lawsync = FALSE
|
||||
O.connected_ai = null
|
||||
else
|
||||
if(forced_ai)
|
||||
O.connected_ai = forced_ai
|
||||
O.notify_ai(AI_SHELL)
|
||||
if(!lawsync)
|
||||
O.lawupdate = FALSE
|
||||
O.make_laws()
|
||||
|
||||
var/datum/robot_component/cell_component = O.components["power cell"]
|
||||
cell_component.install(chest.cell)
|
||||
chest.cell = null
|
||||
O.locked = panel_locked
|
||||
O.job = "Cyborg"
|
||||
forceMove(O)
|
||||
O.robot_suit = src
|
||||
if(!locomotion)
|
||||
O.lockcharge = TRUE
|
||||
O.update_stamina_hud()
|
||||
return ITEM_INTERACT_COMPLETE
|
||||
if(is_pen(used))
|
||||
to_chat(user, SPAN_WARNING("You need to use a multitool to name [src]!"))
|
||||
return ITEM_INTERACT_COMPLETE
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
if(!R.allow_rename)
|
||||
to_chat(R, SPAN_WARNING("Internal diagnostic error: incompatible upgrade module detected."))
|
||||
return
|
||||
R.notify_ai(3, R.name, heldname)
|
||||
R.notify_ai(RENAME, R.name, heldname)
|
||||
R.name = heldname
|
||||
R.custom_name = heldname
|
||||
R.real_name = heldname
|
||||
@@ -160,7 +160,7 @@
|
||||
R.set_stat(CONSCIOUS)
|
||||
GLOB.dead_mob_list -= R //please never forget this ever kthx
|
||||
GLOB.alive_mob_list += R
|
||||
R.notify_ai(1)
|
||||
R.notify_ai(NEW_BORG)
|
||||
|
||||
return TRUE
|
||||
|
||||
@@ -552,3 +552,17 @@
|
||||
/obj/item/reagent_containers/spray/cleaner/safety/abductor,
|
||||
/obj/item/lightreplacer/bluespace/abductor
|
||||
)
|
||||
|
||||
// MARK: B.O.R.I.S.
|
||||
/obj/item/borg/upgrade/ai
|
||||
name = "B.O.R.I.S. module"
|
||||
desc = "Bluespace Optimized Remote Intelligence Synchronization. An uplink device which takes the place of an MMI in cyborg endoskeletons, creating a robotic shell controlled by an AI."
|
||||
icon_state = "bluespacearray"
|
||||
origin_tech = "engineering=3;magnets=4;programming=5"
|
||||
var/alien = FALSE // This is an incredibely scuffed way to do this, but it works.
|
||||
var/syndiemmi = FALSE
|
||||
var/brainmob = null
|
||||
|
||||
/obj/item/borg/upgrade/ai/action(mob/living/silicon/robot/R)
|
||||
to_chat(usr, SPAN_NOTICE("The B.O.R.I.S. module can only be installed into an endoskeleton.")) // Since the BORIS module is an upgrade that acts as an MMI.
|
||||
return
|
||||
|
||||
@@ -76,6 +76,13 @@
|
||||
med_hud_set_health()
|
||||
med_hud_set_status()
|
||||
|
||||
for(var/s in ownedSoullinks)
|
||||
var/datum/soullink/S = s
|
||||
S.ownerDies(gibbed, src)
|
||||
for(var/s in sharedSoullinks)
|
||||
var/datum/soullink/S = s
|
||||
S.sharerDies(gibbed, src)
|
||||
|
||||
GLOB.alive_mob_list -= src
|
||||
GLOB.dead_mob_list += src
|
||||
if(mind)
|
||||
|
||||
@@ -59,6 +59,16 @@
|
||||
if(mind?.current == src)
|
||||
mind.unbind()
|
||||
UnregisterSignal(src, COMSIG_ATOM_PREHIT)
|
||||
for(var/s in ownedSoullinks)
|
||||
var/datum/soullink/S = s
|
||||
S.ownerDies(FALSE)
|
||||
qdel(s) // If the owner is `destroy()`'d, the soul link is `destroy()`'d.
|
||||
ownedSoullinks = null
|
||||
for(var/s in sharedSoullinks)
|
||||
var/datum/soullink/S = s
|
||||
S.sharerDies(FALSE)
|
||||
S.removeSoulsharer(src) // If a sharer is `destroy()`'d, they are simply removed.
|
||||
sharedSoullinks = null
|
||||
return ..()
|
||||
|
||||
/mob/living/ghostize(flags = GHOST_FLAGS_DEFAULT, ghost_name, ghost_color)
|
||||
|
||||
@@ -142,3 +142,8 @@
|
||||
|
||||
/// How long does it take to harvest a mob?
|
||||
var/butcher_time = 8 SECONDS
|
||||
|
||||
/// Soul links we are the owner of.
|
||||
var/list/ownedSoullinks
|
||||
/// Soul links we are the sharer of.
|
||||
var/list/sharedSoullinks
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
msg += "<B>Its casing is melted and heat-warped!</B>\n"
|
||||
if(src.stat == UNCONSCIOUS)
|
||||
msg += "It is non-responsive and displaying the text: \"RUNTIME: Sensory Overload, stack 26/3\".\n"
|
||||
if(!shunted && !client)
|
||||
if(deployed_shell)
|
||||
msg += "The wireless networking light is blinking.\n"
|
||||
if(!shunted && !client && !deployed_shell) // An AI deployed to a shell will always lack a client, but also have a client just in another body.
|
||||
msg += "[src]Core.exe has stopped responding! NTOS is searching for a solution to the problem...\n"
|
||||
msg += "</span>"
|
||||
msg += "</span>"
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
else
|
||||
if(lacks_power())
|
||||
if(!aiRestorePowerRoutine)
|
||||
disconnect_shell()
|
||||
update_blind_effects()
|
||||
aiRestorePowerRoutine = 1
|
||||
update_sight()
|
||||
@@ -149,10 +150,13 @@
|
||||
health = 100
|
||||
set_stat(CONSCIOUS)
|
||||
else
|
||||
health = 100 - getOxyLoss() - getToxLoss() - getFireLoss() - getBruteLoss()
|
||||
var/old_health = health
|
||||
health = 100 - getOxyLoss() - getToxLoss() - getBruteLoss() - getFireLoss()
|
||||
var/old_stat = stat
|
||||
update_stat("updatehealth([reason])")
|
||||
diag_hud_set_health()
|
||||
|
||||
if(old_health > health || old_stat != stat) // Only disconnect if we lose health or change stat.
|
||||
disconnect_shell()
|
||||
|
||||
/mob/living/silicon/ai/proc/lacks_power()
|
||||
var/turf/T = get_turf(src)
|
||||
|
||||
@@ -147,8 +147,13 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
|
||||
var/allow_teleporter = FALSE
|
||||
|
||||
var/obj/machinery/camera/portable/builtInCamera
|
||||
|
||||
var/obj/structure/ai_core/deactivated/linked_core //For exosuit control
|
||||
/// For exosuit control.
|
||||
var/obj/structure/ai_core/deactivated/linked_core
|
||||
// For shell control.
|
||||
/// The shell an AI is deployed to.
|
||||
var/mob/living/silicon/robot/deployed_shell = null
|
||||
var/datum/action/innate/deploy_shell/deploy_action = new
|
||||
var/datum/action/innate/deploy_last_shell/redeploy_action = new
|
||||
|
||||
/// If our AI doesn't want to be the arrivals announcer, this gets set to FALSE.
|
||||
var/announce_arrivals = TRUE
|
||||
@@ -224,6 +229,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
|
||||
additional_law_channels["Holopad"] = ":h"
|
||||
|
||||
aiCamera = new/obj/item/camera/siliconcam/ai_camera(src)
|
||||
deploy_action.Grant(src)
|
||||
|
||||
if(isturf(loc))
|
||||
add_ai_verbs(src)
|
||||
@@ -297,6 +303,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
|
||||
GLOB.ai_list -= src
|
||||
GLOB.shuttle_caller_list -= src
|
||||
SSshuttle.autoEvac()
|
||||
disconnect_shell()
|
||||
if(malfhacking)
|
||||
deltimer(malfhacking)
|
||||
malfhacking = null
|
||||
@@ -384,12 +391,87 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
|
||||
var/dat_text = dat.Join("")
|
||||
src << browse(dat_text, "window=aialerts&can_close=0")
|
||||
|
||||
/mob/living/silicon/ai/proc/deploy_to_shell(mob/living/silicon/robot/target)
|
||||
if(stat || lacks_power() || control_disabled)
|
||||
to_chat(src, SPAN_DANGER("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_NOTICE("No usable AI shell beacons detected."))
|
||||
return
|
||||
|
||||
if(!target || !(target in possible)) // If the AI is looking for a new shell, or its pre-selected shell is no longer valid.
|
||||
target = tgui_input_list(usr, "Which body to control?", "AI Shell Deployment", possible)
|
||||
|
||||
if(!target || target.stat == DEAD || target.deployed || !(!target.connected_ai ||(target.connected_ai == src)))
|
||||
return
|
||||
|
||||
else if(mind)
|
||||
soullink(/datum/soullink/sharedbody, src, target)
|
||||
deployed_shell = target
|
||||
target.deploy_init(src)
|
||||
mind.transfer_to(target)
|
||||
diag_hud_set_deployed()
|
||||
|
||||
/datum/action/innate/deploy_shell
|
||||
name = "Deploy to AI Shell"
|
||||
desc = "Wirelessly control a specialized cyborg shell."
|
||||
button_icon_state = "ai_shell"
|
||||
|
||||
/datum/action/innate/deploy_shell/Trigger()
|
||||
var/mob/living/silicon/ai/AI = owner
|
||||
if(!AI)
|
||||
return
|
||||
AI.deploy_to_shell()
|
||||
|
||||
/datum/action/innate/deploy_last_shell
|
||||
name = "Reconnect to shell"
|
||||
desc = "Reconnect to the most recently used AI shell."
|
||||
button_icon_state = "ai_last_shell"
|
||||
var/mob/living/silicon/robot/last_used_shell
|
||||
|
||||
/datum/action/innate/deploy_last_shell/Trigger()
|
||||
if(!owner)
|
||||
return
|
||||
if(last_used_shell)
|
||||
var/mob/living/silicon/ai/AI = owner
|
||||
AI.deploy_to_shell(last_used_shell)
|
||||
else
|
||||
Remove(owner) // If the last shell is blown, destroy it.
|
||||
|
||||
/// Disconnect the AI from its shell.
|
||||
/mob/living/silicon/ai/proc/disconnect_shell()
|
||||
if(deployed_shell) // Forcibly call back AI in event of things such as damage, EMP or power loss.
|
||||
to_chat(src, SPAN_DANGER("Your remote connection has been reset!"))
|
||||
deployed_shell.undeploy()
|
||||
diag_hud_set_deployed()
|
||||
|
||||
/mob/living/silicon/ai/proc/spawn_shell()
|
||||
var/obj/shell_landmark
|
||||
for(var/obj/effect/landmark/shell_loc in GLOB.landmarks_list)
|
||||
if(shell_loc.name == "AI Shell")
|
||||
shell_landmark = shell_loc
|
||||
|
||||
var/mob/living/silicon/robot/S = new /mob/living/silicon/robot(shell_landmark.loc)
|
||||
var/obj/item/borg/upgrade/ai/board = new /obj/item/borg/upgrade/ai
|
||||
S.make_shell(board)
|
||||
|
||||
/mob/living/silicon/ai/proc/show_borg_info(list/status_tab_data)
|
||||
status_tab_data[++status_tab_data.len] = list("Connected cyborg count:", "[length(connected_robots)]")
|
||||
for(var/mob/living/silicon/robot/R in connected_robots)
|
||||
var/robot_status = "Nominal"
|
||||
if(R.stat || !R.client)
|
||||
robot_status = "OFFLINE"
|
||||
// This needs to be below the OFFLINE check since shells will always lack a client form the AI's view.
|
||||
else if(R.shell)
|
||||
robot_status = "AI SHELL"
|
||||
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!
|
||||
@@ -808,6 +890,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
|
||||
..()
|
||||
Stun((12 SECONDS) / severity)
|
||||
view_core()
|
||||
disconnect_shell()
|
||||
|
||||
/mob/living/silicon/ai/ex_act(severity)
|
||||
..()
|
||||
@@ -904,7 +987,9 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
|
||||
to_chat(src, SPAN_WARNING("This exosuit has a pilot and cannot be controlled."))
|
||||
return
|
||||
if(M)
|
||||
M.transfer_ai(AI_MECH_HACK, src, usr) //Called om the mech itself.
|
||||
deploy_action.Remove(src)
|
||||
redeploy_action.Remove(src)
|
||||
M.transfer_ai(AI_MECH_HACK, src, usr) // Called on the mech itself.
|
||||
|
||||
else if(href_list["open"])
|
||||
var/mob/target = locate(href_list["open"]) in GLOB.mob_list
|
||||
@@ -1459,6 +1544,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
|
||||
if(!..())
|
||||
return
|
||||
if(interaction == AI_TRANS_TO_CARD)//The only possible interaction. Upload AI mob to a card.
|
||||
disconnect_shell() // If the AI is controlling a borg, force the player back to core.
|
||||
if(!mind)
|
||||
to_chat(user, SPAN_WARNING("No intelligence patterns detected."))//No more magical carding of empty cores, AI RETURN TO BODY!!!11
|
||||
return
|
||||
|
||||
@@ -144,6 +144,7 @@
|
||||
if(status_flags & GODMODE)
|
||||
return
|
||||
|
||||
var/old_health = health
|
||||
brute = max((brute - damage_protection) * brute_mod, 0)
|
||||
burn = max((burn - damage_protection) * burn_mod, 0)
|
||||
|
||||
@@ -153,6 +154,8 @@
|
||||
if(A)
|
||||
A.take_damage(brute, burn, sharp)
|
||||
updatehealth()
|
||||
if((old_health > health) && shell && deployed && mainframe) // Only disconnect if we lose health.
|
||||
mainframe.disconnect_shell()
|
||||
return
|
||||
|
||||
while(LAZYLEN(parts) && (brute > 0 || burn > 0))
|
||||
@@ -168,6 +171,8 @@
|
||||
|
||||
parts -= picked
|
||||
updatehealth()
|
||||
if((old_health > health) && shell && deployed && mainframe) // Only disconnect if we lose health.
|
||||
mainframe.disconnect_shell()
|
||||
|
||||
/*
|
||||
Begins the stamcrit reboot process for borgs. Stuns them, and warns people if the borg has no power source.
|
||||
|
||||
@@ -34,9 +34,15 @@
|
||||
return
|
||||
|
||||
/mob/living/silicon/robot/attack_ai(mob/user)
|
||||
if(user.a_intent == INTENT_HELP && is_ai(user))
|
||||
if(user.a_intent == INTENT_HELP && is_ai(user) && !shell) // We check if is not a shell so we don't pet and deploy at the same time.
|
||||
to_chat(src, SPAN_ROBOTEMOTE("[user] gives you a digital headpat."))
|
||||
to_chat(user, SPAN_ROBOTEMOTE("You give [src] a digital headpat."))
|
||||
else if(user.a_intent == INTENT_HELP && is_ai(user) && shell)
|
||||
var/mob/living/silicon/ai/AI = user
|
||||
if(AI.controlled_mech) // If the AI is in a mech it can’t control a shell.
|
||||
to_chat(AI, SPAN_WARNING("Disconnect from your mech before piloting a shell."))
|
||||
return
|
||||
AI.deploy_to_shell(src)
|
||||
|
||||
/mob/living/silicon/robot/attack_hand(mob/living/carbon/human/user)
|
||||
add_fingerprint(user)
|
||||
|
||||
@@ -34,17 +34,20 @@
|
||||
if(cell && cell.charge <= 0)
|
||||
msg += "[SPAN_WARNING("[p_their(TRUE)] battery indicator is blinking red!")]\n"
|
||||
|
||||
switch(stat)
|
||||
if(CONSCIOUS)
|
||||
if(!client)
|
||||
msg += "[p_they(TRUE)] appear[p_s()] to be in stand-by mode.\n" //afk
|
||||
if(UNCONSCIOUS)
|
||||
msg += "[SPAN_WARNING("[p_they(TRUE)] [p_do()]n't seem to be responding.")]\n"
|
||||
if(DEAD)
|
||||
if(!suiciding)
|
||||
msg += "[SPAN_DEADSAY("It looks like [p_their()] internal subsystems are beyond repair and require replacing.")]\n"
|
||||
else
|
||||
msg += "[SPAN_WARNING("It looks like [p_their()] system is corrupted beyond repair. There is no hope of recovery.")]\n"
|
||||
if(shell)
|
||||
msg += "It appears to be an [deployed ? "active" : "empty"] AI shell.\n"
|
||||
else
|
||||
switch(stat)
|
||||
if(CONSCIOUS)
|
||||
if(!client)
|
||||
msg += "[p_they(TRUE)] appear[p_s()] to be in stand-by mode.\n" //afk
|
||||
if(UNCONSCIOUS)
|
||||
msg += "[SPAN_WARNING("[p_they(TRUE)] [p_do()]n't seem to be responding.")]\n"
|
||||
if(DEAD)
|
||||
if(!suiciding)
|
||||
msg += "[SPAN_DEADSAY("It looks like [p_their()] internal subsystems are beyond repair and require replacing.")]\n"
|
||||
else
|
||||
msg += "[SPAN_WARNING("It looks like [p_their()] system is corrupted beyond repair. There is no hope of recovery.")]\n"
|
||||
msg += "</span>"
|
||||
|
||||
if(print_flavor_text())
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
to_chat(who, "<b>Obey these laws:</b>")
|
||||
laws.show_laws(who)
|
||||
// TODO: Update to new antagonist system.
|
||||
if(mind && (mind.special_role == SPECIAL_ROLE_TRAITOR && mind.is_original_mob(src)) && connected_ai)
|
||||
if(shell) // AI shell.
|
||||
to_chat(who, SPAN_USERDANGER("Remember, you are an AI remotely controlling your shell, other AIs can be ignored."))
|
||||
else if(mind && (mind.special_role == SPECIAL_ROLE_TRAITOR && mind.is_original_mob(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>")
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
..()
|
||||
|
||||
show_laws(0)
|
||||
if(connected_ai)
|
||||
if(connected_ai && connected_ai.mind)
|
||||
if(connected_ai.mind.special_role == SPECIAL_ROLE_TRAITOR && connected_ai.malf_picker)
|
||||
make_malf_robot()
|
||||
regenerate_icons()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
/mob/living/silicon/robot/proc/sensor_mode,
|
||||
))
|
||||
GLOBAL_LIST_EMPTY(available_ai_shells)
|
||||
|
||||
/mob/living/silicon/robot
|
||||
name = "Cyborg"
|
||||
@@ -19,6 +20,13 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
var/custom_name = ""
|
||||
var/custom_sprite = FALSE // Due to all the sprites involved, a var for our custom borgs may be best.
|
||||
|
||||
/// Is this borg a shell?
|
||||
var/shell = FALSE
|
||||
/// Is this shell currently deployed?
|
||||
var/deployed = FALSE
|
||||
/// The AI deployed to a shell.
|
||||
var/mob/living/silicon/ai/mainframe = null
|
||||
var/datum/action/innate/undeployment/undeployment_action = new
|
||||
// HUD stuff.
|
||||
var/atom/movable/screen/hands = null
|
||||
var/list/inventory_screens = list()
|
||||
@@ -142,7 +150,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
/// When the camera moved signal was sent last. Avoid overdoing it.
|
||||
var/last_camera_update
|
||||
|
||||
hud_possible = list(SPECIALROLE_HUD, DIAG_STAT_HUD, DIAG_HUD, DIAG_BATT_HUD)
|
||||
hud_possible = list(SPECIALROLE_HUD, DIAG_STAT_HUD, DIAG_HUD, DIAG_BATT_HUD, DIAG_TRACK_HUD)
|
||||
|
||||
var/default_cell_type = /obj/item/stock_parts/cell/high
|
||||
/// Does the robot have ion thrusters installed?
|
||||
@@ -193,8 +201,12 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
if(wires.is_cut(WIRE_BORG_CAMERA)) // 5 = BORG CAMERA
|
||||
camera.turn_off(src, FALSE)
|
||||
|
||||
if(mmi == null)
|
||||
mmi = new /obj/item/mmi/robotic_brain(src) //Give the borg an MMI if he spawns without for some reason. (probably not the correct way to spawn a robotic brain, but it works)
|
||||
if(shell)
|
||||
var/obj/item/borg/upgrade/ai/board = new(src)
|
||||
make_shell(board)
|
||||
|
||||
else if(mmi == null)
|
||||
mmi = new /obj/item/mmi/robotic_brain(src) // Give the borg an MMI if they spawn without for some reason (probably not the correct way to spawn a robotic brain, but it works).
|
||||
mmi.icon_state = "boris"
|
||||
|
||||
initialize_components()
|
||||
@@ -251,11 +263,13 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
playsound(loc, 'sound/voice/liveagain.ogg', 75, 1)
|
||||
|
||||
/mob/living/silicon/robot/rename_character(oldname, newname)
|
||||
if(shell)
|
||||
return
|
||||
if(!..(oldname, newname))
|
||||
return FALSE
|
||||
|
||||
if(oldname != real_name)
|
||||
notify_ai(3, oldname, newname)
|
||||
notify_ai(RENAME, oldname, newname)
|
||||
custom_name = (newname != get_default_name()) ? newname : null
|
||||
setup_PDA()
|
||||
|
||||
@@ -347,6 +361,9 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
/mob/living/silicon/robot/Destroy()
|
||||
remove_robot_mindslave() // You cannot be connected to the malf AI if you are a pile of debris.
|
||||
SStgui.close_uis(wires)
|
||||
if(shell)
|
||||
undeploy()
|
||||
revert_shell()
|
||||
if(mmi && mind)//Safety for when a cyborg gets dust()ed. Or there is no MMI inside.
|
||||
var/turf/T = get_turf(loc)//To hopefully prevent run time errors.
|
||||
if(T)
|
||||
@@ -687,27 +704,33 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
switch(selected_module)
|
||||
if("Engineering")
|
||||
module = new /obj/item/robot_module/engineering(src)
|
||||
module.channels = list("Engineering" = 1)
|
||||
// AI shells have the same channels as the AI itself so we skip it.
|
||||
if(!shell)
|
||||
module.channels = list("Engineering" = 1)
|
||||
if(camera && ("Robots" in camera.network))
|
||||
camera.network += "Engineering"
|
||||
if("Janitor")
|
||||
module = new /obj/item/robot_module/janitor(src)
|
||||
module.channels = list("Service" = 1)
|
||||
if(!shell)
|
||||
module.channels = list("Service" = 1)
|
||||
if("Medical")
|
||||
module = new /obj/item/robot_module/medical(src)
|
||||
module.channels = list("Medical" = 1)
|
||||
if(!shell)
|
||||
module.channels = list("Medical" = 1)
|
||||
if(camera && ("Robots" in camera.network))
|
||||
camera.network += "Medical"
|
||||
status_flags &= ~CANPUSH
|
||||
has_advanced_reagent_vision = TRUE
|
||||
if("Mining")
|
||||
module = new /obj/item/robot_module/miner(src)
|
||||
module.channels = list("Supply" = 1)
|
||||
if(!shell)
|
||||
module.channels = list("Supply" = 1)
|
||||
if(camera && ("Robots" in camera.network))
|
||||
camera.network += "Mining Outpost"
|
||||
if("Service")
|
||||
module = new /obj/item/robot_module/butler(src)
|
||||
module.channels = list("Service" = 1)
|
||||
if(!shell)
|
||||
module.channels = list("Service" = 1)
|
||||
has_advanced_reagent_vision = TRUE
|
||||
if(selected_sprite == "Bro")
|
||||
module.module_type = "Brobot"
|
||||
@@ -724,19 +747,22 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
return FALSE
|
||||
modtype = selected_module
|
||||
designation = selected_module
|
||||
if(shell) // Update the shell name right after choosing a module.
|
||||
real_name = "[real_name] -[designation]"
|
||||
name = real_name
|
||||
module.add_languages(src)
|
||||
module.add_armor(src)
|
||||
module.add_subsystems_and_actions(src)
|
||||
if(emagged)
|
||||
module.emag_act(src)
|
||||
if(!static_radio_channels)
|
||||
if(!static_radio_channels && !shell) // Shells have the same channels as the AI and we dont want to reset them.
|
||||
radio.config(module.channels)
|
||||
rename_character(real_name, get_default_name())
|
||||
initialize_sprites(selected_sprite, module_sprites)
|
||||
if(client.stat_tab == "Status")
|
||||
SSstatpanels.set_status_tab(client)
|
||||
SSblackbox.record_feedback("tally", "cyborg_modtype", 1, "[lowertext(selected_module)]")
|
||||
notify_ai(2)
|
||||
notify_ai(NEW_MODULE)
|
||||
|
||||
/mob/living/silicon/robot/proc/initialize_sprites(selected_sprite, list/module_sprites)
|
||||
var/image/sprite_image = module_sprites[selected_sprite]
|
||||
@@ -750,10 +776,12 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
for(var/obj/item/borg/upgrade/U in contents)
|
||||
if(istype(U, /obj/item/borg/upgrade/reset)) // The reset module is supposed to be consumed on use, this stops it from dropping on the floor if used
|
||||
QDEL_NULL(U)
|
||||
if(istype(U, /obj/item/borg/upgrade/ai)) // So you can change the shell module but not drop the BORIS module.
|
||||
continue
|
||||
U.forceMove(get_turf(src))
|
||||
|
||||
/mob/living/silicon/robot/proc/reset_module()
|
||||
notify_ai(2)
|
||||
notify_ai(NEW_MODULE)
|
||||
client?.screen -= hud_used.module_store_icon
|
||||
uneq_all()
|
||||
SStgui.close_user_uis(src)
|
||||
@@ -1072,8 +1100,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
module?.update_cells()
|
||||
diag_hud_set_borgcell()
|
||||
return ITEM_INTERACT_COMPLETE
|
||||
|
||||
if(istype(used, /obj/item/encryptionkey) && opened)
|
||||
if(istype(used, /obj/item/encryptionkey/) && opened)
|
||||
if(radio)
|
||||
to_chat(user, SPAN_NOTICE("You install [used] into [src]'s radio."))
|
||||
radio.item_interaction(user, used)
|
||||
@@ -1167,8 +1194,11 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
update_icons()
|
||||
I.play_tool_sound(user, I.tool_volume)
|
||||
else //radio check
|
||||
if(shell) // Prevents AI shell key theft.
|
||||
to_chat(user, SPAN_NOTICE("The shell appears to not have an encryption key."))
|
||||
return
|
||||
if(radio)
|
||||
radio.screwdriver_act(user, I)//Push it to the radio to let it handle everything
|
||||
radio.screwdriver_act(user, I) // Push it to the radio to let it handle everything.
|
||||
else
|
||||
to_chat(user, "Unable to locate a radio.")
|
||||
update_icons()
|
||||
@@ -1275,10 +1305,20 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
|
||||
to_chat(user, SPAN_NOTICE("You emag the cover lock."))
|
||||
locked = FALSE
|
||||
// A warning to Traitors who may not know that emagging AI shells does not slave them.
|
||||
if(shell)
|
||||
to_chat(user, SPAN_BOLDWARNING("[src] seems to be controlled remotely! Emagging the interface may not work as expected."))
|
||||
log_game("[user]([user.key]) emagged [src]'s cover.")
|
||||
return TRUE
|
||||
|
||||
if(opened)
|
||||
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_BOLDWARNING("[src] is remotely controlled! Your emag attempts to disable AI control!"))
|
||||
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.")
|
||||
undeploy()
|
||||
reset_module()
|
||||
revert_shell()
|
||||
return
|
||||
if(emagged)
|
||||
to_chat(user, SPAN_WARNING("The emag sparks, and flashes red. [src] has already been emagged!"))
|
||||
return
|
||||
@@ -1444,6 +1484,8 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
|
||||
/mob/living/silicon/robot/proc/deconstruct()
|
||||
var/turf/T = get_turf(src)
|
||||
if(shell)
|
||||
undeploy()
|
||||
if(robot_suit)
|
||||
robot_suit.forceMove(T)
|
||||
robot_suit.l_leg.forceMove(T)
|
||||
@@ -1555,16 +1597,35 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
for(var/datum/action/innate/robot_override_lock/override in actions)
|
||||
override.Remove(src)
|
||||
|
||||
/**
|
||||
* Notifies the AI of a certain event related to borgs and shells.
|
||||
*
|
||||
* Arguments:
|
||||
* * notifytype - The type of notification to send.
|
||||
* * oldname - The old name of the cyborg.
|
||||
* * newname - The new name of the cyborg.
|
||||
*
|
||||
* Notify Types:
|
||||
* - NEW_BORG: A new cyborg has connected.
|
||||
* - NEW_MODULE: A cyborg has loaded a new module.
|
||||
* - RENAME: A cyborg has been renamed.(This one needs the old and new name arguments.)
|
||||
* - AI_SHELL: A new AI shell has been detected.
|
||||
* - DISCONNECT: A cyborg has disconnected.
|
||||
*/
|
||||
/mob/living/silicon/robot/proc/notify_ai(notifytype, oldname, newname)
|
||||
if(!connected_ai)
|
||||
return
|
||||
switch(notifytype)
|
||||
if(1) //New Cyborg
|
||||
if(NEW_BORG) // New Cyborg.
|
||||
to_chat(connected_ai, "<br><br>[SPAN_NOTICE("NOTICE - New cyborg connection detected: <a href='byond://?src=[connected_ai.UID()];track2=\ref[connected_ai];track=\ref[src]'>[name]</a>")]<br>")
|
||||
if(2) //New Module
|
||||
if(NEW_MODULE) // New Module.
|
||||
to_chat(connected_ai, "<br><br>[SPAN_NOTICE("NOTICE - Cyborg module change detected: [name] has loaded the [designation] module.")]<br>")
|
||||
if(3) //New Name
|
||||
if(RENAME) // New Name.
|
||||
to_chat(connected_ai, "<br><br>[SPAN_NOTICE("NOTICE - Cyborg reclassification detected: [oldname] is now designated as [newname].")]<br>")
|
||||
if(AI_SHELL) // New Shell.
|
||||
to_chat(connected_ai, "<br><br>[SPAN_NOTICE("NOTICE - New cyborg shell detected: <a href='byond://?src=[connected_ai.UID()];track2=\ref[connected_ai];track=\ref[src]'>[name]</a>")]<br>")
|
||||
if(DISCONNECT) // Disconnect.
|
||||
to_chat(connected_ai, "<br><br>[SPAN_NOTICE("NOTICE - Remote telemetry lost with [name].")]<br>")
|
||||
|
||||
/mob/living/silicon/robot/proc/disconnect_from_ai()
|
||||
if(connected_ai)
|
||||
@@ -1578,7 +1639,9 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
if(AI && AI != connected_ai)
|
||||
disconnect_from_ai()
|
||||
set_connected_ai(AI)
|
||||
notify_ai(1)
|
||||
// Shells get notifications already, so we don't want to duplicate the message for them.
|
||||
if(!shell)
|
||||
notify_ai(NEW_BORG)
|
||||
if(AI.mind.special_role == ROLE_TRAITOR && AI.malf_picker)
|
||||
make_malf_robot(AI)
|
||||
if(module)
|
||||
@@ -1721,7 +1784,9 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
if(emp_protection)
|
||||
return
|
||||
..()
|
||||
adjustStaminaLoss((30 / severity)) //They also get flashed for an additional 30
|
||||
if(shell)
|
||||
undeploy()
|
||||
adjustStaminaLoss((30 / severity)) // They also get flashed for an additional 30.
|
||||
switch(severity)
|
||||
if(EMP_HEAVY)
|
||||
disable_random_component(2, 20 SECONDS)
|
||||
@@ -2025,3 +2090,93 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
|
||||
if(curse_time == -1)
|
||||
QDEL_NULL(mmi)
|
||||
return ..()
|
||||
|
||||
/mob/living/silicon/robot/proc/make_shell(obj/item/borg/upgrade/ai/board)
|
||||
if(isnull(board))
|
||||
stack_trace("make_shell was called without a board argument! This is never supposed to happen!")
|
||||
return FALSE
|
||||
shell = TRUE
|
||||
mmi = board // This is to drop the BORIS module when we deconstruct a shell.
|
||||
braintype = "AI Shell"
|
||||
name = "AI Shell [rand(100,999)]"
|
||||
real_name = name
|
||||
GLOB.available_ai_shells |= src
|
||||
if(camera)
|
||||
camera.c_tag = real_name // Update the camera name too.
|
||||
|
||||
/// Reverts a shell back to a unformatted cyborg also drops the BORIS module.
|
||||
/mob/living/silicon/robot/proc/revert_shell()
|
||||
if(!shell)
|
||||
return
|
||||
notify_ai(DISCONNECT)
|
||||
shell = FALSE
|
||||
GLOB.available_ai_shells -= src
|
||||
name = "Unformatted Cyborg [rand(100,999)]"
|
||||
real_name = name
|
||||
for(var/obj/item/borg/upgrade/ai/U in src.contents)
|
||||
if(U)
|
||||
U.forceMove(src.loc)
|
||||
if(camera)
|
||||
camera.c_tag = real_name
|
||||
diag_hud_set_aishell()
|
||||
|
||||
/mob/living/silicon/robot/proc/deploy_init(mob/living/silicon/ai/AI)
|
||||
real_name = "[AI.real_name] shell [rand(100, 999)] [designation ? "-[designation]" : ""]" // Randomizing the name so it shows up seperately in the shells list.
|
||||
name = real_name
|
||||
setup_PDA()
|
||||
if(camera)
|
||||
camera.c_tag = real_name // Update the camera name too.
|
||||
mainframe = AI
|
||||
deployed = TRUE
|
||||
connected_ai = mainframe
|
||||
mainframe.connected_robots |= src
|
||||
lawupdate = TRUE
|
||||
lawsync()
|
||||
if(radio && AI.aiRadio) // AI keeps all channels, including Syndie if it is a Traitor.
|
||||
if(AI.aiRadio.syndie)
|
||||
radio.make_syndie()
|
||||
radio.channels = AI.aiRadio.channels
|
||||
for(var/chan in radio.channels)
|
||||
radio.secure_radio_connections[chan] = SSradio.add_object(radio, SSradio.radiochannels[chan], RADIO_CHAT)
|
||||
|
||||
diag_hud_set_aishell()
|
||||
undeployment_action.Grant(src)
|
||||
|
||||
/datum/action/innate/undeployment
|
||||
name = "Disconnect from shell"
|
||||
desc = "Stop controlling your shell and resume normal core operations."
|
||||
button_icon_state = "ai_core"
|
||||
|
||||
/datum/action/innate/undeployment/Trigger()
|
||||
if(!..())
|
||||
return FALSE
|
||||
var/mob/living/silicon/robot/R = owner
|
||||
|
||||
R.undeploy()
|
||||
return TRUE
|
||||
|
||||
/// Undeploys the AI from its shell.
|
||||
/mob/living/silicon/robot/proc/undeploy()
|
||||
if(!deployed || !mind || !mainframe)
|
||||
return
|
||||
mainframe.redeploy_action.Grant(mainframe)
|
||||
mainframe.redeploy_action.last_used_shell = src
|
||||
mind.transfer_to(mainframe)
|
||||
deployed = FALSE
|
||||
mainframe.deployed_shell = null
|
||||
undeployment_action.Remove(src)
|
||||
if(radio) // Return radio to normal.
|
||||
radio.recalculateChannels()
|
||||
if(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.
|
||||
if(mainframe.eyeobj)// Makes it so that when an AI undeploys its view isn’t moved to its core.
|
||||
mainframe.eyeobj.set_loc(loc)
|
||||
mainframe = null
|
||||
|
||||
/mob/living/silicon/robot/shell
|
||||
shell = TRUE
|
||||
allow_rename = FALSE // This is to prevent someone renaming the shell and causing confusion with it.
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
|
||||
/datum/action/innate/robot_sight/engineering_scanner/process()
|
||||
var/mob/living/silicon/robot/user = owner
|
||||
if(!user.client)
|
||||
if(!user || !user.client)
|
||||
return
|
||||
switch(mode)
|
||||
if(MODE_TRAY)
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
diag_hud_set_status()
|
||||
diag_hud_set_health()
|
||||
update_health_hud()
|
||||
diag_hud_set_aishell()
|
||||
|
||||
/mob/living/silicon/robot/KnockOut(updating = TRUE)
|
||||
. = ..()
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
//var/sensor_mode = 0 //Determines the current HUD.
|
||||
|
||||
hud_possible = list(SPECIALROLE_HUD, DIAG_STAT_HUD, DIAG_HUD)
|
||||
hud_possible = list(SPECIALROLE_HUD, DIAG_STAT_HUD, DIAG_HUD, DIAG_TRACK_HUD)
|
||||
|
||||
|
||||
var/med_hud = DATA_HUD_MEDICAL_ADVANCED //Determines the med hud to use
|
||||
|
||||
@@ -84,6 +84,13 @@
|
||||
var/datum/spell/spell = S
|
||||
spell.build_all_button_icons()
|
||||
|
||||
for(var/s in ownedSoullinks)
|
||||
var/datum/soullink/S = s
|
||||
S.ownerRevives(src)
|
||||
for(var/s in sharedSoullinks)
|
||||
var/datum/soullink/S = s
|
||||
S.sharerRevives(src)
|
||||
|
||||
return TRUE
|
||||
|
||||
/mob/living/proc/check_death_method()
|
||||
|
||||
@@ -239,7 +239,7 @@ GLOBAL_LIST_INIT(wabbajack_docile_animals, list(
|
||||
if(isrobot(M))
|
||||
var/mob/living/silicon/robot/Robot = M
|
||||
QDEL_NULL(Robot.mmi)
|
||||
Robot.notify_ai(1)
|
||||
Robot.notify_ai(NEW_BORG)
|
||||
else
|
||||
if(ishuman(M))
|
||||
var/mob/living/carbon/human/H = M
|
||||
|
||||
@@ -31,6 +31,17 @@
|
||||
build_path = /obj/item/aicard
|
||||
category = list("Miscellaneous")
|
||||
|
||||
/datum/design/boris_ai_controller
|
||||
name = "B.O.R.I.S."
|
||||
desc = "Bluespace Optimized Remote Intelligence Synchronization. An uplink device which takes the place of an MMI in cyborg endoskeletons, creating a robotic shell controlled by an AI."
|
||||
id = "borg_ai_control"
|
||||
req_tech = list("programming" = 5, "magnets" = 4, "engineering" = 3)
|
||||
build_type = MECHFAB | PROTOLATHE
|
||||
materials = list(MAT_METAL = 1700, MAT_GLASS = 1350, MAT_GOLD = 500) // Same as robobrain.
|
||||
construction_time = 75
|
||||
build_path = /obj/item/borg/upgrade/ai
|
||||
category = list("Miscellaneous", "Misc")
|
||||
|
||||
/datum/design/paicard
|
||||
name = "Personal Artificial Intelligence Card"
|
||||
desc = "Allows for the construction of a pAI Card."
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 126 KiB |
@@ -478,6 +478,7 @@
|
||||
#include "code\datums\revision.dm"
|
||||
#include "code\datums\ruins.dm"
|
||||
#include "code\datums\shuttles.dm"
|
||||
#include "code\datums\soullink.dm"
|
||||
#include "code\datums\spawners_menu.dm"
|
||||
#include "code\datums\station_state.dm"
|
||||
#include "code\datums\tgs_event_handler.dm"
|
||||
|
||||
Reference in New Issue
Block a user