diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm
index 6b5807deaef..9e01753d492 100644
--- a/code/__HELPERS/lists.dm
+++ b/code/__HELPERS/lists.dm
@@ -353,12 +353,12 @@
var/middle = L.len / 2 + 1 // Copy is first,second-1
return mergeLists(sortList(L.Copy(0,middle)), sortList(L.Copy(middle))) //second parameter null = to end of list
-//Mergsorge: uses sortList() but uses the var's name specifically. This should probably be using mergeAtom() instead
+//Mergsorge: uses sortAssoc() but uses the var's name specifically. This should probably be using mergeAtom() instead
/proc/sortNames(var/list/L)
var/list/Q = new()
for(var/atom/x in L)
Q[x.name] = x
- return sortList(Q)
+ return sortAssoc(Q)
/proc/mergeLists(var/list/L, var/list/R)
var/Li=1
diff --git a/code/datums/helper_datums/input.dm b/code/datums/helper_datums/input.dm
index 3bcf9509e37..19205a4ab62 100644
--- a/code/datums/helper_datums/input.dm
+++ b/code/datums/helper_datums/input.dm
@@ -1,71 +1,90 @@
/proc/input_async(mob/user=usr, prompt, list/choices)
- var/datum/async_input/A = new(choices, prompt)
- A.show(user)
+ var/datum/async_input/A = new(choices, prompt, , user)
+ A.show()
return A
/proc/input_ranked_async(mob/user=usr, prompt="Order by greatest to least preference", list/choices)
- var/datum/async_input/ranked/A = new(choices, prompt)
- A.show(user)
+ var/datum/async_input/ranked/A = new(choices, prompt, "ranked_input", user)
+ A.show()
+ return A
+
+/proc/input_autocomplete_async(mob/user=usr, prompt="Enter text: ", list/choices)
+ var/datum/async_input/autocomplete/A = new(choices, prompt, "ac_input", user)
+ A.show()
return A
/datum/async_input
var/datum/browser/popup
+ // If associative list, key will be used for display, but the final result will be the value
var/list/choices
+ var/datum/callback/onCloseCb
var/flash = TRUE
var/immediate_submit = FALSE
var/prompt
var/result = null
var/style = "text-align: center;"
+ var/mob/user
var/window_id
var/height = 200
var/width = 400
-/datum/async_input/New(list/new_choices, new_prompt="Pick an option:", new_window_id="async_input")
+/datum/async_input/New(list/new_choices, new_prompt="Pick an option:", new_window_id="async_input", mob/new_user=usr)
choices = new_choices
prompt = new_prompt
window_id = new_window_id
+ user = new_user
+ popup = new(user, window_id, , width, height, src)
/datum/async_input/proc/close()
if(popup)
popup.close()
+ if(result && choices[result])
+ result = choices[result]
+ if(onCloseCb)
+ onCloseCb.Invoke(result)
return result
-/datum/async_input/proc/show(mob/user)
- var/dat = create_ui(user)
- popup = new(user, window_id, , width, height, src)
- popup.set_content(dat)
+// Callback function should take the result as the last argument
+/datum/async_input/proc/on_close(var/datum/callback/cb)
+ onCloseCb = cb
+
+/datum/async_input/proc/show()
+ popup.set_content(create_ui())
if(flash && result == null)
window_flash(user.client)
popup.open()
-/datum/async_input/proc/create_ui(mob/user)
+/datum/async_input/proc/create_ui()
var/dat = "
"
- dat += render_prompt(user)
- dat += render_choices(user)
+ dat += render_prompt()
+ dat += render_choices()
dat += " "
dat += " "
- dat += button("Submit", "submit=1", , result == null && !immediate_submit)
+ dat += render_buttons()
dat += "
"
return dat
-/datum/async_input/proc/render_prompt(mob/user)
+/datum/async_input/proc/render_prompt()
return "
"
for(var/i = 1, i <= choices.len, i++)
@@ -103,13 +122,47 @@
if(href_list["upvote"])
var/index = text2num(href_list["upvote"])
choices.Swap(index, index - 1)
- show(usr)
+ show()
return
if(href_list["downvote"])
var/index = text2num(href_list["downvote"])
choices.Swap(index, index + 1)
- show(usr)
+ show()
+ return
+
+ ..()
+
+/datum/async_input/autocomplete
+ immediate_submit = TRUE
+ height = 150
+
+/datum/async_input/autocomplete/New()
+ ..()
+ popup.add_script("autocomplete.js", 'html/browser/autocomplete.js')
+
+/datum/async_input/autocomplete/render_prompt()
+ return ""
+
+/datum/async_input/autocomplete/render_choices()
+ var/dat = ""
+ dat += ""
+ return dat
+
+/datum/async_input/autocomplete/render_buttons()
+ var/dat = button("Submit", "", , result == null && !immediate_submit, "submit-button")
+ dat += button("Cancel", "close=1")
+ return dat
+
+/datum/async_input/autocomplete/Topic(href, href_list)
+ if(href_list["submit"])
+ // Entering an invalid choice is the same as canceling
+ if(href_list["submit"] in choices)
+ result = href_list["submit"]
+ close()
return
..()
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index c8c7d5c32e3..1b11a35e58b 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -402,9 +402,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
to_chat(usr, "Not when you're not dead!")
return
- var/area/A = input("Area to jump to", "BOOYEA") as null|anything in ghostteleportlocs
- var/area/thearea = ghostteleportlocs[A]
+ var/datum/async_input/A = input_autocomplete_async(usr, "Area to jump to: ", ghostteleportlocs)
+ A.on_close(CALLBACK(src, .proc/teleport))
+/mob/dead/observer/proc/teleport(area/thearea)
if(!thearea)
return
@@ -425,9 +426,8 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
set desc = "Follow and orbit a mob."
var/list/mobs = getpois(skip_mindless=1)
- var/input = input("Please, select a mob!", "Haunt", null, null) as null|anything in mobs
- var/mob/target = mobs[input]
- ManualFollow(target)
+ var/datum/async_input/A = input_autocomplete_async(usr, "Please, select a mob: ", mobs)
+ A.on_close(CALLBACK(src, .proc/ManualFollow))
// This is the ghost's follow verb with an argument
/mob/dead/observer/proc/ManualFollow(var/atom/movable/target)
@@ -499,25 +499,21 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
set name = "Jump to Mob"
set desc = "Teleport to a mob"
- if(isobserver(usr)) //Make sure they're an observer!
- var/list/dest = list() //List of possible destinations (mobs)
- var/target = null //Chosen target.
+ if(isobserver(usr)) //Make sure they're an observer!
+ var/list/dest = getpois(mobs_only=1) //Fill list, prompt user with list
+ var/datum/async_input/A = input_autocomplete_async(usr, "Enter a mob name: ", dest)
+ A.on_close(CALLBACK(src, .proc/jump_to_mob))
- dest += getpois(mobs_only=1) //Fill list, prompt user with list
- target = input("Please, select a mob!", "Jump to Mob", null, null) as null|anything in dest
-
- if(!target) //Make sure we actually have a target
- return
- else
- var/mob/M = dest[target] //Destination mob
- var/mob/A = src //Source mob
- var/turf/T = get_turf(M) //Turf of the destination mob
-
- if(T && isturf(T)) //Make sure the turf exists, then move the source to that destination.
- A.forceMove(T)
- else
- to_chat(A, "This mob is not located in the game world.")
+/mob/dead/observer/proc/jump_to_mob(mob/M)
+ if(!M)
+ return
+ var/mob/A = src //Source mob
+ var/turf/T = get_turf(M) //Turf of the destination mob
+ if(T && isturf(T)) //Make sure the turf exists, then move the source to that destination.
+ A.forceMove(T)
+ return
+ to_chat(A, "This mob is not located in the game world.")
/* Now a spell. See spells.dm
/mob/dead/observer/verb/boo()
diff --git a/html/browser/autocomplete.js b/html/browser/autocomplete.js
new file mode 100644
index 00000000000..28d3be539c4
--- /dev/null
+++ b/html/browser/autocomplete.js
@@ -0,0 +1,61 @@
+var $ = document.querySelector.bind(document);
+var input;
+var submitButton;
+var optionsMap = {};
+
+function updateTopic() {
+ if (!input || !submitButton) {
+ return;
+ }
+
+ var hrefList = submitButton.getAttribute('href').split(';');
+ // Topic must come last in the submit button for this to work
+ hrefList = hrefList.slice(0, hrefList.length - 1);
+ hrefList.push(optionsMap[input.value] ? 'submit=' + optionsMap[input.value] : '');
+ submitButton.setAttribute('href', hrefList.join(';'));
+}
+
+function clean(name) {
+ // \improper shows up as 2 characters outside of ascii range
+ if (name.charCodeAt(0) > 127) {
+ return name.slice(2);
+ }
+ return name;
+}
+
+function setElements() {
+ input = $('#input');
+ submitButton = $('#submit-button');
+ var choices = $('#choices');
+
+ if (!input || !submitButton || !choices) {
+ return;
+ }
+
+
+ for (var i = 0; i < choices.options.length; i++) {
+ var name = choices.options[i].value;
+ var cleaned = clean(name);
+ optionsMap[cleaned] = name;
+ choices.options[i].value = cleaned;
+ }
+
+ input.addEventListener('keyup', function(event) {
+ if (event.key !== 'Enter') {
+ return;
+ }
+
+ if (Object.keys(optionsMap).indexOf(input.value) === -1) {
+ // Byond doesn't let you to use enter to select
+ // so we need to prevent unintended submissions
+ return
+ }
+
+ submitButton.click();
+ event.preventDefault();
+ });
+
+ input.focus();
+}
+
+window.onload = setElements;