Scripts with user interfaces

jasonharper

Developer
r8318 adds some experimental features for giving scripts a HTML-based graphical user interface, that appears in the relay browser. This isn't actually a new capability; the basic idea is simply a relay override script that doesn't actually override any existing KoL page, so it can create a page from scratch. However, the capability has been mostly ignored, probably due to various inconveniences in actually using it - such as lack of any easy way to actually access such a page. (You'd have to have a second override script, to add a link to your page from some existing page!)

r8318 solves that problem by collecting relay scripts with names that start with "relay_" (which are unlikely to ever match an in-game page) into a menu in the top pane. It also adds some ASH features for more conveniently dealing with HTML fields and text.

This isn't going to obsolete other forms of mafia scripting - relay scripts aren't really suitable for long, ongoing processes such as autoadventuring, since nothing appears in the browser until the script exits. Also, writing them requires a rather different mindset than the sequential execution of a normal script: if the relay script presents options for the user, they are actually read by an entirely separate invocation of the script than the one that displayed the options. The areas in which relay scripts will be the most useful are user-friendly configuration of other scripts, and presentation of options for the user to carry out in the relay browser themselves (since the script can simply link to any in-game activity).

I've provided one useful example of a relay script already: Breakable equipment fine tuning. Here's another one, more useful to script writers: it allows you to type in HTML code, and immediately see what it produces in the browser, and the form field values that it would submit.
PHP:
void main()
{
	string[string] fields = form_fields();
	string scr;
	write("<html><body><form method=POST action=\"");
	write(__FILE__);
	writeln("\">");
	if (fields contains "__scriptcode__") {
		scr = entity_decode(fields["__scriptcode__"]);
		remove fields["__scriptcode__"];
		writeln("Form fields detected:<br>");
		writeln("<table border=2>");
		writeln("<tr><td>Name:</td><td>Value:</td>");
		foreach key, val in fields {
			write("<tr><td>");
			write(entity_encode(key));
			write("</td><td>");
			write(entity_encode(val));
			writeln("</td></tr>");
		}
		writeln("</table>");
	} else {
		writeln("This script allows you to quickly test HTML code, and see exactly what values are submitted by form elements.");
		scr = "<input type=radio name=rb value=23 checked>this<br>\n<input type=radio name=rb value=42>that<br>\n<input type=checkbox name=cb checked>other<br>";
	}
	writeln("<p>");
	writeln(entity_encode("Edit your code here.  Don't include <html>, <body>, or <form> tags, as the script provides them."));
	writeln("<br><textarea name='__scriptcode__' rows=15 cols=80>");
	write(entity_encode(scr));
	writeln("</textarea>");
	writeln("<br><input type='submit' value='exec'> Your results will appear below:</p>");
	writeln(scr);
	writeln("</body></html>");
}

I'm planning on writing some further tools to assist in relay script writing.

EDIT: Oops, I uploaded a version of this script where I'd temporarily changed to method=GET for testing, instead of method=POST which is generally a better idea. It's a simple change on line 5 if you already downloaded the script.
 

Attachments

Last edited:
This could certainly be a useful addition to scripts with a lot of configurable options. I'm sure there are uses that haven't vaguely occurred to me yet.

It's... interesting.
 
I was already doing this for my clannies! I replaced the never-used clan war room with a link to the clan Treasury (nonexistent clan_treasury.php, overridden by existing clan_treasury.ash), where people can see their current credit score, send items/meat to the Treasury, see progress toward clan goals, etc. I thought I was quite clever at the time. :)

I foresee this as the future of adjusting ZLib settings.
 
