add MALE, FEMALE, and gender-neutral names for individual monster species
to the mons array. The gender-neutral name (NEUTRAL) is mandatory, the
MALE and FEMALE versions are not.
replace code uses of the mname field of permonst with one of the three
potentially-available gender-specific names.
consolidate some separate mons entries that differed only by species into a
single mons entry (caveman, cavewoman and priest,priestess etc.)
consolidate several "* lord" and "* queen/* king" monst entries into
their single species, and allow both genders on some where it makes some
sense (there is probably more work and cleanup to come out of this at some
point, and the chosen gender-neutral name variations are not cast in stone
if someone has better suggestions).
related function or macro additions:
pmname(pm, gender) to get the gender variation of the permonst name. It
guards against monsters that haven't got anything except NEUTRAL naming
and falls back to the NEUTRAL version if FEMALE and MALE versions are
missing.
Ugender to obtain the current hero gender.
Mgender(mtmp) to obtain the gender of a monster
While the code can safely refer directly to pmnames[NEUTRAL] safely in the
code because it always exists, the other two (pmnames[MALE] and
pmnames[FEMALE] may not exist so use:
pmname(ptr, gidx)
where -ptr is a permonst *
-gidx is an index into the pmnames array field of the
permonst struct
pmname() checks for a valid index and checks for null-pointers for
pmnames[MALE] and pmnames[FEMALE], and will fall back to pmnames[NEUTRAL] if
the pointer requested if the requested variation is unavailable, or if the
gidx is out-of-range.
Allow code to specify makemon flags to request female or male (via MM_MALE
and MM_FEMALE flags respectively)to makedefs, since the species alone doesn't
distinguish male/female anymore. Specifying MM_MALE or MM_FEMALE won't
override the pm M2_MALE and M2_FEMALE flags on a mons[] entry.
male and female tiles have been added to win/share/monsters.txt.
The majority are duplicated placeholders except for those that were
separate mons entries before. Perhaps someone will contribute artwork in the
future to make the male and female variations visually distinguishable.
tilemapping via has the MALE tile indexes in the glyph2tile[]
array produced at build time. If a window port has information that the
FEMALE tile is required, it just has to increment the index returned
from the glyph2tile[] array by 1.
statues already preserved gender of the monster through STATUE_FEMALE
and STATUE_MALE, so ensure that pmnames takes that into consideration.
I expect some refinement will be required after broad play-testing puts it to
the test.
consolidate caveman,cavewoman and priest,priestess monst.c entries etc
This commit will require a bump of editlevel in patchlevel.h because it alters
the index numbers of the monsters due to the consolidation of some. Those
index numbers are saved in some other structures, even though the mons[] array
itself is not part of the savefile.
Window Port Interface Change
Also add a parameter to print_glyph to convey additional information beyond
the glyph to the window ports. Every single window port was calling back to
mapglyph for the information anyway, so just included it in the interface and
produce the information right in the display core.
The mapglyph() function uses will be eliminated, although there are still some
in the code yet to be dealt with.
win32, tty, x11, Qt, msdos window ports have all had adjustments done to
utilize the new parameter instead of calling mapglyph, but some of those
window ports have not been thoroughly tested since the changes.
Interface change additional info:
print_glyph(window, x, y, glyph, bkglyph, *glyphmod)
-- Print the glyph at (x,y) on the given window. Glyphs are
integers at the interface, mapped to whatever the window-
port wants (symbol, font, color, attributes, ...there's
a 1-1 map between glyphs and distinct things on the map).
-- bkglyph is a background glyph for potential use by some
graphical or tiled environments to allow the depiction
to fall against a background consistent with the grid
around x,y. If bkglyph is NO_GLYPH, then the parameter
should be ignored (do nothing with it).
-- glyphmod provides extended information about the glyph
that window ports can use to enhance the display in
various ways.
unsigned int glyphmod[NUM_GLYPHMOD]
where:
glyphmod[GM_TTYCHAR] is the text characters associated
with the original NetHack display.
glyphmod[GM_FLAGS] are the special flags that denote
additional information that window
ports can use.
glyphmod[GM_COLOR] is the text character
color associated with the original
NetHack display.
Support for including the glyphmod info in the display glyph buffer
alongside the glyph itself was added and is the default operation.
That can be turned off by defining UNBUFFERED_GLYPHMOD at compile time.
With UNBUFFERED_GLYPHMOD operation, a call will be placed to map_glyphmod()
immediately prior to every print_glyph() call.
315 lines
13 KiB
C
315 lines
13 KiB
C
/* NetHack 3.7 winshim.c $NHDT-Date: 1596498345 2020/08/03 23:45:45 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.259 $ */
|
|
/* Copyright (c) Adam Powers, 2020 */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
/* not an actual windowing port, but a fake win port for libnethack */
|
|
|
|
#include "hack.h"
|
|
#include <string.h>
|
|
|
|
#ifdef SHIM_GRAPHICS
|
|
#include <stdarg.h>
|
|
/* for cross-compiling to WebAssembly (WASM) */
|
|
#ifdef __EMSCRIPTEN__
|
|
#include <emscripten/emscripten.h>
|
|
#endif
|
|
|
|
#undef SHIM_DEBUG
|
|
|
|
#ifdef SHIM_DEBUG
|
|
#define debugf printf
|
|
#else /* !SHIM_DEBUG */
|
|
#define debugf(...)
|
|
#endif /* SHIM_DEBUG */
|
|
|
|
|
|
/* shim_graphics_callback is the primary interface to shim graphics,
|
|
* call this function with your declared callback function
|
|
* and you will receive all the windowing calls
|
|
*/
|
|
#ifdef __EMSCRIPTEN__
|
|
/************
|
|
* WASM interface
|
|
************/
|
|
EMSCRIPTEN_KEEPALIVE
|
|
static char *shim_callback_name = NULL;
|
|
void shim_graphics_set_callback(char *cbName) {
|
|
if (shim_callback_name != NULL) free(shim_callback_name);
|
|
if(cbName && strlen(cbName) > 0) {
|
|
debugf("setting shim_callback_name: %s\n", cbName);
|
|
shim_callback_name = strdup(cbName);
|
|
} else {
|
|
debugf("un-setting shim_callback_name\n");
|
|
shim_callback_name = NULL;
|
|
}
|
|
/* TODO: free(shim_callback_name) during shutdown? */
|
|
}
|
|
void local_callback (const char *cb_name, const char *shim_name, void *ret_ptr, const char *fmt_str, void *args);
|
|
|
|
/* A2P = Argument to Pointer */
|
|
#define A2P &
|
|
/* P2V = Pointer to Void */
|
|
#define P2V (void *)
|
|
#define DECLCB(ret_type, name, fn_args, fmt, ...) \
|
|
ret_type name fn_args { \
|
|
void *args[] = { __VA_ARGS__ }; \
|
|
ret_type ret = (ret_type) 0; \
|
|
debugf("SHIM GRAPHICS: " #name "\n"); \
|
|
if (!shim_callback_name) return ret; \
|
|
local_callback(shim_callback_name, #name, (void *)&ret, fmt, args); \
|
|
debugf("SHIM GRAPHICS: " #name " done.\n"); \
|
|
return ret; \
|
|
}
|
|
|
|
#define VDECLCB(name, fn_args, fmt, ...) \
|
|
void name fn_args { \
|
|
void *args[] = { __VA_ARGS__ }; \
|
|
debugf("SHIM GRAPHICS: " #name "\n"); \
|
|
if (!shim_callback_name) return; \
|
|
local_callback(shim_callback_name, #name, NULL, fmt, args); \
|
|
debugf("SHIM GRAPHICS: " #name " done.\n"); \
|
|
}
|
|
|
|
#else /* !__EMSCRIPTEN__ */
|
|
|
|
/************
|
|
* libnethack.a interface
|
|
************/
|
|
typedef void(*shim_callback_t)(const char *name, void *ret_ptr, const char *fmt, ...);
|
|
static shim_callback_t shim_graphics_callback = NULL;
|
|
void shim_graphics_set_callback(shim_callback_t cb) {
|
|
shim_graphics_callback = cb;
|
|
}
|
|
|
|
#define A2P
|
|
#define P2V
|
|
#define DECLCB(ret_type, name, fn_args, fmt, ...) \
|
|
ret_type name fn_args { \
|
|
ret_type ret = (ret_type) 0; \
|
|
debugf("SHIM GRAPHICS: " #name "\n"); \
|
|
if (!shim_graphics_callback) return ret; \
|
|
shim_graphics_callback(#name, (void *)&ret, fmt, ## __VA_ARGS__); \
|
|
debugf("SHIM GRAPHICS: " #name " done.\n"); \
|
|
return ret; \
|
|
}
|
|
|
|
#define VDECLCB(name, fn_args, fmt, ...) \
|
|
void name fn_args { \
|
|
debugf("SHIM GRAPHICS: " #name "\n"); \
|
|
if (!shim_graphics_callback) return; \
|
|
shim_graphics_callback(#name, NULL, fmt, ## __VA_ARGS__); \
|
|
debugf("SHIM GRAPHICS: " #name " done.\n"); \
|
|
}
|
|
#endif /* __EMSCRIPTEN__ */
|
|
|
|
VDECLCB(shim_init_nhwindows,(int *argcp, char **argv), "vpp", P2V argcp, P2V argv)
|
|
VDECLCB(shim_player_selection,(void), "v")
|
|
VDECLCB(shim_askname,(void), "v")
|
|
VDECLCB(shim_get_nh_event,(void), "v")
|
|
VDECLCB(shim_exit_nhwindows,(const char *str), "vs", P2V str)
|
|
VDECLCB(shim_suspend_nhwindows,(const char *str), "vs", P2V str)
|
|
VDECLCB(shim_resume_nhwindows,(void), "v")
|
|
DECLCB(winid, shim_create_nhwindow, (int type), "ii", A2P type)
|
|
VDECLCB(shim_clear_nhwindow,(winid window), "vi", A2P window)
|
|
VDECLCB(shim_display_nhwindow,(winid window, BOOLEAN_P blocking), "vii", A2P window, A2P blocking)
|
|
VDECLCB(shim_destroy_nhwindow,(winid window), "vi", A2P window)
|
|
VDECLCB(shim_curs,(winid a, int x, int y), "viii", A2P a, A2P x, A2P y)
|
|
VDECLCB(shim_putstr,(winid w, int attr, const char *str), "viis", A2P w, A2P attr, P2V str)
|
|
VDECLCB(shim_display_file,(const char *name, BOOLEAN_P complain), "vsi", P2V name, A2P complain)
|
|
VDECLCB(shim_start_menu,(winid window, unsigned long mbehavior), "vii", A2P window, A2P mbehavior)
|
|
VDECLCB(shim_add_menu,
|
|
(winid window, int glyph, const ANY_P *identifier, CHAR_P ch, CHAR_P gch, int attr, const char *str, unsigned int itemflags),
|
|
"viipiiisi",
|
|
A2P window, A2P glyph, P2V identifier, A2P ch, A2P gch, A2P attr, P2V str, A2P itemflags)
|
|
VDECLCB(shim_end_menu,(winid window, const char *prompt), "vis", A2P window, P2V prompt)
|
|
/* XXX: shim_select_menu menu_list is an output */
|
|
DECLCB(int, shim_select_menu,(winid window, int how, MENU_ITEM_P **menu_list), "iiio", A2P window, A2P how, P2V menu_list)
|
|
DECLCB(char, shim_message_menu,(CHAR_P let, int how, const char *mesg), "ciis", A2P let, A2P how, P2V mesg)
|
|
VDECLCB(shim_mark_synch,(void), "v")
|
|
VDECLCB(shim_wait_synch,(void), "v")
|
|
VDECLCB(shim_cliparound,(int x, int y), "vii", A2P x, A2P y)
|
|
VDECLCB(shim_update_positionbar,(char *posbar), "vp", P2V posbar)
|
|
VDECLCB(shim_print_glyph,(winid w, int x, int y, int glyph, int bkglyph, int glyphmod[NUM_GLYPHMOD]), "viiiii", A2P w, A2P x, A2P y, A2P glyph, A2P bkglyph, A2P glyphmod)
|
|
VDECLCB(shim_raw_print,(const char *str), "vs", P2V str)
|
|
VDECLCB(shim_raw_print_bold,(const char *str), "vs", P2V str)
|
|
DECLCB(int, shim_nhgetch,(void), "i")
|
|
DECLCB(int, shim_nh_poskey,(int *x, int *y, int *mod), "iooo", P2V x, P2V y, P2V mod)
|
|
VDECLCB(shim_nhbell,(void), "v")
|
|
DECLCB(int, shim_doprev_message,(void),"iv")
|
|
DECLCB(char, shim_yn_function,(const char *query, const char *resp, CHAR_P def), "cssi", P2V query, P2V resp, A2P def)
|
|
VDECLCB(shim_getlin,(const char *query, char *bufp), "vso", P2V query, P2V bufp)
|
|
DECLCB(int,shim_get_ext_cmd,(void),"iv")
|
|
VDECLCB(shim_number_pad,(int state), "vi", A2P state)
|
|
VDECLCB(shim_delay_output,(void), "v")
|
|
VDECLCB(shim_change_color,(int color, long rgb, int reverse), "viii", A2P color, A2P rgb, A2P reverse)
|
|
VDECLCB(shim_change_background,(int white_or_black), "vi", A2P white_or_black)
|
|
DECLCB(short, set_shim_font_name,(winid window_type, char *font_name),"2is", A2P window_type, P2V font_name)
|
|
DECLCB(char *,shim_get_color_string,(void),"sv")
|
|
|
|
/* other defs that really should go away (they're tty specific) */
|
|
VDECLCB(shim_start_screen, (void), "v")
|
|
VDECLCB(shim_end_screen, (void), "v")
|
|
VDECLCB(shim_preference_update, (const char *pref), "vp", P2V pref)
|
|
DECLCB(char *,shim_getmsghistory, (BOOLEAN_P init), "si", A2P init)
|
|
VDECLCB(shim_putmsghistory, (const char *msg, BOOLEAN_P restoring_msghist), "vsi", P2V msg, A2P restoring_msghist)
|
|
VDECLCB(shim_status_init, (void), "v")
|
|
VDECLCB(shim_status_enablefield,
|
|
(int fieldidx, const char *nm, const char *fmt, BOOLEAN_P enable),
|
|
"vippi",
|
|
A2P fieldidx, P2V nm, P2V fmt, A2P enable)
|
|
/* XXX: the second argument to shim_status_update is sometimes an integer and sometimes a pointer */
|
|
VDECLCB(shim_status_update,
|
|
(int fldidx, genericptr_t ptr, int chg, int percent, int color, unsigned long *colormasks),
|
|
"vioiiip",
|
|
A2P fldidx, P2V ptr, A2P chg, A2P percent, A2P color, P2V colormasks)
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
/* XXX: calling display_inventory() from shim_update_inventory() causes reentrancy that breaks emscripten Asyncify */
|
|
/* this should be fine since according to windows.doc, the only purpose of shim_update_inventory() is to call display_inventory() */
|
|
void shim_update_inventory() {
|
|
if(iflags.perm_invent) {
|
|
display_inventory(NULL, FALSE);
|
|
}
|
|
}
|
|
#else /* !__EMSCRIPTEN__ */
|
|
VDECLCB(shim_update_inventory,(void), "v")
|
|
#endif
|
|
|
|
/* Interface definition used in windows.c */
|
|
struct window_procs shim_procs = {
|
|
"shim",
|
|
(0
|
|
| WC_ASCII_MAP
|
|
| WC_COLOR | WC_HILITE_PET | WC_INVERSE | WC_EIGHT_BIT_IN),
|
|
(0
|
|
#if defined(SELECTSAVED)
|
|
| WC2_SELECTSAVED
|
|
#endif
|
|
#if defined(STATUS_HILITES)
|
|
| WC2_HILITE_STATUS | WC2_HITPOINTBAR | WC2_FLUSH_STATUS
|
|
| WC2_RESET_STATUS
|
|
#endif
|
|
| WC2_DARKGRAY | WC2_SUPPRESS_HIST | WC2_STATUSLINES),
|
|
#ifdef TEXTCOLOR
|
|
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, /* color availability */
|
|
#else
|
|
{1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1},
|
|
#endif
|
|
shim_init_nhwindows, shim_player_selection, shim_askname, shim_get_nh_event,
|
|
shim_exit_nhwindows, shim_suspend_nhwindows, shim_resume_nhwindows,
|
|
shim_create_nhwindow, shim_clear_nhwindow, shim_display_nhwindow,
|
|
shim_destroy_nhwindow, shim_curs, shim_putstr, genl_putmixed,
|
|
shim_display_file, shim_start_menu, shim_add_menu, shim_end_menu,
|
|
shim_select_menu, shim_message_menu, shim_update_inventory, shim_mark_synch,
|
|
shim_wait_synch,
|
|
#ifdef CLIPPING
|
|
shim_cliparound,
|
|
#endif
|
|
#ifdef POSITIONBAR
|
|
shim_update_positionbar,
|
|
#endif
|
|
shim_print_glyph, shim_raw_print, shim_raw_print_bold, shim_nhgetch,
|
|
shim_nh_poskey, shim_nhbell, shim_doprev_message, shim_yn_function,
|
|
shim_getlin, shim_get_ext_cmd, shim_number_pad, shim_delay_output,
|
|
#ifdef CHANGE_COLOR /* the Mac uses a palette device */
|
|
shim_change_color,
|
|
#ifdef MAC
|
|
shim_change_background, set_shim_font_name,
|
|
#endif
|
|
shim_get_color_string,
|
|
#endif
|
|
|
|
/* other defs that really should go away (they're tty specific) */
|
|
shim_start_screen, shim_end_screen, genl_outrip,
|
|
shim_preference_update,
|
|
shim_getmsghistory, shim_putmsghistory,
|
|
shim_status_init,
|
|
genl_status_finish, genl_status_enablefield,
|
|
#ifdef STATUS_HILITES
|
|
shim_status_update,
|
|
#else
|
|
genl_status_update,
|
|
#endif
|
|
genl_can_suspend_yes,
|
|
};
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
/* convert the C callback to a JavaScript callback */
|
|
EM_JS(void, local_callback, (const char *cb_name, const char *shim_name, void *ret_ptr, const char *fmt_str, void *args), {
|
|
// Asyncify.handleAsync() is the more logical choice here; however, the stack unrolling in Asyncify is performed by
|
|
// function call analysis during compilation. Since we are using an indirect callback (cb_name), it can't predict the stack
|
|
// unrolling and it crashes. Thus we use Asyncify.handleSleep() and wakeUp() to make sure that async doesn't break
|
|
// Asyncify. For details, see: https://emscripten.org/docs/porting/asyncify.html#optimizing
|
|
Asyncify.handleSleep(wakeUp => {
|
|
// convert callback arguments to proper JavaScript varaidic arguments
|
|
let name = UTF8ToString(shim_name);
|
|
let fmt = UTF8ToString(fmt_str);
|
|
let cbName = UTF8ToString(cb_name);
|
|
// console.log("local_callback:", cbName, fmt, name);
|
|
|
|
// get pointer / type conversion helpers
|
|
let getPointerValue = globalThis.nethackGlobal.helpers.getPointerValue;
|
|
let setPointerValue = globalThis.nethackGlobal.helpers.setPointerValue;
|
|
|
|
reentryMutexLock(name);
|
|
|
|
let argTypes = fmt.split("");
|
|
let retType = argTypes.shift();
|
|
|
|
// build array of JavaScript args from WASM parameters
|
|
let jsArgs = [];
|
|
for (let i = 0; i < argTypes.length; i++) {
|
|
let ptr = args + (4*i);
|
|
let val = getArg(name, ptr, argTypes[i]);
|
|
jsArgs.push(val);
|
|
}
|
|
|
|
// do the callback
|
|
let userCallback = globalThis[cbName];
|
|
runJsEventLoop(() => userCallback.call(this, name, ... jsArgs)).then((retVal) => {
|
|
// save the return value
|
|
setPointerValue(name, ret_ptr, retType, retVal);
|
|
// return
|
|
setTimeout(() => {
|
|
reentryMutexUnlock();
|
|
wakeUp();
|
|
}, 0);
|
|
});
|
|
|
|
function getArg(name, ptr, type) {
|
|
return (type === "o")?ptr:getPointerValue(name, getValue(ptr, "*"), type);
|
|
}
|
|
|
|
// setTimeout() with value of '0' is similar to setImmediate() (but setImmediate isn't standard)
|
|
// this lets the JS loop run for a tick so that other events can occur
|
|
// XXX: I also tried replacing the for(;;) in allmain.c:moveloop() with emscripten_set_main_loop()
|
|
// unfortunately that won't work -- if the simulate_infinite_loop arg is false, it falls through
|
|
// and the program ends;
|
|
// if is true, it throws an exception to break out of main(), but doesn't get caught because
|
|
// the stack isn't running under main() anymore...
|
|
// I think this is suboptimal, but we will have to live with it (for now?)
|
|
async function runJsEventLoop(cb) {
|
|
return new Promise((resolve) => {
|
|
setTimeout(() => {
|
|
resolve(cb());
|
|
}, 0);
|
|
});
|
|
}
|
|
|
|
function reentryMutexLock(name) {
|
|
globalThis.nethackGlobal = globalThis.nethackGlobal || {};
|
|
if(globalThis.nethackGlobal.shimFunctionRunning) {
|
|
throw new Error(`'${name}' attempting second call to 'local_callback' before '${globalThis.nethackGlobal.shimFunctionRunning}' has finished, will crash emscripten Asyncify. For details see: emscripten.org/docs/porting/asyncify.html#reentrancy`);
|
|
}
|
|
globalThis.nethackGlobal.shimFunctionRunning = name;
|
|
}
|
|
|
|
function reentryMutexUnlock() {
|
|
globalThis.nethackGlobal.shimFunctionRunning = null;
|
|
}
|
|
});
|
|
})
|
|
#endif /* __EMSCRIPTEN__ */
|
|
|
|
#endif /* SHIM_GRAPHICS */
|