diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm
index 6fe2d302e7b..6771d8062fc 100644
--- a/code/__DEFINES/hud.dm
+++ b/code/__DEFINES/hud.dm
@@ -25,6 +25,7 @@
#define PLANT_TOXIN_HUD "20"// Toxin level
#define PLANT_PEST_HUD "21"// Pest level
#define PLANT_WEED_HUD "22"// Weed level
+#define DIAG_TRACK_HUD "23"// Mech tracking beacon
//data HUD (medhud, sechud) defines
//Don't forget to update human/New() if you change these!
diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm
index 172aaa821f2..59557caea6a 100644
--- a/code/game/data_huds.dm
+++ b/code/game/data_huds.dm
@@ -44,7 +44,7 @@
hud_icons = list(ID_HUD, IMPTRACK_HUD, IMPLOYAL_HUD, IMPCHEM_HUD, WANTED_HUD)
/datum/atom_hud/data/diagnostic
- hud_icons = list (DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD)
+ hud_icons = list (DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD, DIAG_TRACK_HUD)
/datum/atom_hud/data/hydroponic
hud_icons = list (PLANT_NUTRIENT_HUD, PLANT_WATER_HUD, PLANT_STATUS_HUD, PLANT_HEALTH_HUD, PLANT_TOXIN_HUD, PLANT_PEST_HUD, PLANT_WEED_HUD)
@@ -287,6 +287,17 @@
if(internal_damage)
holder.icon_state = "hudwarn"
+/obj/mecha/proc/diag_hud_set_mechtracking() //Shows tracking beacons on the mech
+ var/image/holder = hud_list[DIAG_TRACK_HUD]
+ var/new_icon_state //This var exists so that the holder's icon state is set only once in the event of multiple mech beacons.
+ for(var/obj/item/mecha_parts/mecha_tracking/T in trackers)
+ if(T.ai_beacon) //Beacon with AI uplink
+ new_icon_state = "hudtrackingai"
+ break //Immediately terminate upon finding an AI beacon to ensure it is always shown over the normal one, as mechs can have several trackers.
+ else
+ new_icon_state = "hudtracking"
+ holder.icon_state = new_icon_state
+
/*~~~~~~~~~
Bots!
~~~~~~~~~~*/
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 6e155779aa5..4ec4746a198 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -50,6 +50,7 @@
var/obj/machinery/atmospherics/unary/portables_connector/connected_port = null
var/obj/item/device/radio/radio = null
+ var/list/trackers = list()
var/max_temperature = 25000
var/internal_damage_threshold = 50 //health percentage below which internal damage is possible
@@ -74,7 +75,7 @@
var/melee_cooldown = 10
var/melee_can_hit = 1
- hud_possible = list (DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD)
+ hud_possible = list (DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_TRACK_HUD)
/obj/mecha/New()
..()
@@ -102,6 +103,7 @@
diag_hud_set_mechhealth()
diag_hud_set_mechcell()
diag_hud_set_mechstat()
+ diag_hud_set_mechtracking()
////////////////////////
////// Helpers /////////
@@ -165,7 +167,7 @@
/obj/mecha/proc/drop_item()//Derpfix, but may be useful in future for engineering exosuits.
return
-/obj/mecha/hear_talk(mob/M as mob, text)
+/obj/mecha/hear_talk(mob/M, text)
if(M == occupant && radio.broadcasting)
radio.talk_into(M, text)
@@ -465,7 +467,7 @@
qdel(src)
return
-/obj/mecha/attack_hand(mob/living/user as mob)
+/obj/mecha/attack_hand(mob/living/user)
user.changeNext_move(CLICK_CD_MELEE)
user.do_attack_animation(src)
log_message("Attack by hand/paw. Attacker - [user].",1)
@@ -481,7 +483,7 @@
return
-/obj/mecha/attack_alien(mob/living/user as mob)
+/obj/mecha/attack_alien(mob/living/user)
log_message("Attack by alien. Attacker - [user].",1)
user.changeNext_move(CLICK_CD_MELEE)
user.do_attack_animation(src)
@@ -500,7 +502,7 @@
return
-/obj/mecha/attack_animal(mob/living/simple_animal/user as mob)
+/obj/mecha/attack_animal(mob/living/simple_animal/user)
log_message("Attack by simple animal. Attacker - [user].",1)
user.changeNext_move(CLICK_CD_MELEE)
user.do_attack_animation(src)
@@ -513,7 +515,7 @@
take_damage(damage)
check_for_internal_damage(list(MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST))
visible_message("[user] [user.attacktext] [src]!")
- user.create_attack_log("attacked [src.name]")
+ user.create_attack_log("attacked [name]")
else
log_append_to_last("Armor saved.")
playsound(loc, 'sound/weapons/slash.ogg', 50, 1, -1)
@@ -798,7 +800,9 @@
to_chat(user, "\the [W] is stuck to your hand, you cannot put it in \the [src]")
return
W.forceMove(src)
+ trackers += W
user.visible_message("[user] attaches [W] to [src].", "You attach [W] to [src].")
+ diag_hud_set_mechtracking()
return
else if(istype(W, /obj/item/weapon/paintkit))
@@ -866,20 +870,35 @@
//////////// AI piloting ////////////
/////////////////////////////////////
-/obj/mecha/attack_ai(var/mob/living/silicon/ai/user as mob)
+/obj/mecha/attack_ai(mob/living/silicon/ai/user)
if(!isAI(user))
return
//Allows the Malf to scan a mech's status and loadout, helping it to decide if it is a worthy chariot.
if(user.can_dominate_mechs)
examine(user) //Get diagnostic information!
- var/obj/item/mecha_parts/mecha_tracking/B = locate(/obj/item/mecha_parts/mecha_tracking) in src
- if(B) //Beacons give the AI more detailed mech information.
+ for(var/obj/item/mecha_parts/mecha_tracking/B in trackers)
to_chat(user, "Warning: Tracking Beacon detected. Enter at your own risk. Beacon Data:")
- to_chat(user, "[B.get_mecha_info()]")
+ to_chat(user, "[B.get_mecha_info_text()]")
+ break
//Nothing like a big, red link to make the player feel powerful!
to_chat(user, "ASSUME DIRECT CONTROL?
")
+ else
+ examine(user)
+ if(occupant)
+ user << "This exosuit has a pilot and cannot be controlled."
+ return
+ var/can_control_mech = FALSE
+ for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in trackers)
+ can_control_mech = TRUE
+ to_chat(user, "[bicon(src)] Status of [name]:\n\
+ [A.get_mecha_info_text()]")
+ break
+ if(!can_control_mech)
+ to_chat(user, "You cannot control exosuits without AI control beacons installed.")
+ return
+ to_chat(user, "Take control of exosuit?
")
-/obj/mecha/transfer_ai(var/interaction, mob/user, var/mob/living/silicon/ai/AI, var/obj/item/device/aicard/card)
+/obj/mecha/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/device/aicard/card)
if(!..())
return
@@ -907,12 +926,13 @@
to_chat(AI, "You have been downloaded to a mobile storage device. Wireless connection offline.")
to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) removed from [name] and stored within local memory.")
- if(AI_MECH_HACK) //Called by Malf AI mob on the mech.
- new /obj/structure/AIcore/deactivated(AI.loc)
- if(occupant) //Oh, I am sorry, were you using that?
- to_chat(AI, "Pilot detected! Forced ejection initiated!")
- to_chat(occupant, "You have been forcibly ejected!")
- go_out(1) //IT IS MINE, NOW. SUCK IT, RD!
+ if(AI_MECH_HACK) //Called by AIs on the mech
+ AI.linked_core = new /obj/structure/AIcore/deactivated(AI.loc)
+ if(AI.can_dominate_mechs)
+ if(occupant) //Oh, I am sorry, were you using that?
+ to_chat(AI, "Pilot detected! Forced ejection initiated!")
+ to_chat(occupant, "You have been forcibly ejected!")
+ go_out(1) //IT IS MINE, NOW. SUCK IT, RD!
ai_enter_mech(AI, interaction)
if(AI_TRANS_FROM_CARD) //Using an AI card to upload to a mech.
@@ -932,7 +952,7 @@
ai_enter_mech(AI, interaction)
//Hack and From Card interactions share some code, so leave that here for both to use.
-/obj/mecha/proc/ai_enter_mech(var/mob/living/silicon/ai/AI, var/interaction)
+/obj/mecha/proc/ai_enter_mech(mob/living/silicon/ai/AI, interaction)
AI.aiRestorePowerRoutine = 0
AI.loc = src
occupant = AI
@@ -945,7 +965,7 @@
AI.remote_control = src
AI.canmove = 1 //Much easier than adding AI checks! Be sure to set this back to 0 if you decide to allow an AI to leave a mech somehow.
AI.can_shunt = 0 //ONE AI ENTERS. NO AI LEAVES.
- to_chat(AI, "[interaction == AI_MECH_HACK ? "Takeover of [name] complete! You are now permanently loaded onto the onboard computer. Do not attempt to leave the station sector!" \
+ to_chat(AI, "[AI.can_dominate_mechs ? "Takeover of [name] complete! You are now permanently loaded onto the onboard computer. Do not attempt to leave the station sector!" \
: "You have been uploaded to a mech's onboard computer."]")
to_chat(AI, "Use Middle-Mouse to activate mech functions and equipment. Click normally for AI interactions.")
@@ -1172,7 +1192,7 @@
to_chat(user, "You stop inserting the MMI.")
return 0
-/obj/mecha/proc/mmi_moved_inside(var/obj/item/device/mmi/mmi_as_oc as obj,mob/user as mob)
+/obj/mecha/proc/mmi_moved_inside(obj/item/device/mmi/mmi_as_oc,mob/user)
if(mmi_as_oc && user in range(1))
if(!mmi_as_oc.brainmob || !mmi_as_oc.brainmob.client)
to_chat(user, "Consciousness matrix not detected.")
@@ -1239,7 +1259,7 @@
if(occupant && occupant == M) // The occupant exited the mech without calling go_out()
go_out(1, newloc)
-/obj/mecha/proc/go_out(var/forced, var/atom/newloc = loc)
+/obj/mecha/proc/go_out(forced, atom/newloc = loc)
if(!occupant)
return
var/atom/movable/mob_container
@@ -1248,15 +1268,29 @@
if(ishuman(occupant))
mob_container = occupant
//RemoveActions(occupant, human_occupant=1)
- else if(istype(occupant, /mob/living/carbon/brain))
+ else if(isbrain(occupant))
var/mob/living/carbon/brain/brain = occupant
//RemoveActions(brain)
mob_container = brain.container
- else if(isAI(occupant) && forced) //This should only happen if there are multiple AIs in a round, and at least one is Malf.
- //RemoveActions(occupant)
- occupant.gib() //If one Malf decides to steal a mech from another AI (even other Malfs!), they are destroyed, as they have nowhere to go when replaced.
- occupant = null
- return
+ else if(isAI(occupant))
+ var/mob/living/silicon/ai/AI = occupant
+ if(forced)//This should only happen if there are multiple AIs in a round, and at least one is Malf.
+ //RemoveActions(occupant)
+ occupant.gib() //If one Malf decides to steal a mech from another AI (even other Malfs!), they are destroyed, as they have nowhere to go when replaced.
+ occupant = null
+ return
+ else
+ if(!AI.linked_core || qdeleted(AI.linked_core))
+ to_chat(AI, "Inactive core destroyed. Unable to return.")
+ AI.linked_core = null
+ return
+ to_chat(AI, "Returning to core...")
+ AI.controlled_mech = null
+ AI.remote_control = null
+ //RemoveActions(occupant, 1)
+ mob_container = AI
+ newloc = get_turf(AI.linked_core)
+ qdel(AI.linked_core)
else
return
var/mob/living/L = occupant
@@ -1439,6 +1473,7 @@
diag_hud_set_mechhealth()
diag_hud_set_mechcell()
diag_hud_set_mechstat()
+ diag_hud_set_mechtracking()
/obj/mecha/speech_bubble(var/bubble_state = "",var/bubble_loc = src, var/list/bubble_recipients = list())
diff --git a/code/game/mecha/mecha_control_console.dm b/code/game/mecha/mecha_control_console.dm
index 11c940f7971..2bb73e2c636 100644
--- a/code/game/mecha/mecha_control_console.dm
+++ b/code/game/mecha/mecha_control_console.dm
@@ -10,10 +10,10 @@
var/screen = 0
var/stored_data
-/obj/machinery/computer/mecha/attack_ai(var/mob/user as mob)
+/obj/machinery/computer/mecha/attack_ai(mob/user)
return attack_hand(user)
-/obj/machinery/computer/mecha/attack_hand(var/mob/user as mob)
+/obj/machinery/computer/mecha/attack_hand(mob/user)
ui_interact(user)
/obj/machinery/computer/mecha/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
@@ -73,11 +73,12 @@
icon_state = "motion2"
w_class = 2
origin_tech = "programming=2;magnets=2"
+ var/ai_beacon = FALSE //If this beacon allows for AI control. Exists to avoid using istype() on checking.
/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info()
if(!in_mecha())
- return 0
- var/obj/mecha/M = src.loc
+ return FALSE
+ var/obj/mecha/M = loc
var/list/answer[0]
answer["reference"] = "\ref[src]"
answer["name"] = sanitize(replacetext(M.name,"\"","'")) // Double apostrophes break JSON
@@ -101,6 +102,25 @@
return answer
+/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info_text()
+ if(!in_mecha())
+ return FALSE
+ var/obj/mecha/M = loc
+ var/cell_charge = M.get_charge()
+ var/area/A = get_area(M)
+ var/answer = {"Name: [M.name]
+ Integrity: [M.health/initial(M.health)*100]%
+ Cell charge: [isnull(cell_charge)?"Not found":"[M.cell.percent()]%"]
+ Airtank: [M.return_pressure()]kPa
+ Pilot: [M.occupant||"None"]
+ Location: [sanitize(A.name)||"Unknown"]
+ Active equipment: [M.selected||"None"]
"}
+ if(istype(M, /obj/mecha/working/ripley))
+ var/obj/mecha/working/ripley/RM = M
+ answer += "Used cargo space: [RM.cargo.len/RM.cargo_capacity*100]%
"
+
+ return answer
+
/obj/item/mecha_parts/mecha_tracking/emp_act()
qdel(src)
return
@@ -110,9 +130,9 @@
return
/obj/item/mecha_parts/mecha_tracking/proc/in_mecha()
- if(istype(src.loc, /obj/mecha))
- return src.loc
- return 0
+ if(istype(loc, /obj/mecha))
+ return loc
+ return FALSE
/obj/item/mecha_parts/mecha_tracking/proc/shock()
var/obj/mecha/M = in_mecha()
@@ -121,19 +141,26 @@
qdel(src)
/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_log()
- if(!src.in_mecha())
+ if(!in_mecha())
return 0
- var/obj/mecha/M = src.loc
+ var/obj/mecha/M = loc
return M.get_log_html()
+/obj/item/mecha_parts/mecha_tracking/ai_control
+ name = "exosuit AI control beacon"
+ desc = "A device used to transmit exosuit data. Also allows active AI units to take control of said exosuit."
+ origin_tech = "programming=3;magnets=2;engineering=2"
+ ai_beacon = TRUE
+
/obj/item/weapon/storage/box/mechabeacons
name = "Exosuit Tracking Beacons"
- New()
- ..()
- new /obj/item/mecha_parts/mecha_tracking(src)
- new /obj/item/mecha_parts/mecha_tracking(src)
- new /obj/item/mecha_parts/mecha_tracking(src)
- new /obj/item/mecha_parts/mecha_tracking(src)
- new /obj/item/mecha_parts/mecha_tracking(src)
- new /obj/item/mecha_parts/mecha_tracking(src)
- new /obj/item/mecha_parts/mecha_tracking(src)
+
+/obj/item/weapon/storage/box/mechabeacons/New()
+ ..()
+ new /obj/item/mecha_parts/mecha_tracking(src)
+ new /obj/item/mecha_parts/mecha_tracking(src)
+ new /obj/item/mecha_parts/mecha_tracking(src)
+ new /obj/item/mecha_parts/mecha_tracking(src)
+ new /obj/item/mecha_parts/mecha_tracking(src)
+ new /obj/item/mecha_parts/mecha_tracking(src)
+ new /obj/item/mecha_parts/mecha_tracking(src)
diff --git a/code/game/mecha/medical/medical.dm b/code/game/mecha/medical/medical.dm
index ce28687a9e6..8aee3ef1f4c 100644
--- a/code/game/mecha/medical/medical.dm
+++ b/code/game/mecha/medical/medical.dm
@@ -4,4 +4,4 @@
/obj/mecha/medical/New()
..()
- new /obj/item/mecha_parts/mecha_tracking(src)
+ trackers += new /obj/item/mecha_parts/mecha_tracking(src)
diff --git a/code/game/mecha/working/ripley.dm b/code/game/mecha/working/ripley.dm
index f26e34834fb..bb5e0237c9b 100644
--- a/code/game/mecha/working/ripley.dm
+++ b/code/game/mecha/working/ripley.dm
@@ -110,10 +110,10 @@
//Add ore box to cargo
cargo.Add(new /obj/structure/ore_box(src))
- //Attach hydrolic clamp
+ //Attach hydraulic clamp
var/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/HC = new /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp
HC.attach(src)
- for(var/obj/item/mecha_parts/mecha_tracking/B in contents)//Deletes the beacon so it can't be found easily
+ for(var/obj/item/mecha_parts/mecha_tracking/B in trackers)//Deletes the beacon so it can't be found easily
qdel(B)
var/obj/item/mecha_parts/mecha_equipment/mining_scanner/scanner = new /obj/item/mecha_parts/mecha_equipment/mining_scanner
diff --git a/code/game/mecha/working/working.dm b/code/game/mecha/working/working.dm
index ccbfe2ef21b..e4c001a508a 100644
--- a/code/game/mecha/working/working.dm
+++ b/code/game/mecha/working/working.dm
@@ -3,4 +3,4 @@
/obj/mecha/working/New()
..()
- new /obj/item/mecha_parts/mecha_tracking(src)
+ trackers += new /obj/item/mecha_parts/mecha_tracking(src)
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 6d3527d3f09..44bb237848a 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -99,6 +99,8 @@ var/list/ai_verbs_default = list(
var/obj/machinery/camera/portable/builtInCamera
+ var/obj/structure/AIcore/deactivated/linked_core //For exosuit control
+
var/arrivalmsg = "$name, $rank, has arrived on the station."
/mob/living/silicon/ai/proc/add_ai_verbs()
@@ -535,7 +537,16 @@ var/list/ai_verbs_default = list(
if(href_list["ai_take_control"]) //Mech domination
var/obj/mecha/M = locate(href_list["ai_take_control"])
if(controlled_mech)
- to_chat(src, "You are already loaded into an onboard computer!")
+ to_chat(src, "You are already loaded into an onboard computer!")
+ return
+ if(!cameranet.checkCameraVis(M))
+ to_chat(src, "Exosuit is no longer near active cameras.")
+ return
+ if(lacks_power())
+ to_chat(src, "You're depowered!")
+ return
+ if(!isturf(loc))
+ to_chat(src, "You aren't in your core!")
return
if(M)
M.transfer_ai(AI_MECH_HACK,src, usr) //Called om the mech itself.
diff --git a/code/modules/research/designs/mechfabricator_designs.dm b/code/modules/research/designs/mechfabricator_designs.dm
index 702afa68f2f..5ea86d2c7a3 100644
--- a/code/modules/research/designs/mechfabricator_designs.dm
+++ b/code/modules/research/designs/mechfabricator_designs.dm
@@ -1078,6 +1078,16 @@
construction_time = 50
category = list("Misc")
+/datum/design/mecha_tracking_ai_control
+ name = "AI Control Beacon"
+ id = "mecha_tracking_ai_control"
+ build_type = MECHFAB
+ build_path = /obj/item/mecha_parts/mecha_tracking/ai_control
+ materials = list(MAT_METAL = 1000, MAT_GLASS = 500, MAT_SILVER = 200)
+ req_tech = list("programming" = 3, "magnets" = 2, "engineering" = 2)
+ construction_time = 50
+ category = list("Misc")
+
/datum/design/ipc_head
name = "IPC Head"
id = "ipc_head"
diff --git a/icons/mob/hud.dmi b/icons/mob/hud.dmi
index efff252421b..1de6fc4ac1d 100644
Binary files a/icons/mob/hud.dmi and b/icons/mob/hud.dmi differ