From 31d05da05063e80d01515b0e584f64c07cf8b10d Mon Sep 17 00:00:00 2001 From: Geeves Date: Sat, 2 Jan 2021 17:01:55 +0200 Subject: [PATCH] Verbal Exosuit Commands (#10814) --- aurorastation.dme | 1 + code/game/atoms_movable.dm | 5 +- .../heavy_vehicle/interface/screen_objects.dm | 36 ++-- code/modules/heavy_vehicle/mech_helpers.dm | 74 ++++++++ .../modules/heavy_vehicle/mech_interaction.dm | 178 +++++++++++++++--- code/modules/heavy_vehicle/mech_life.dm | 19 ++ code/modules/heavy_vehicle/mecha.dm | 16 ++ code/modules/mob/floating_messages.dm | 4 +- html/changelogs/geeves-titanfall.yml | 7 + 9 files changed, 296 insertions(+), 44 deletions(-) create mode 100644 code/modules/heavy_vehicle/mech_helpers.dm create mode 100644 html/changelogs/geeves-titanfall.yml 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