I realized that failed explore-mode authorization on a special-mode saved game cannot downgrade the game mode further down to a normal game, because this would dump the player back into a state where she has completed some part of the game in explore mode but is eligible for the topten list. This is even more true when the game was formerly a wizard-mode game. Unforunately, that was the state my previous commits left the game in. Instead, if restoring an explore-mode or wizard-mode savegame, and the player is authorized via sysconf for neither of those modes, fail restoration entirely and start a new game instead. That's sort of clunky and there could probably be more explanation provided, but it should be an exceedingly rare occurance and I'm not sure what alternative exists that would still honor the EXPLORERS and WIZARDS restrictions. This shouldn't affect the way they default 'down a mode' in other circumstances, i.e. the overwhelming majority of situations in which EXPLORERS authorization is needed/checked. For the same reason, I realized that the player can't be prompted whether or not to enter explore mode, if being downgraded from a no-longer-authorized wizmode save while explore mode is authorized. The change from wizard mode to explore mode must be mandatory. I have also switched that up so that it will force the change -- unfortunately, this has the side effect of allowing the preservation of the save, but it's more important to make sure a wizard mode game doesn't get reverted to normal mode. They won't be able to load the save into wizard mode anyway.
About
This creates a library for NetHack that can be incorporated into other programs. There are two different libraries that are currently available:
- libnethack.a - a binary Unix library
- nethack.js / nethack.wasm - a WebAssembly / WASM library for use in JavaScript programs (both nodejs and browser)
Build
This library has only been built on MacOS, but should work on Linux and other unix-ish platforms. If you have problems, start by stealing hints files from the sys/unix/hints for your platform. Contributions for other platforms are happily accepted.
Building the WASM module requires that you have the emscripten toolchain / sdk installed.
Generally the build is the same as the unix build:
[Edit Oct 4, 2020: Use the existing Makefile and hints, hints/include system for cross-compiles]
cd sys/unix./setup.sh hints/macOS.370cd ../..- For
libnethack.a:make WANT_LIBNH=1 all - For
nethack.js:make CROSS_TO_WASM=1 all
[Original text was:]
cd sys/lib- For
libnethack.a:./setup.sh hints/macOS.370; fornethack.js:./setup.sh hints/wasm cd ../..make
[Edit Oct 4, 2020:]
Resulting libaries will be in the targets/wasm directory for CROSS_TO_WASM=1.
Resulting libaries will be in the src directory for WANT_LIBNH=1.
[Original text:]
Resulting libaries will be in the src directory.
WASM also has a npm module that can be published out of sys/lib/npm-library. After building the nethack.js it can be published by:
cd sys/lib/npm-librarynpm publish
API: libnethack.a
The API is two functions:
nhmain(int argc, char *argv[])- The main function for NetHack that configures the program and runs themoveloop()until the game is over. The arguments to this function are the command line arguments to NetHack.shim_graphics_set_callback(shim_callback_t cb)- A single function that sets a callback to gather graphics events: write a string to screen, get user input, etc. Your job is to pass in a callback and handle all the requested rendering events to show NetHack on the scrren. The callback isvoid shim_callback_t(const char *name, void *ret_ptr, const char *fmt, ...)nameis the name of the window function that needs to be handledret_ptris a pointer to a memory space for the return value. The type expected to be returned in this pointer is described by the first character of thefmtstring.fmtis a string that describes the signature of the callback. The first character in the string is the return type and any additional characters describe the variable arguments:ifor integer,sfor string,pfor pointer,cfor character,vfor void. For example, if format is "vis" the callback will have no return (void), the first argument will be an integer, and the second argument will be a string. If format is "iii" the callback must return an integer, and both the arguments passed in will be integers.- Variadic arguments: a variable number and type of arguments depending on the
window functionthat is being called. The arguments associated with eachnameare described in the NetHack window.txt.
Where is the header file for the API you ask? There isn't one. It's three functions, just drop the forward declarations at the top of your file (or create your own header). It's more work figuring out how to install and copy around header files than it's worth for such a small API. If you disagree, feel free to sumbit a PR to fix it. :)
API: nethack.js
The WebAssembly API has a similar signature to libnethack.a with minor syntactic differences:
main(int argc, char argv[])- The main function for NetHackshim_graphics_set_callback(char *cbName)- AStringrepresenting a name of a callback function. The callback function be registered asglobalThis[cbName] = function yourCallback(name, ... args) { /* your stuff */ }. Note that globalThis points towindowin browsers andglobalin node.js.nameis the name of the window function that needs to be handled... argsis a variable number and type of arguments depending on thewindow functionthat is being called. The arguments associated with eachnameare described in the NetHack window.txt- The function must return the value expected for the specified
name
API Stability
The "shim graphics" API should generally be stable. I aspire to replace the command line arguments (argc / argv) with a structure of options, so the nhmain() and main() functions may change at some point.
libnethack.a example
#include <stdio.h>
int nhmain(int argc, char *argv[]);
typedef void(*shim_callback_t)(const char *name, void *ret_ptr, const char *fmt, ...);
void shim_graphics_set_callback(shim_callback_t cb);
void window_cb(const char *name, void *ret_ptr, const char *fmt, ...) {
/* TODO */
}
int main(int argc, char *argv[]) {
shim_graphics_set_callback(window_cb);
nhmain(argc, argv);
}
nethack.js example
const path = require("path");
// starts nethack
function nethackStart(cb, inputModule = {}) {
// set callback
let cbName = cb.name;
if (cbName === "") cbName = "__anonymousNetHackCallback";
let userCallback = globalThis[cbName] = cb;
// Emscripten Module config
let Module = inputModule;
savedOnRuntimeInitialized = Module.onRuntimeInitialized;
Module.onRuntimeInitialized = function (... args) {
// after the WASM is loaded, add the shim graphics callback function
Module.ccall(
"shim_graphics_set_callback", // C function name
null, // return type
["string"], // arg types
[cbName], // arg values
{async: true} // options
);
};
// load and run the module
var factory = require(path.join(__dirname, "../build/nethack.js"));
factory(Module);
}
nethackStart(yourCallbackFunction);