Weapon M Scripting Tutorial
| Author |
Message |
|
Mongoose
Commander
Joined: Mon Oct 29, 2001 3:00 am Posts: 1096 Location: Tucson, AZ
|
 Weapon M Scripting Tutorial
Barring any major complications, Weapon M will be released to the $10 backers in about a week. It'll be available to the public 30 days after that. I figured now is as good a time as any to write a small scripting tutorial. Weapon M's scripts are entirely event-driven. The parser generates ScriptEvents in response to text from the game, then calls the handleEvent(...) method on every loaded script that is registered for that event. In this tutorial, I'll show how to write a script that automatically responds to the "[Pause]" prompt. This is about as simple as a script can get and still be useful. Every script is a class that extends krum.weaponm.script.Script. If you import the package krum.weaponm.script, you don't have to type that part of the name when you refer to classes in that package. It's also a good idea to put your scripts in a package of your own. That way there won't be a conflict if someone else creates a script with the same name. Code: package mongoose;
import krum.weaponm.script.*;
class AutoPause extends Script {
} It's not required, but it'll make your script nicer if you override the getScriptName() and getMenuPath() methods. These control how your script appears in the Scripts menu. The @Override annotation is optional, but it's good practice to use it. It tells the compiler that you're trying to override a method from the superclass. Then if you misspell the method name or something, the compiler will complain. Code: package mongoose;
import krum.weaponm.script.*;
class AutoPause extends Script { @Override public String getScriptName() { return "Auto-Pause"; }
@Override public String getMenuPath() { return "Mongoose"; } } When a script is loaded, three methods are called on it: initScript(), displayParametersDialog(), and startScript(). Every script must implement startScript(), but for this simple script we don't need to do anything with the other two. This is where we'll register for the event the script is interested in. Code: package mongoose;
import krum.weaponm.script.*;
class AutoPause extends Script { @Override public String getScriptName() { return "Auto-Pause"; }
@Override public String getMenuPath() { return "Mongoose"; }
@Override public void startScript() throws ScriptException { registerEvents(ScriptEvent.PAUSE_PROMPT); } } All that's left to do is override the method that handles the event. Because we've only registered for one type of event, the method doesn't need to test what type of event it's handling; we know it'll always be PAUSE_PROMPT. Code: package mongoose;
import krum.weaponm.script.*;
class AutoPause extends Script { @Override public String getScriptName() { return "Auto-Pause"; }
@Override public String getMenuPath() { return "Mongoose"; }
@Override public void startScript() throws ScriptException { registerEvents(ScriptEvent.PAUSE_PROMPT); }
@Override public void handleEvent(ScriptEvent event, Object... params) { try { sendText(" "); } catch(NetworkLockedException e) { // do nothing } } } Notice that sendText(...) can throw a NetworkLockedException. This happens when another script has locked the network so it can send bursts without interference, or when another script has already processed the event we're handling and sent a response of its own. Most of the time, the correct way to handle a NetworkLockedException is to do nothing. The script will get another chance to do its thing the next time the same event comes around. And that's it! To install the script, just drop its .class file into the Weapon's scripts/ directory. This script is called mongoose.AutoPause, so the correct name and location for the class file would be scripts/mongoose/AutoPause.class. Then reload scripts and you should see it appear in the Scripts menu.
_________________ Suddenly you're Busted!
|
| Tue Oct 23, 2012 5:07 am |
|
 |
|
Mongoose
Commander
Joined: Mon Oct 29, 2001 3:00 am Posts: 1096 Location: Tucson, AZ
|
 Re: Weapon M Scripting Tutorial
Here's a slightly more complicated script showing how to handle multiple script events. A typical Weapon M script is basically a state machine. In this example, there are only two states: armed and not armed. A more complicated script might have many states. This script also demonstrates how Weapon M scripts can work together. This script and the AutoPause script both respond to the PAUSE_PROMPT event, but both of them can run at the same time. Each time a PAUSE_PROMPT comes in, only one script will successfully respond to it. The other script will get a NetworkLockedException, which it will ignore. You should never have to silence messages to prevent Weapon M scripts from interfering with each other. Scripts that can't be interrupted should just lock the network until they're finished. Code: package standard;
import javax.swing.JOptionPane;
import krum.weaponm.database.LoginOptions; import krum.weaponm.script.NetworkLockedException; import krum.weaponm.script.Script; import krum.weaponm.script.ScriptEvent; import krum.weaponm.script.ScriptException;
/** * A basic auto-login script. Logs you in only once each time you connect. * Doesn't handle creating new traders or ships. */ public class AutoLogin extends Script { /* * Before any database is loaded, an instance of each script is created to * populate the Scripts menu. Therefore, you have to be careful about * initializing fields. If you initialize a field like this, it'll cause * an error and your script won't be listed in the Scripts menu: * * final LoginOptions options = getDatabase().getLoginOptions(); * * To avoid this problem, use the ternary operator: */ final LoginOptions options = getDatabase() != null ? getDatabase().getLoginOptions() : null; volatile boolean armed; @Override public String getScriptName() { return "Auto-Login"; }
@Override public String getMenuPath() { return "Standard"; } @Override public void startScript() throws ScriptException { registerEvents(new ScriptEvent[] { ScriptEvent.CONNECTING, ScriptEvent.NAME_PROMPT, ScriptEvent.PASSWORD_PROMPT, ScriptEvent.PAUSE_PROMPT, ScriptEvent.GAME_PROMPT, ScriptEvent.SHOW_LOG_PROMPT, ScriptEvent.CLEAR_AVOIDS_PROMPT, ScriptEvent.ACCESS_MODE_LOCKOUT, ScriptEvent.PERMANENT_LOCKOUT, ScriptEvent.DEATH_DELAY_LOCKOUT, ScriptEvent.COMMAND_PROMPT, ScriptEvent.INVALID_PASSWORD }); }
@Override public void handleEvent(ScriptEvent event, Object... params) { if(event == ScriptEvent.CONNECTING) { armed = options.isAutoLogin(); return; } else if(armed) try { switch(event) { case PAUSE_PROMPT: case CLEAR_AVOIDS_PROMPT: case SHOW_LOG_PROMPT: sendText("\r\n"); // carriage return + line feed is the proper network newline break; case NAME_PROMPT: sendText(options.getName() + "\r\n" + options.getGame()); break; case PASSWORD_PROMPT: sendText(options.getPassword() + "\r\n"); break; case GAME_PROMPT: sendText("T\r\n"); break; case ACCESS_MODE_LOCKOUT: case PERMANENT_LOCKOUT: case DEATH_DELAY_LOCKOUT: case COMMAND_PROMPT: armed = false; break; case INVALID_PASSWORD: armed = false; options.setAutoLogin(false); this.showMessageDialog("Invalid password. Auto-login disabled.", "Auto-Login", JOptionPane.ERROR_MESSAGE); break; default: break; } } catch(NetworkLockedException e) { // chill } } }
_________________ Suddenly you're Busted!
|
| Wed Nov 21, 2012 10:31 am |
|
 |
|
Tweety
Boo! inc.
Joined: Fri Jan 04, 2002 3:00 am Posts: 221 Location: Canada
|
 Re: Weapon M Scripting Tutorial
why don't you create a constant for the carriage return so that people don't have to remember /r/n
they can call something like Krum.RETURN_KEY
so if you have a whole bunch of variables, you have to have a whole lot of huge lines like this??
final LoginOptions options = getDatabase() != null ? getDatabase().getLoginOptions() : null; volatile boolean armed;
is there a way to maybe simplify this so that people could get the login options from the database by going
LoginOptions = Database.getloginOptions();
maybe requiring someone to implement a Database include. i don't know just throwing out ideas. your script import deals mostly with script functions. loading, running, etc. then have a database import that pulls data from the actual database.
shrug. figured some input would be nice.
|
| Wed Nov 21, 2012 6:36 pm |
|
 |
|
Mongoose
Commander
Joined: Mon Oct 29, 2001 3:00 am Posts: 1096 Location: Tucson, AZ
|
 Re: Weapon M Scripting Tutorial
Tweety wrote: why don't you create a constant for the carriage return so that people don't have to remember /r/n That's a good suggestion. I'll call it something like Script.RETURN so a script can just refer to it as RETURN. Tweety wrote: is there a way to maybe simplify this so that people could get the login options from the database by going
LoginOptions = Database.getloginOptions(); The problem is that the GUI needs to create an instance of the script to call its getScriptName() and getMenuPath() methods, and at that point there is no database yet. But I could create shortcut methods in Script that hide the ternary logic from the user. For example, instead of getDatabase().getLoginOptions(), a script could just call getLoginOptions(). That would still return null if there's no database, but it wouldn't automatically cause a NullPointerException. Perhaps the most important thing is that I explain this better. Thanks for the input!
_________________ Suddenly you're Busted!
|
| Wed Nov 21, 2012 7:52 pm |
|
 |
|
Mongoose
Commander
Joined: Mon Oct 29, 2001 3:00 am Posts: 1096 Location: Tucson, AZ
|
 Re: Weapon M Scripting Tutorial
Trading is one of the most complicated processes in the game, and obviously one of the most important. I've finally put in all the trading-related script events, and I think they're all working correctly. I'll send out an update to the Kickstarter backers tonight or tomorrow. For everyone else... sorry to leave you hanging so long! I'd planned to have all the basic features finished by now. But there are only a couple more things I need to do before I announce the 30-day countdown to the public release.
Here's a run-down of the script events involved in trading, in the order a script will see them:
1. PLANET_TRADING or SHIP_TRADING indicates the trading type.
2. TRADING_CREDITS appears several times throughout a trading session, before the first product and again after each product discussed.
2. TRADE_INIT_PROMPT is when the port asks, "How many holds of (product) do you want to (buy|sell)?" The parameters indicate what product is being discussed and whether you would be buying it.
3. TRADING_UNITS is when the port says, "Agreed, (number) units." The parameter indicates the number of units.
4. TRADE_OFFER_PROMPT is a port's offer and request for a counter-offer. The parameter indicates the port's offer.
5. FINAL_OFFER indicates that any subsequent TRADE_OFFER_PROMPTs in this transaction are final. (There can be more than one "final" offer if you make a counter-offer that the port takes as a joke.)
6. TRADE_ACCEPTED or TRADE_REJECTED indicates whether the trade completed successfully.
7. If you have a psychic probe, PSYCHIC_PROBE_REPORT appears after TRADE_ACCEPTED and before TRADING_CREDITS. The parameter is the percentage of best price indicated by the probe.
_________________ Suddenly you're Busted!
|
| Sun Nov 25, 2012 9:30 pm |
|
 |
|
Tweety
Boo! inc.
Joined: Fri Jan 04, 2002 3:00 am Posts: 221 Location: Canada
|
 Re: Weapon M Scripting Tutorial
is this all for single scripts? what if I had a script that warped around to different ports, could i then run a trading script inside that other script? or make a custom command? or does it all have to be in one large single script?
That way a writer could write libraries for themselves. Swath does something similar by offering a Command class i believe that deals with text coming in. I was thinking a command class that could be overridden which would allow people to modularize their code some. It would have its own event handling function where you could deal with those events particular to the function you are performing.
Last edited by Tweety on Mon Nov 26, 2012 1:37 am, edited 1 time in total.
|
| Mon Nov 26, 2012 1:00 am |
|
 |
|
Tweety
Boo! inc.
Joined: Fri Jan 04, 2002 3:00 am Posts: 221 Location: Canada
|
 Re: Weapon M Scripting Tutorial
and would stealing product or robbing credits fall into trading events for ports?
|
| Mon Nov 26, 2012 1:12 am |
|
 |
|
Mongoose
Commander
Joined: Mon Oct 29, 2001 3:00 am Posts: 1096 Location: Tucson, AZ
|
 Re: Weapon M Scripting Tutorial
It doesn't all need to be in a single script. You could have an auto-haggle script that recognizes the port prompts and info, and a completely separate script that moves you around and determines where to port and what products to trade. I had things like MoMbot in mind when I designed the scripting system. Scripts can even "require" another script, which will cause it to be loaded if it isn't already... and later cancel the requirement, which will cause the other script to be unloaded if nothing else requires it. The way scripts interact will become clearer as I publish more of them. And yes, there will be events related to stealing and robbing.
You can create your own classes that are callable from scripts. Unlike SWATH's UserDefinedCommands, they don't need to conform to any particular interface. You could even call third-party libraries if you wanted to. I think the main reason SWATH is set up to use Commands the way it does is because the unregistered version limits the number of Commands a script can call before it is interrupted. Since my helper is free, there is no need for any such restriction.
BTW, I've thought of a way to remove the limitation I mentioned before about initializing fields. I just won't populate the Scripts menu until a database is loaded. Scripts can't run without a database, anyway.
Keep the questions coming! I'm going to use this thread as the basis of the project wiki on SourceForge.
_________________ Suddenly you're Busted!
|
| Mon Nov 26, 2012 3:58 am |
|
 |
|
Micro
Ambassador
Joined: Wed Apr 20, 2011 1:19 pm Posts: 2559 Location: Oklahoma City, OK 73170 US
|
 Re: Weapon M Scripting Tutorial
All of this seems extremely complex. Microbot has a much simpler scripting language, and eventually FirstMate will use the same language. Here is a typical script that I would use for a Sycronet BBS: Code: EventHandler.Add("pause", "[Pause]", "\r", 0); EventHandler.Add("mail", "mail now?", "n", 1);
WaitFor("Login:"); Send(data.UserName + "\r");
WaitFor("Password:"); Send(data.PassWord + "\r"); DebugLog("Sent Login\r\n");
WaitFor("Select ("); Send("g13"); DebugLog("Loading TradeWars\r\n");
EventHandler.Clear(); GetBbsStats("1","Game 1");
WaitForIdle(); Send("qq!y"); DebugLog("Done\r\n");
All of this is actually the contents of a method called "PlayScript();" which is a member of the class "public class Script : Interfaces.IScript", but all if this is abstracted from the actual script. All of the commands above are actually members of various classes, but that is also abstracted from the script. GetBbsStats is another script, that is actually pretty complex as it has to log into the game an parse statistics from the "v" screen. GetTwgsStats is used for a TWGS behind a BBS, but no login script is required for a standalone TWGS. I use the EventHandler to respond to "[pause]" because it repeats, and to "mail now?" because it only appears if you have new mail. EventHandler can automatically respond to these simple triggers, and the last paramater is a hitcount that will delete the event when reached (0=unlimited). EventHandler can also be called without a response paramater: Code: EventHandler.Add("CommandPrompt", "command:");
This would initiate a callback when the trigger is received. You can also delete individual events instead if "Clear()": Code: EventHandler.Delete("CommandPrompt");
Strictly speaking, the resulting file is not a script, but it is a .net IScript Interface or "Plug-in" compiled by the .net CodeDom runtime compiler. Since it is .net, you could also use VB or any of the .net languages instead of C#.
_________________ Regards, Micro Website: http://www.microblaster.net TWGS2.20b/TW3.34: telnet://twgs.microblaster.net:2002
ICQ is Dead Jim! Join us on Discord: https://discord.gg/zvEbArscMN
Last edited by Micro on Tue Nov 27, 2012 2:51 pm, edited 1 time in total.
|
| Tue Nov 27, 2012 2:09 pm |
|
 |
|
Micro
Ambassador
Joined: Wed Apr 20, 2011 1:19 pm Posts: 2559 Location: Oklahoma City, OK 73170 US
|
 Re: Weapon M Scripting Tutorial
I should probably add exception handlers to the above script, as WaitFor will throw an exception on timeout: Code: try { WaitFor("Login:"); Send("%username%\r"); } catch { DebugLog("Timeout Error waiting for Login Prompt.\r\n"); return; }
_________________ Regards, Micro Website: http://www.microblaster.net TWGS2.20b/TW3.34: telnet://twgs.microblaster.net:2002
ICQ is Dead Jim! Join us on Discord: https://discord.gg/zvEbArscMN
|
| Tue Nov 27, 2012 2:26 pm |
|
 |
|
Mongoose
Commander
Joined: Mon Oct 29, 2001 3:00 am Posts: 1096 Location: Tucson, AZ
|
 Re: Weapon M Scripting Tutorial
Weapon M scripting uses a completely different paradigm, and I expect that a lot of people are going to have trouble wrapping their heads around it. It's the paradigm that Stein tried to push people toward in later versions of SWATH, but which hardly anyone ever used because the old paradigm was still available. Weapon M scripts are entirely event-driven, similar to a GUI library like Swing. If you block the Swing event thread, your whole GUI will freeze. Likewise, if you block Weapon M's event thread, the network, parser, and scripts will all freeze. So, not only is the waitFor() paradigm intrinsically prone to the kinds of race conditions that plague TWX scripts, but it would be nearly impossible to implement in Weapon M. (Not entirely impossible; it could do something similar to how Swing forks its event thread when it is blocked by a dialog. But it would be extremely complicated...)
I may eventually give scripts the ability to create custom text events, but it would never be as efficient as the built-in lexer and parser. If it's missing something, I want people to tell me so I can add it.
_________________ Suddenly you're Busted!
|
| Tue Nov 27, 2012 3:05 pm |
|
 |
|
Micro
Ambassador
Joined: Wed Apr 20, 2011 1:19 pm Posts: 2559 Location: Oklahoma City, OK 73170 US
|
 Re: Weapon M Scripting Tutorial
This paradigm doesn't seem practical to me. The resulting code looks disjointed and unreadable. Also, I don't see how you are going to create events for every TradeWars Prompt.
Each Microbot script is run in it's own thread, so the blocking call to WaitFor doesn't freeze anything else.
_________________ Regards, Micro Website: http://www.microblaster.net TWGS2.20b/TW3.34: telnet://twgs.microblaster.net:2002
ICQ is Dead Jim! Join us on Discord: https://discord.gg/zvEbArscMN
|
| Tue Nov 27, 2012 4:45 pm |
|
 |
|
Mongoose
Commander
Joined: Mon Oct 29, 2001 3:00 am Posts: 1096 Location: Tucson, AZ
|
 Re: Weapon M Scripting Tutorial
It's very practical for some purposes. Most windowing toolkits use the event-driven paradigm. It's also how lexers and parsers work, such as programming laguage compilers, SAX parsers for XML, or the HTML parser in your browser. I realized that the incoming text from TW is an ideal candidate for event-driven parsing. It allows any number of scripts to receive each event, which is pretty awsome. The paradigm does take some getting used to, though. And I've seen a lot of programs do it wrong. For years, the leading free Java terminal component has been de.mud.Terminal. But it's very slow, mainly because it doesn't use Java's passive rendering properly. I had to roll my own event-driven terminal system. With my terminal, how often the screen actually updates depends on your graphics hardware, but that doesn't affect how quickly a program can write to it. I benchmarked writes at over a million characters per second on my gimpy netbook. Micro wrote: I don't see how you are going to create events for every TradeWars Prompt. It's a tedious pain the butt. But I've already created events for many of them. And the cool thing about lexers is that they can scan for any number of events in a single pass. It doesn't matter if I have ten events or a thousand, it will not slow down the lexer. By contrast, at the heart of TWX is a routine that examines each line with if-else logic. It's looking for a few dozen different text triggers, so it has to do a few dozen string comparisons on every single line of text. That's enormously inefficient. As fast as it is, TWX could be many times faster if it used a lexer. (Not that it would matter a whole lot, since network latency is a much bigger factor than helper speed...)
_________________ Suddenly you're Busted!
|
| Tue Nov 27, 2012 7:27 pm |
|
 |
|
Micro
Ambassador
Joined: Wed Apr 20, 2011 1:19 pm Posts: 2559 Location: Oklahoma City, OK 73170 US
|
 Re: Weapon M Scripting Tutorial
I'm afraid i don't know much about lexers, except what I just dread on wikipedia ( http://en.wikipedia.org/wiki/Lexical_analysis). How does this help with parsing the Tradewars text stream?
_________________ Regards, Micro Website: http://www.microblaster.net TWGS2.20b/TW3.34: telnet://twgs.microblaster.net:2002
ICQ is Dead Jim! Join us on Discord: https://discord.gg/zvEbArscMN
|
| Tue Nov 27, 2012 7:43 pm |
|
 |
|
Mongoose
Commander
Joined: Mon Oct 29, 2001 3:00 am Posts: 1096 Location: Tucson, AZ
|
 Re: Weapon M Scripting Tutorial
The lexer identifies what each interesting piece of text is (the token type) and then the parser extracts the useful data from it. For example, whenever the parser scans a Command prompt, it passes the matching text to the parser's commandPrompt() method, which extracts the sector number and time left and fires the appropriate script events.
Most lexers assume that every byte of input should be part of a token, and produce an error if the input contains anything unexpected. But I wrote JPlex so all the junk text is ignored in the innermost loop and only the interesting patterns generate events.
The big payoff of this approach is that scripts can be version-agnostic. If I write the lexer pattern for a Command prompt so that it matches both v1 and v2, and write the parser so it handles both, then to a script, a Command prompt is a Command prompt regardless of any little differences in the ANSI or whatever. In theory, I should be able to support v1, v2, and HVS, and the same scripts will run on all of them without changes.
_________________ Suddenly you're Busted!
|
| Tue Nov 27, 2012 8:17 pm |
|
 |
|
Who is online |
Users browsing this forum: No registered users and 12 guests |
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot post attachments in this forum
|
|