diff --git a/aurorastation.dme b/aurorastation.dme
index 0d54d224216..8a5dc6e0d41 100644
--- a/aurorastation.dme
+++ b/aurorastation.dme
@@ -1661,6 +1661,7 @@
#include "code\modules\heavy_vehicle\mech_construction.dm"
#include "code\modules\heavy_vehicle\mech_damage.dm"
#include "code\modules\heavy_vehicle\mech_damage_immunity.dm"
+#include "code\modules\heavy_vehicle\mech_helpers.dm"
#include "code\modules\heavy_vehicle\mech_icon.dm"
#include "code\modules\heavy_vehicle\mech_interaction.dm"
#include "code\modules\heavy_vehicle\mech_life.dm"
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index be1b9db2254..4b9942f1dae 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -365,4 +365,7 @@
M.Turn(pick(-30, 30))
animate(I, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 3, transform = M, easing = CUBIC_EASING)
sleep(1)
- animate(I, alpha = 0, transform = matrix(), time = 1)
\ No newline at end of file
+ animate(I, alpha = 0, transform = matrix(), time = 1)
+
+/atom/movable/proc/get_floating_chat_x_offset()
+ return 0
\ No newline at end of file
diff --git a/code/modules/heavy_vehicle/interface/screen_objects.dm b/code/modules/heavy_vehicle/interface/screen_objects.dm
index b76d3c2fc43..89a8ad697ad 100644
--- a/code/modules/heavy_vehicle/interface/screen_objects.dm
+++ b/code/modules/heavy_vehicle/interface/screen_objects.dm
@@ -6,6 +6,10 @@
var/mob/living/heavy_vehicle/owner
maptext_y = 11
+/obj/screen/mecha/proc/notify_user(var/mob/user, var/text)
+ if(user.loc == owner)
+ to_chat(user, text)
+
/obj/screen/mecha/radio
name = "radio"
icon_state = "base"
@@ -136,12 +140,12 @@
var/modifiers = params2list(params)
if(modifiers["ctrl"])
if(owner.hardpoints_locked)
- to_chat(usr, "Hardpoint ejection system is locked.")
+ notify_user(usr, SPAN_WARNING("The hardpoint ejection system is locked."))
return
if(owner.remove_system(hardpoint_tag))
- to_chat(usr, "You disengage and discard the system mounted to your [hardpoint_tag] hardpoint.")
+ notify_user(usr, SPAN_NOTICE("You disengage and discard the system mounted to your [hardpoint_tag] hardpoint."))
else
- to_chat(usr, "You fail to remove the system mounted to your [hardpoint_tag] hardpoint.")
+ notify_user(usr, SPAN_DANGER("You fail to remove the system mounted to your [hardpoint_tag] hardpoint."))
return
if(owner.selected_hardpoint == hardpoint_tag)
@@ -203,7 +207,7 @@
owner.use_air = toggled
var/main_color = owner.use_air ? "#d1d1d1" : "#525252"
maptext = "AIR"
- to_chat(usr, "Auxiliary atmospheric system [owner.use_air ? "enabled" : "disabled"].")
+ notify_user(usr, SPAN_NOTICE("Auxiliary atmospheric system [owner.use_air ? "enabled" : "disabled"]."))
/obj/screen/mecha/toggle/maint
name = "toggle maintenance protocol"
@@ -217,7 +221,7 @@
owner.maintenance_protocols = toggled
var/main_color = owner.maintenance_protocols ? "#d1d1d1" : "#525252"
maptext = "MAINT"
- to_chat(usr, "Maintenance protocols [owner.maintenance_protocols ? "enabled" : "disabled"].")
+ notify_user(usr, SPAN_NOTICE("Maintenance protocols [owner.maintenance_protocols ? "enabled" : "disabled"]."))
/obj/screen/mecha/toggle/hardpoint
name = "toggle hardpoint lock"
@@ -227,13 +231,13 @@
/obj/screen/mecha/toggle/hardpoint/toggled()
if(owner.force_locked)
- to_chat(usr, "The locking system cannot be operated due to software restriction. Contact the manufacturer for more details.")
+ notify_user(usr, SPAN_WARNING("The locking system cannot be operated due to software restriction. Contact the manufacturer for more details."))
return
toggled = !toggled
owner.hardpoints_locked = toggled
var/main_color = owner.hardpoints_locked ? "#d1d1d1" : "#525252"
maptext = "GEAR"
- to_chat(usr, "Hardpoint system access is now [owner.hardpoints_locked ? "disabled" : "enabled"].")
+ notify_user(usr, SPAN_NOTICE("Hardpoint system access is now [owner.hardpoints_locked ? "disabled" : "enabled"]."))
/obj/screen/mecha/toggle/hatch
name = "toggle hatch lock"
@@ -243,10 +247,10 @@
/obj/screen/mecha/toggle/hatch/toggled()
if(!owner.hatch_locked && !owner.hatch_closed)
- to_chat(usr, "You cannot lock the hatch while it is open.")
+ notify_user(usr, SPAN_WARNING("You cannot lock the hatch while it is open."))
return
if(owner.force_locked)
- to_chat(usr, "The locking system cannot be operated due to software restriction. Contact the manufacturer for more details.")
+ notify_user(usr, SPAN_WARNING("The locking system cannot be operated due to software restriction. Contact the manufacturer for more details."))
return
toggled = !toggled
owner.hatch_locked = toggled
@@ -258,7 +262,7 @@
maptext = "LOCK"
maptext_y = 11
maptext_x = 5
- to_chat(usr, "The [owner.body.hatch_descriptor] is [owner.hatch_locked ? "now" : "no longer" ] locked.")
+ notify_user(usr, SPAN_NOTICE("The [owner.body.hatch_descriptor] is [owner.hatch_locked ? "now" : "no longer" ] locked."))
/obj/screen/mecha/toggle/hatch_open
name = "open or close hatch"
@@ -268,13 +272,13 @@
/obj/screen/mecha/toggle/hatch_open/toggled()
if(owner.hatch_locked && owner.hatch_closed)
- to_chat(usr, "You cannot open the hatch while it is locked.")
+ notify_user(usr, SPAN_WARNING("You cannot open the hatch while it is locked."))
return
toggled = !toggled
owner.hatch_closed = toggled
maptext = "[owner.hatch_closed ? "OPEN" : "CLOSE"]"
maptext_x = owner.hatch_closed ? 4 : 3
- to_chat(usr, "The [owner.body.hatch_descriptor] is now [owner.hatch_closed ? "closed" : "open" ].")
+ notify_user(usr, SPAN_NOTICE("The [owner.body.hatch_descriptor] is now [owner.hatch_closed ? "closed" : "open" ]."))
owner.update_icon()
// This is basically just a holder for the updates the mech does.
@@ -291,15 +295,15 @@
/obj/screen/mecha/toggle/sensor/toggled()
if(!owner.head)
- to_chat(usr, "I/O Error: Sensor systems not found.")
+ notify_user(usr, SPAN_WARNING("I/O Error: Sensor systems not found."))
return
if(!owner.head.vision_flags)
- to_chat(usr, "\The [owner.head] does not have any special sensor configurations.")
+ notify_user(usr, SPAN_WARNING("\The [owner.head] does not have any special sensor configurations."))
return
toggled = !toggled
owner.head.active_sensors = toggled
var/main_color = owner.head.active_sensors ? "#d1d1d1" : "#525252"
maptext = "SENSOR"
- to_chat(usr, "[capitalize_first_letters(owner.head.name)] Advanced Sensor mode is [owner.head.active_sensors ? "now" : "no longer" ] active.")
+ notify_user(usr, SPAN_NOTICE("[capitalize_first_letters(owner.head.name)] Advanced Sensor mode is [owner.head.active_sensors ? "now" : "no longer" ] active."))
-#undef BAR_CAP
+#undef BAR_CAP
\ No newline at end of file
diff --git a/code/modules/heavy_vehicle/mech_helpers.dm b/code/modules/heavy_vehicle/mech_helpers.dm
new file mode 100644
index 00000000000..deb71d6aa0c
--- /dev/null
+++ b/code/modules/heavy_vehicle/mech_helpers.dm
@@ -0,0 +1,74 @@
+/mob/living/heavy_vehicle/proc/can_move(var/mob/user)
+ . = 0
+ if(world.time < next_mecha_move)
+ return
+
+ if(incapacitated() || (user && user.incapacitated()) || lockdown)
+ return
+
+ if(!legs)
+ if(user)
+ to_chat(user, "\The [src] has no means of propulsion!")
+ next_mecha_move = world.time + 3 // Just to stop them from getting spammed with messages.
+ return
+
+ if(!legs.motivator || legs.total_damage > 45)
+ if(user)
+ to_chat(user, "Your motivators are damaged! You can't move!")
+ next_mecha_move = world.time + 15
+ return
+
+ next_mecha_move = world.time + legs.move_delay
+
+ if(maintenance_protocols)
+ if(user)
+ to_chat(user, "Maintenance protocols are in effect.")
+ return
+
+ var/obj/item/cell/C = get_cell()
+ if(!C || !C.check_charge(legs.power_use * CELLRATE))
+ if(user)
+ to_chat(user, "The power indicator flashes briefly.")
+ return
+
+ return TRUE
+
+/mob/living/heavy_vehicle/proc/toggle_maintenance_protocols()
+ var/obj/screen/mecha/toggle/maint/M = locate() in hud_elements
+ M.toggled()
+ return TRUE
+
+/mob/living/heavy_vehicle/proc/toggle_hatch()
+ var/obj/screen/mecha/toggle/hatch_open/H = locate() in hud_elements
+ H.toggled()
+ return TRUE
+
+/mob/living/heavy_vehicle/proc/toggle_lock()
+ var/obj/screen/mecha/toggle/hatch/L = locate() in hud_elements
+ L.toggled()
+ return TRUE
+
+/mob/living/heavy_vehicle/proc/can_listen()
+ return TRUE
+
+/mob/living/heavy_vehicle/proc/assign_leader(var/mob/living/carbon/human/H)
+ leader_name = H.name
+ leader = WEAKREF(H)
+
+/mob/living/heavy_vehicle/proc/unassign_leader()
+ leader = null
+ leader_name = null
+
+/mob/living/heavy_vehicle/proc/assign_following(var/mob/living/carbon/human/H)
+ following_name = H.name
+ following = WEAKREF(H)
+
+/mob/living/heavy_vehicle/proc/unassign_following()
+ following = null
+ following_name = null
+
+/mob/living/heavy_vehicle/proc/prepare_nickname(var/text)
+ text = replacemany(text, list("\"" = "", "." = "", "," = ""))
+ text = trim_left(text)
+ text = trim_right(text)
+ return text
\ No newline at end of file
diff --git a/code/modules/heavy_vehicle/mech_interaction.dm b/code/modules/heavy_vehicle/mech_interaction.dm
index 21e84e03f27..c4474a7ab68 100644
--- a/code/modules/heavy_vehicle/mech_interaction.dm
+++ b/code/modules/heavy_vehicle/mech_interaction.dm
@@ -238,6 +238,7 @@
if(user.client) user.client.screen |= hud_elements
LAZYDISTINCTADD(user.additional_vision_handlers, src)
update_icon()
+ walk(src, 0) // stop it from auto moving when the pilot gets in
return 1
/mob/living/heavy_vehicle/proc/sync_access()
@@ -275,31 +276,7 @@
UNSETEMPTY(pilots)
/mob/living/heavy_vehicle/relaymove(var/mob/living/user, var/direction)
- if(world.time < next_mecha_move)
- return 0
-
- if(!user || incapacitated() || user.incapacitated() || lockdown)
- return
-
- if(!legs)
- to_chat(user, "\The [src] has no means of propulsion!")
- next_mecha_move = world.time + 3 // Just to stop them from getting spammed with messages.
- return
-
- if(!legs.motivator || legs.total_damage > 45)
- to_chat(user, "Your motivators are damaged! You can't move!")
- next_mecha_move = world.time + 15
- return
-
- next_mecha_move = world.time + legs.move_delay
-
- if(maintenance_protocols)
- to_chat(user, "Maintenance protocols are in effect.")
- return
-
- var/obj/item/cell/C = get_cell()
- if(!C || !C.check_charge(legs.power_use * CELLRATE))
- to_chat(user, "The power indicator flashes briefly.")
+ if(!can_move(user))
return
if(hallucination >= EMP_MOVE_DISRUPT && prob(30))
@@ -565,3 +542,154 @@
src.visible_message("\The [src] beeps loudly as its servos sieze up, and it enters lockdown mode!")
else
src.visible_message("\The [src] hums with life as it is released from its lockdown mode!")
+
+/mob/living/heavy_vehicle/get_floating_chat_x_offset()
+ return 8
+
+/mob/living/heavy_vehicle/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "", var/italics = 0, var/mob/speaker = null, var/sound/speech_sound, var/sound_vol)
+ if(can_listen())
+ addtimer(CALLBACK(src, .proc/handle_hear_say, speaker, message), 0.5 SECONDS)
+ return ..()
+
+// heavily commented so it doesn't look like one fat chunk of code, which it still does - Geeves
+/mob/living/heavy_vehicle/proc/handle_hear_say(var/mob/speaker, var/text)
+ var/found_text = findtext(text, name)
+ if(!found_text)
+ found_text = findtext(text, nickname)
+ if(found_text)
+ text = copytext(text, found_text) // I'm trimming the text each time so only information stated after eachother is valid
+
+ // a quick way to figure out the remote control status of the mech
+ if(findtext(text, "report diagnostics"))
+ var/has_leader = FALSE
+ if(leader)
+ var/mob/resolved_leader = leader.resolve()
+ if(!resolved_leader)
+ say("Error, leader not found. Unassigning...")
+ unassign_leader()
+ return
+ has_leader = TRUE
+ say("Currently [has_leader ? "paired with [leader_name]" : "unpaired"].")
+ if(following)
+ var/mob/resolved_following = following.resolve()
+ if(!resolved_following)
+ say("Error, follow target not found. Unassigning...")
+ unassign_following()
+ else
+ say("Currently following [resolved_following.name].")
+ if(nickname)
+ say("Nickname set to [nickname].")
+ say("Maintenance protocols, [maintenance_protocols ? "active" : "disabled"].")
+ return
+
+ // Checking whether we have a leader or not
+ if(!leader)
+ if(!maintenance_protocols) // don't select a leader unless we have maintenance protocols set
+ return
+ // If we have no leader, we listen to the keywords 'listen to'
+ if(findtext(text, "listen to"))
+ text = copytext(text, found_text)
+ found_text = findtext(text, "me") // if they say listen to me, we listen to them
+ if(found_text)
+ assign_leader(speaker)
+ say("New paired leader, [leader_name], confirmed and added to temporary biometric database.")
+ return
+ // check for humans and their IDs
+ for(var/mob/living/carbon/human/H in view(world.view, src))
+ var/obj/item/card/id/ID = H.GetIdCard(TRUE)
+ if(ID?.registered_name) // we ID people based on their... ID
+ if(findtext(text, ID.registered_name))
+ assign_leader(H)
+ say("New paired leader, [ID.registered_name], confirmed and added to temporary biometric database.")
+ break
+ return
+ else
+ var/mob/resolved_leader = leader.resolve()
+ if(!resolved_leader)
+ say("Error, leader not found. Unassigning...")
+ unassign_leader()
+ return
+ if(speaker != resolved_leader || (speaker in pilots))
+ return
+
+ found_text = findtext(text, "set nickname to")
+ if(found_text)
+ text = copytext(text, found_text + 15)
+ text = prepare_nickname(text)
+ if(lowertext(text) == "null")
+ nickname = null
+ say("Nickname removed.")
+ else
+ nickname = text
+ say("Nickname set to [text].")
+ return
+
+ // simply toggle maintenance protocols
+ if(findtext(text, "toggle maintenance protocols"))
+ if(toggle_maintenance_protocols())
+ say("Maintenance protocols toggled [maintenance_protocols ? "on" : "off"].")
+ return
+
+ // simply open or close the hatch
+ if(findtext(text, "toggle hatch"))
+ if(hatch_locked || force_locked)
+ say("Hatch locked, cannot toggle status.")
+ return
+ if(toggle_hatch())
+ say("Hatch [hatch_closed ? "closed" : "opened"].")
+ return
+
+ // simply toggle the lock status
+ if(findtext(text, "toggle lock"))
+ if(!hatch_closed)
+ say("Hatch lock cannot be toggled while the hatch is open.")
+ return
+ if(force_locked)
+ say("Hatch lock forced on, cannot override.")
+ return
+ if(toggle_lock())
+ say("Hatch [hatch_locked ? "locked" : "unlocked"].")
+ return
+
+ // unlink the leader to get a new one
+ if(findtext(text, "unlink"))
+ unassign_leader()
+ say("Leader dropped, awaiting new leader.")
+ return
+
+ // stop following who you were assigned to follow
+ if(findtext(text, "stop"))
+ unassign_following()
+ walk(src, 0)
+ say("Holding position.")
+ return
+
+ // set a follow range for the mecha, one to three, at which point it stops approaching
+ found_text = findtext(text, "follow range")
+ if(found_text)
+ text = copytext(text, found_text)
+ var/list/follow_range = list("one", "two", "three")
+ for(var/i = 1 to length(follow_range))
+ if(findtext(text, follow_range[i]))
+ say("Follow range set to [follow_range[i]] units.")
+ follow_distance = i
+ break
+ return
+
+ // set who it has to follow, broken into two steps to make it more versatile
+ found_text = findtext(text, "follow")
+ if(found_text)
+ text = copytext(text, found_text)
+ found_text = findtext(text, "me")
+ if(found_text)
+ assign_following(speaker)
+ say("Following [speaker.name].")
+ return
+ for(var/mob/living/carbon/human/H in view(world.view, src))
+ var/obj/item/card/id/ID = H.GetIdCard(TRUE)
+ if(ID?.registered_name) // we ID people based on their... ID
+ if(findtext(text, ID.registered_name))
+ assign_following(H)
+ say("Following [ID.registered_name].")
+ break
+ return
\ No newline at end of file
diff --git a/code/modules/heavy_vehicle/mech_life.dm b/code/modules/heavy_vehicle/mech_life.dm
index 975ee16c16c..e7e22b28e4f 100644
--- a/code/modules/heavy_vehicle/mech_life.dm
+++ b/code/modules/heavy_vehicle/mech_life.dm
@@ -41,6 +41,25 @@
handle_vision()
handle_hud_icons()
+/mob/living/heavy_vehicle/think()
+ var/mob/resolved_following
+ if(following)
+ resolved_following = following.resolve()
+
+ if(length(pilots))
+ if(resolved_following && !(resolved_following in pilots)) // if the person we're following is our pilot, we keep following them, otherwise we drop them
+ unassign_following()
+ return
+
+ if(following)
+ if(isturf(loc) && can_move())
+ if(resolved_following)
+ walk_to(src, resolved_following, follow_distance, legs.move_delay)
+ else
+ unassign_following()
+ else
+ walk(src, 0) // this stops them from moving
+
/mob/living/heavy_vehicle/get_cell()
RETURN_TYPE(/obj/item/cell)
return body?.cell
diff --git a/code/modules/heavy_vehicle/mecha.dm b/code/modules/heavy_vehicle/mecha.dm
index c2dc0a19cac..f2f753a36b9 100644
--- a/code/modules/heavy_vehicle/mecha.dm
+++ b/code/modules/heavy_vehicle/mecha.dm
@@ -9,6 +9,7 @@
mob_size = MOB_LARGE
mob_push_flags = ALLMOBS
can_buckle = FALSE
+ accent = ACCENT_TTS
var/decal
var/emp_damage = 0
@@ -27,6 +28,16 @@
var/list/saved_access = list()
var/sync_access = TRUE
+ // Mob we're currently paired with or following | the names are saved to prevent metagaming when returning diagnostics
+ var/datum/weakref/leader
+ var/leader_name
+ var/datum/weakref/following
+ var/following_name
+
+ // Orders from our leader
+ var/nickname // we'll respond to our name or our nickname
+ var/follow_distance = 3
+
// Mob currently piloting the mech.
var/list/pilots
var/list/pilot_overlays
@@ -75,6 +86,8 @@
var/obj/screen/mecha/power/hud_power
/mob/living/heavy_vehicle/Destroy()
+ unassign_leader()
+ unassign_following()
selected_system = null
@@ -199,6 +212,9 @@
// Build icon.
update_icon()
+ add_language(LANGUAGE_TCB)
+ set_default_language(LANGUAGE_TCB)
+
. = INITIALIZE_HINT_LATELOAD
/mob/living/heavy_vehicle/LateInitialize()
diff --git a/code/modules/mob/floating_messages.dm b/code/modules/mob/floating_messages.dm
index 7778072ba12..311f9fac99c 100644
--- a/code/modules/mob/floating_messages.dm
+++ b/code/modules/mob/floating_messages.dm
@@ -51,7 +51,7 @@ var/list/floating_chat_colors = list()
I.maptext_height = 64
I.plane = FLOAT_PLANE
I.layer = HUD_LAYER - 0.01
- I.pixel_x = -round(I.maptext_width/2) + 16
+ I.pixel_x = (-round(I.maptext_width/2) + 16) + holder.get_floating_chat_x_offset()
I.appearance_flags = RESET_COLOR|RESET_ALPHA|RESET_TRANSFORM
style = "font-family: 'Small Fonts'; -dm-text-outline: 1 black; font-size: [size]px; [style]"
@@ -69,4 +69,4 @@ var/list/floating_chat_colors = list()
/proc/remove_floating_text(atom/movable/holder, image/I)
animate(I, 2, pixel_y = I.pixel_y + 10, alpha = 0)
- LAZYREMOVE(holder.stored_chat_text, I)
\ No newline at end of file
+ LAZYREMOVE(holder.stored_chat_text, I)
diff --git a/html/changelogs/geeves-titanfall.yml b/html/changelogs/geeves-titanfall.yml
new file mode 100644
index 00000000000..b31d425782a
--- /dev/null
+++ b/html/changelogs/geeves-titanfall.yml
@@ -0,0 +1,7 @@
+author: Geeves
+
+delete-after: True
+
+changes:
+ - rscadd: "Added the ability for exosuits to respond to verbal commands."
+ - rscadd: "The guide for the verbal commands will be on the Guide to Robotics wiki page."
\ No newline at end of file