Originally created by @ciastex_ and @i20k, slightly edited and updated by @ryo
Special thanks to @dtr/@sudo, @ada and @soron for their expertise.
This documentation is a mirror of the community's Scripting Tutorial (Google Docs). If this document becomes out of date, check there for more recent information.
If you have any questions, don't hesitate to hit us on Discord: https://discord.gg/sc6gVse
Command: #edit <filename>
This command will create or edit a script, opening it up with your default text editor.
If you are on Windows this may crash, as the default .js command on Windows is the system built-in compiler.
To fix this issue, associate your text editor with the .js file extension. If you don’t have a text editor, get one like Notepad++.
Scripts will be created with a default template.
Command: #dir
This command simply opens up your script directory.
You can create new scripts here and them upload them ingame.
Command: #up <filename>
This command will upload your created script to the server, so you can execute it.
Possible arguments AFTER the filename:
- delete: will delete your script from the server, but leave it locally.
- public: will make your script public - assuming you have the public slot upgrade installed and loaded within your system.
- private: will explicitly mark a script as private (useful to un-public a script while debugging, for example)
- shift: will 'shift' your script. This is necessary if the security level changes, or you make it public for the first time. Takes approximately 15 minutes, during which you cannot interact with the script.
Command: #down <filename>
This command will download an already uploaded script from the server.
You could use this to download a script to another machine.
Command: #delete <filename>
This command will remove your script from your computer’s file system, which means you won’t be able to access it from your editor anymore.
Be careful around this command, though - it runs without any confirmation.
Command: #scripts
or simply #
This command will list all your local and uploaded scripts.
To see your currently uploaded scripts, run scripts.user
Command: #help
This command will print the in-game architect commands help.
Scripts in hackmud are JavaScript (ES6 strict mode) files consisting of a single function with 2 arguments passed by the game:
- context - This is a context the script is run from, i.e. if a user called noob ran your script, then any command executed from context will be treated as executed by the user who executed it, just like he/she would write them in their command line. Context has the following subkeys:
-
context.caller
- The name of the user who is calling the script (i.e. n00b)
-
context.this_script
- The name of this script
-
context.calling_script
- The name of the script that directly called this script, or null if called on the command line
-
context.cols
- The number of columns in the caller's terminal, if reported by the client.
args
- This is a dictionary containing all the arguments a user passed to your script.
If the script was called without any arguments (i.e.foo.bar
), args will be null. If called with empty arguments (i.e.foo.bar {}
), args will be an empty JS object.
Hackmud does not count whitespace characters (space, tab, carriage return, newline, vertical tab, and possibly a few others), and does not count // comments. All other forms of comments are counted.
Hackmud ignores // comments by replacing // through the end of a line with the empty string. The parser is not smart enough to know that // inside a string isn’t a comment.
Thus,
var x="http://google.com"
will get mangled -- hackmud will truncate the line to
var x="http:
which will cause a syntax error. The easiest fix is to use // anywhere you want // to appear.
Scriptors are one of the hackmud specific features.
They allow you to call an in-game script from your script. That allows you to parametrize your script’s behavior.
The scriptor syntax is as follows:
#s.user.script
The above can be then passed to your script as an argument, like the following (remember to upload the script!):
crk_ez21 { target:#s.user.command }
To call a command the scriptor points to, there’s a scriptor-specific method which optionally accepts your arguments that will be passed to the called command:
args.target.call({/* optional arguments for the called scriptor */})
If you want to call a hard-coded script (ed note: this isn’t technically a scriptor, it is just a script call), you can do so without using a scriptor, as follows.
Be aware, you cannot store a script to a variable like this:
var x = #fs.user.name
As #s is really a preprocessing directive. #fs.user.name must be used immediately, in the form
#fs.user.name({key:value})
If you want to hard-code a script call that you can reuse, define a wrapper function, like:
function foo(args) {
return #fs.user.name(args);
}
As of 20/10/2017, using the syntax #s.user.script
will result in an error while uploading. The syntax for scriptors is still #s.user.script, however.
The new syntax is as follows:
#fs.user.script
OR #4s.user.script
Sec levels go from 0 (NULLSEC) to 4 (FULLSEC) or ns, ls, ms, hs and fs. These guarantee that the calling script is at least of that sec level.
Many people want to take a string, like a loc from an NPC corp, and call it directly inside another script.
This is, deliberately, impossible in hackmud.
If you could convert a string into a callable in any way, the entire security level system would fall apart (because any string in any dependency could possibly be a nullsec script. And those strings could come from the database).
If you want to do something with those locs (or similar cases), you will have to pass them in as a scriptor or hard-code them in the file.
You cannot call the string directly.
A called script can return basically anything - an array, a string, an object, or even null.
Most scripts in the game however simply return a string.
- Your script itself generally returns both {ok:true, msg:"string"}
- The contents of string will automatically be printed to your terminal
- Both of these arguments are optional, and while you may get an error message if you return nothing from a script, it will still work fine.
To add autocomplete args to your script, on the first line, after the function boilerplate, add a comment with a list of args and values, like this:
function(context, args) { // arg1:val1, arg2:val2, arg3:#s.an.example, arg4:”example”
var myArg = args.arg1;
…
}
After uploading the script, you might need to run scripts.user to update your autocomplete, and then it should work.
If #D is called in a script you own, the return value of the top level script is suppressed and instead an array of every #D’d entry is printed. This lets you use #D kind of like console.log. #D in scripts not owned by you are not shown. #D returns its argument unchanged, so you can do things like return #D(ob) to return the object when the caller isn’t you, and debug-log it when it is you (allowing you to “keep” your returns with other debug logs). #D’d items are returned even if the script times out or errors.
#FMCL is what escrow.charge uses to ensure it is only called once per script execution. The first time (in each script) that #FMCL is encountered, it returns falsey, and every time thereafter it returns truthy. A common usage pattern is
if(#FMCL)
return "error"
// do work
The first time that block of code is hit, it will do work, and every time after it will return the error (this applies even if (and specifically for the case where) your script is called multiple times in the same execution)
#G -- Global
#G is a per-script global object. It starts out blank, and you can add whatever properties you want to it. If your script is called multiple times in a single overall script run, its #G is persisted between those calls (but each script sees its own #G). This is useful to cache db lookups that won’t change in scripts that expect to be called many times. Sample usage:
if(!#G.my_db_entry)
#G.my_db_entry=#db.f({whatever:true}).first();
// use #G.my_db_entry in code below
This contains a JS timestamp (not Date) set immediately before your code begins running. You can see how much time remains by doing Date.now()-_START
This contains the number of milliseconds a script is allowed to run for. Effectively always just 5000, except when a trust script is called on the command line and its value is, presumably, 8000.
Macros are fairly simple, and very useful in hackmud. This is not strictly coding related, but they are not that widely known. Example:
/macroname = test{target:"canhavefixedarguments"}
/hl = kernel.hardline
/dc = kernel.hardline{dc:true}
Running /macroname or /hl or /dc will run exactly that command.
Macro with arguments
/fullsec = scripts.fullsec{{start:{0}}}
Notice that you need {{}}
(double brackets), call like /fullsec 128
You need to replace every "real" {} with {{}}
Each users’ database in hackmud is a MongoDB collection, in which data is stored as JSON documents.
Query Objects:
Query Objects are a regular JSON object containing keys and values you want to search against.
Projections:
Projections allow you to fetch specific subfields in a #db object. These speed things up quite a bit if your document is large.
Check https://docs.mongodb.com/v3.0/tutorial/project-fields-from-query-results/ for more information.
#db.i()
Mongodb documentation on inserting
This command creates new #db documents.
Called like #db.i(<JSON object or array of JSON objects>);
Ex: #db.i({ SID:”scriptname” })
Inserts a document with key “SID” and value “scriptname”
#db.r()
Mongodb documentation on removing
This command deletes #db documents matching your query.
Called like #db.r({query})
;
Ex: #db.r({ SID:”scriptname” })
removes all documents where key “SID” contains the value “scriptname”.
#db.f()
Mongodb documentation on finding
This command returns any documents matching your query.
Called like #db.f({query}, {projection}).command()
where “command” is either “first” or “array”/
Ex: #db.f({ SID:”scriptname” }).array()
returns an array of documents where key “SID” contains the value “scriptname”.
Ex: #db.f({ SID:”scriptname” }, { field:1, _id:0 }).first()
returns the value for the key “field” inside the first document it finds where key “SID” contains the value “scriptname”.
#db.u()
Mongodb documentation on updating
This command updates any pre-existing documents matching the query.
Called like #db.u({query}, { updateOper:{updatedfields} })
applies “update” to any documents matching the query.
Ex: #db.u({ SID:”scriptname” }, { $set:{field:”new value”} })
sets key field to “new value” in any documents where key “SID” contains the value “scriptname”.
This can be a very complex operation. It is HIGHLY recommended you follow the aforementioned hyperlink.\
The chat api wrapper allows you to read and send messages through the users you own.
You will need to get the chat_pass
from the game. (running chat_pass
).
The API contains different classes:
var API = require('./chat.js')
var acct = new API.Account();
acct.login("token or pass").then((data) => {
// data is a account object
// means it contains the users, token, last and the methods...
var username = data.users.username;
})
Method name | Arguments | Description |
---|---|---|
Account | last=null | The Account constructor |
login | pass | Method used to login |
update | token | Updates the token |
poll | ext={} | Polls the chat |
Prints the account details. |
Properties | Description |
---|---|
users | Array object of the users you own. |
token | String storing your token. |
last |
Note: the Account class generates the users using this class.
Method name | Arguments | Description |
---|---|---|
User | (account, name, dat) | The User constructor |
tell | (to, msg) | Sends a private message to the user "to" |
Prints the user details. |
Properties | Description |
---|---|
account | Object that stores the account of the user. |
name | Name of the user. |
channels | Object Array containing the channels the user is in. |
Method name | Arguments | Description |
---|---|---|
Channel | (user, name, users) | Channel constructor. |
send | (msg) | Sends the "msg" to the channel |
Prints the channel details |
Properties | Description |
---|---|
user | Your user |
name | The name of the channel |
users | Array containing the users of the channel. |
var API = require('./chat.js');
var acct = new API.Account();
acct.login("token or pass").then((data) => {
...
})
var API = require('./chat.js');
var acct = new API.Account();
acct.login("token or pass").then((data) => {
var channel = data.users.username.channels["0000"];
channel.send("Hello world!");
})
var API = require('./chat.js')
var acct = new API.Account();
acct.login("token or pass").then((data) => {
data.poll().then(o => {
console.log(o.chats.user); // Replace user with your username
})
})
This is a code library containing useful helper functions you can use in your scripts.
Function name | Arguments | Returns | Description | Example (var l = #fs.scripts.lib(); ) |
---|---|---|---|---|
ok | none | {ok:true} |
Helper method to save chars. | return l.ok(); |
not_impl | none | {ok:false, msg:"not implemented"} |
Helper method to save chars. | return l.not_impl(); |
log | (message) | nothing | Pushes a string representation of a value onto an array of log messages. You have to use the method below to print it. | l.log("hackmud is the best hacking game!") |
get_log | none | Array containing the stored logs | Gets the stored logs, Does not clone or clear the array afterwards; it's a direct reference to the same array. | var log_arr = l.get_log(); |
rand_int | (min, max) | integer | Returns a random integer between min/max | var r = l.rand_int(0, 10); |
are_ids_eq | (id1, id2) | boolean | Tests whether id1 and id2 values are equal. Apparently buggy at the moment. | |
is_obj | (what) | boolean | Returns true if the what is a object. (Arrays are objs too) | var is_obj = l.is_str({name:"ryo", gc:"1TGC"}); |
is_num | (what) | boolean | Returns true if the what is a Number. (This treats NaN (not a number) as not a number, even though in JS, typeof NaN == "number".) | var b = l.is_num(2); |
is_int | (what) | boolean | Returns true if what is is both a Number (via is_num), and also an integer. | var b = l.is_int(3); |
is_neg | (what) | boolean | Returns true if what is is both a Number (via is_num), and also negative (i.e. <0) | var b = l.is_neg(-3); |
is_arr | (what) | boolean | Returns true if what is an array | var b = l.is_arr([2,3,4]); |
is_arr | (what) | boolean | Returns true if what is a function | var b = l.is_func(my_func); |
is_def | (what) | boolean | Returns true if what is defined so, NOT undefined. Note: null != undefined | var b = l.is_def(my_var); |
is_valid_name | (what) | boolean | Returns true if what is a valid user/script name. | var b = l.is_func(my_func); |
dump | (obj) | string | Returns a string representation of the obj argument. | return l.dump(my_obj); |
clone | (obj) | object | Returns a clone of the obj argument (meaning references are broken). | var cln_obj = l.clone(org_obj); |
merge | (obj1, obj2) | nothing | Merges the contents of obj2 into obj1. This can be useful for combining defaults with user-specified values, but it is not quite secure on its own (i.e. don’t trust it to secure DB filters). | |
get_values | (obj) | array | Returns an array containing the values of the object properties. | var value_arr = l.get_values(my_obj); |
hash_code | (string) | number | Returns a number calculated based on the string argument. | var hash = l.hash_code("test"); |
to_gc_str | (num) | string | Converts raw num number to a GC currency representation. | var gc_string = l.to_gc_str(100); |
to_gc_num | (str) | number | Converts raw num number to a GC currency representation. | var gc_num = l.to_gc_num("10MGC"); |
to_game_timestr | (date) | string | Converts a Date object specified via date parameter to a game-styled time string. | var date_str = l.to_game_timestr(new Date()) |
cap_str_len | (string, length) | strring | Truncates the given string to the given length if it's longer than that. | |
each | (array, fn) | array | Runs fn on each array element. | var new_arr = l.each(old_arr, (key, val) => {...}); |
select | (array, fn) | array | Returns a collection of values from array that matches the fn predicate. If the predicate returns true, the select function adds the key:value pair currently processed to the returned collection of values. | var new_arr = l.select(old_arr, (key, val) => val % 2 == 0 ? true : false); |
count | (array, fn) | number | Returns a number of items from array that matches the fn predicate. If the predicate returns true, the count function increments the returned number by one. | |
select_one | (array, fn) | array | Same as the select function, but returns the first value that matches the predicate. | |
map | (array, fn) | array | Creates a new array with the results of calling a provided function on every element in the calling array. | var new_arr = l.map(old_arr, x => x*2); |
shuffle | (array) | array | Shuffles an array and returns it. | |
sort_asc | (one, two) | array | If one > two, returns 1. If two is greater than one, return -1. Else return 0. Looks like a sorting function | |
sort_desc | (one, two) | array | Returns the opposite of the above, ie -1 on one > two, and 1 on two > one | |
num_sort_asc | (...?) | |||
num_sort_desc | (...?) | |||
add_time | (date, add_ms) | date | Gets the date of date + add_ms (milliseconds) | return l.add_time(new Date(), 4000); |
security_level_names | It's not a function, array containing the security levels (NULLSEC, LOWSEC, MIDSEC, HIGHSEC, FULLSEC) | var nullsec = l.security_level_names[0]; |
||
get_security_level_name | (level) | string | Takes a parameter between 0 and 4 (inclusive), returns the corresponding security from NULLSEC (0) to FULLSEC (4) | |
create_rand_string | (len) | string | Returns a random string consisting of lowercase alphanumeric characters. | |
get_user_from_script | (script_name) | string | Returns the user from a script name. Ie me.target returns me | |
u_sort_num_arr_desc | ||||
can_continue_execution | (ms) | boolean | Returns true whether the script can still be on execution in the provided time. | l.can_continue_execution(1000); |
can_continue_execution_error | ||||
can_continue_execution_error | ||||
date | ||||
get_date | date | Gets the current date | ||
get_date_utcsecs | number | Gets the current time from the date (ie Date.getTime()) |