Wow, I was going to post this in the SVN thread but decided to look around first... This is awesomesauce! I'm going to need to go back and clean up customdaily.ash & it's relay override script to make use of this (somehow, I'd totally overlooked the existing form_field() function). It's probably going to be a bit before I can actually play around with it, but this looks like exactly what I was wishing existed back when I originally wrote the aforementioned scripts! Thanks!
 
It would be handy if there was an ASH function to query the request method. You would have to expose it on the RelayBrowser I think (from HttpURLConnection.getRequestMethod).
 
General web programming style would be to only apply changes on POST, not GET. Currently the best you can do is check for the URL argument of the submit button.
 
This isn't "general web programming", this is a feature for adding a GUI to scripts. The only likely place a relay script is going to be receiving parameters from is ITSELF (via a form it wrote to the browser, during the previous invocation); presumably you can remember whether you used POST or GET.

In the more general case of a relay override that's modifying an in-game form, the POST vs. GET choice depends entirely on what the game expects for that particular form. If there's a conflict between KoL and good style, good style utterly and completely loses.
 
Thank you, that's awesome. I'm not very familiar with html, so I'm learning a lot as I try to figure out how to set this up for Universal recovery settings. Your example helps.
 
Thank you, that's awesome. I'm not very familiar with html, so I'm learning a lot as I try to figure out how to set this up for Universal recovery settings. Your example helps.

Between jason's examples and mine, I would be interested to know if any of the various HTML output helpers were useful to you. Might be worth pulling some of them into an include.
 
Well, I am using Jason's write_option() for fun stuff like this to mimic mafia's preferences:

Code:
void numeric_dropdown(string pref, string fail, string message, string [string] fields) {
	if(fields contains pref)
		set_property(pref, fields[pref]);
	string current = get_property(pref);
	writeln("<td><select name='" + pref + "'>");
	if(fail != "") {
		write_option(fail, "-0.05", current);
		write_option(message+ "0%", "0.0", current);
	}
	foreach i in $ints[5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
		write_option(message+ i +"%", to_string(i / 100.0), current);
	if(fail == "")
		write_option(message+ "100%", "1.0", current);
	write("</select></td>");
}

writeln("<tr><td>Restore your Health</td><td>Restore your Mana</td></tr>");
write("<tr>");
numeric_dropdown("hpAutoRecovery", "Do not auto-recover health", "Auto-recover health at ", fields);
numeric_dropdown("mpAutoRecovery", "Do not auto-recover mana", "Auto-recover mana at ", fields);
writeln("</tr>");
write("<tr>");
numeric_dropdown("hpAutoRecoveryTarget", "", "Try to recover health up to ", fields);
numeric_dropdown("mpAutoRecoveryTarget", "", "Try to recover mana up to ", fields);
writeln("</tr>");

Now I'm gonna get started on those checkboxes using your work as a reference. What is the purpose of write_hidden() ?
 
Last edited:
A hidden field would let you save variables between reloads of the script, without having to save them somewhere really persistent (like a preference). Keep in mind that a relay script is being run from scratch every time you click a submit button in it...

I'm planning on writing a complete library of functions for generating the basic input elements, including niceties like field value validation. A "phial of HTMLform", so to speak... You might not want to get too involved in the low-level details right now, as you'll largely be able to ignore them later.
 
Then perhaps I'll put off further work on my UI for UR until I can use a phial of HTMLforum to save myself some trouble. Just one thing before I shelve this...

@coderanger, I tried using your style for a nice KoL-like appearance, but it insisted on boxing in every single element of my table. Is there some simple way to fix this?

ui1.png


Code:
void write_option(string label, string value, string current) {
	write("<option value='" + value + "'");
	if(value == current)
		write(" selected");
	writeln(">" + entity_encode(label) + "</option>");
}

void numeric_dropdown(string pref, string fail, string message, string [string] fields) {
	if(fields contains pref)
		set_property(pref, fields[pref]);
	string current = get_property(pref);
	writeln("<td><select name='" + pref + "'>");
	if(fail != "") {
		write_option(fail, "-0.05", current);
		write_option(message+ "0%", "0.0", current);
	}
	foreach i in $ints[5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
		write_option(message+ i +"%", to_string(i / 100.0), current);
	if(fail == "")
		write_option(message+ "100%", "1.0", current);
	write("</select></td>");
}

void options() {
	writeln("<table border=0>");
	string[string] fields = form_fields();
	
	write("<tr><td align=right>Stop Automation</td>");
	numeric_dropdown("autoAbortThreshold", "Stop if auto-recovery fails", "Stop if health at ", fields);
	writeln("</tr>");
	writeln("<tr><td> </td></tr>");
	writeln("<tr><td>Restore your Health</td><td>Restore your Mana</td></tr>");
	write("<tr>");
	numeric_dropdown("hpAutoRecovery", "Do not auto-recover health", "Auto-recover health at ", fields);
	numeric_dropdown("mpAutoRecovery", "Do not auto-recover mana", "Auto-recover mana at ", fields);
	writeln("</tr>");
	write("<tr>");
	numeric_dropdown("hpAutoRecoveryTarget", "", "Try to recover health up to ", fields);
	numeric_dropdown("mpAutoRecoveryTarget", "", "Try to recover mana up to ", fields);
	writeln("</tr>");
	
	writeln("</table>");
}

void write_header() {
	writeln("<!DOCTYPE html>");
	writeln("<html>");
	writeln("  <head>");
	writeln("    <title>Universal Recovery settings</title>");
	writeln("    <link href=\"http://images.kingdomofloathing.com/styles.css\" type=\"text/css\" rel=\"stylesheet\" />");
	writeln("    <style type=\"text/css\">");
	writeln("table { border-spacing: 0; margin: auto; }" +
		"td { border: 1px solid blue; padding: 1px; }"  +
		"tr.header { background-color: blue; color: white; text-align: center; font-weight: bold; }" +
		"tr.header td { border: 0; padding: 0; }" +
		"div.input { margin: 0.3em 0; clear: both;}" + 
		"div.input label { float: left; text-align: right; width: 14em; margin-right: 1em;}" +
		"div.input input { padding: 0.15em; }" );
	writeln("    </style>");
	writeln("  </head>");
	writeln("  <body>");
}

void main() {
	write_header();
	write("<center><input type=submit value='Save changes'>");
	writeln(" to <a href='http://kolmafia.us/showthread.php?1780' target=\"_blank\">Universal Recovery</a>.</center></p>");
	writeln("<table><tr class=\"header\"><td>Universal Recovery settings</td></tr><tr><td><form method=POST action='"+ __FILE__ +"'>");
	options();
	writeln("</form></td></tr></table>");
	writeln("  </body>");
	writeln("</html>");
}

I suppose my ignorance is amazing, eh? Please teach me. I'm looking up lots of stuff online, but I'm struggling a bit.
 
Try this on for size

Code:
void write_option(string label, string value, string current) {
	write("<option value='" + value + "'");
	if(value == current)
		write(" selected");
	writeln(">" + entity_encode(label) + "</option>");
}

void numeric_dropdown(string pref, string fail, string message, string [string] fields) {
	if(fields contains pref)
		set_property(pref, fields[pref]);
	string current = get_property(pref);
	writeln("<td><select name='" + pref + "'>");
	if(fail != "") {
		write_option(fail, "-0.05", current);
		write_option(message+ "0%", "0.0", current);
	}
	foreach i in $ints[5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
		write_option(message+ i +"%", to_string(i / 100.0), current);
	if(fail == "")
		write_option(message+ "100%", "1.0", current);
	write("</select></td>");
}

void options() {
	writeln("<table border=0>");
	string[string] fields = form_fields();
	
	write("<tr><td align=right>Stop Automation</td>");
	numeric_dropdown("autoAbortThreshold", "Stop if auto-recovery fails", "Stop if health at ", fields);
	writeln("</tr>");
	writeln("<tr><td> </td></tr>");
	writeln("<tr><td>Restore your Health</td><td>Restore your Mana</td></tr>");
	write("<tr>");
	numeric_dropdown("hpAutoRecovery", "Do not auto-recover health", "Auto-recover health at ", fields);
	numeric_dropdown("mpAutoRecovery", "Do not auto-recover mana", "Auto-recover mana at ", fields);
	writeln("</tr>");
	write("<tr>");
	numeric_dropdown("hpAutoRecoveryTarget", "", "Try to recover health up to ", fields);
	numeric_dropdown("mpAutoRecoveryTarget", "", "Try to recover mana up to ", fields);
	writeln("</tr>");
	
	writeln("</table>");
}

void write_header() {
	writeln("<!DOCTYPE html>");
	writeln("<html>");
	writeln("  <head>");
	writeln("    <title>Universal Recovery settings</title>");
	writeln("    <link href=\"http://images.kingdomofloathing.com/styles.css\" type=\"text/css\" rel=\"stylesheet\" />");
	writeln("    <style type=\"text/css\">");
	writeln("table { border-spacing: 0; margin: auto; }" +
		"td.box { border: 1px solid blue; padding: 1px; }"  +
		"tr.header { background-color: blue; color: white; text-align: center; font-weight: bold; }" +
		"tr.header td { border: 0; padding: 0; }" +
		"div.input { margin: 0.3em 0; clear: both;}" + 
		"div.input label { float: left; text-align: right; width: 14em; margin-right: 1em;}" +
		"div.input input { padding: 0.15em; }" );
	writeln("    </style>");
	writeln("  </head>");
	writeln("  <body>");
}

void main() {
	write_header();
	write("<center><input type=submit value='Save changes'>");
	writeln(" to <a href='http://kolmafia.us/showthread.php?1780' target=\"_blank\">Universal Recovery</a>.</center></p>");
	writeln("<table><tr class=\"header\"><td>Universal Recovery settings</td></tr><tr><td class=\"box\"><form method=POST action='"+ __FILE__ +"'>");
	options();
	writeln("</form></td></tr></table>");
	writeln("  </body>");
	writeln("</html>");
}

I threw a class on the blue-line-box <td> so all the inner ones don't use that style too. KoL's styling is a bit erratic, I'll see if I can make something simpler tomorrow.
 
This is too cool. I really wish I had time to play with this!

Not sure of the extent of your plans Jason, but a prepopulated input for each mafia type would be a nice touch, and would make data validation a little less likely to make noise.
 
Thanks coderanger, it looks much nicer now. Thanks for the lesson! I guess I'll put off the rest while Jason does his thing. I really need to learn more html 'cause the coding possibilities are awesome.
 
Back
Top