--- a/src/GameSrc/objsim.c +++ b/src/GameSrc/objsim.c @@ -26,8 +26,10 @@ // Object simulator code for Citadel #define __OBJSIM_SRC -#include +#include #include +#include +#include #include "Shock.h" @@ -1924,6 +1926,93 @@ *h = s_critterNativeH[id][frame]; } +// Resource id of the last-drawn critter animation, updated every frame from +// gameobj.c's FAUBJ_CRIT case - read by debug_dump_critter_animation() on +// the debug dump hotkey (see engine-debug-tools/, root README.md's +// "Debugging" section). Debug-build only: see +// engine-debug-tools/engine-patches/. +static Id s_lastCritterId = 0; + +void debug_track_critter_draw(Id id) { s_lastCritterId = id; } + +// Dumps every frame of the last-drawn critter's current animation (the +// whole resource id, not just the currently-visible frame) as raw +// palette-index bytes, one file per frame, independent of the renderer's +// own texture/GL state - lets a corrupted frame be told apart from +// corruption introduced later, in rendering. Format: 2-byte LE width, +// 2-byte LE height, then width*height index bytes - converted to a viewable +// image by engine-debug-tools/dump2png, not here, to avoid this debug path +// depending on the renderer's own palette/SDL state. +void debug_dump_critter_animation(void) { + RefTable *prt; + int numFrames, frame; + char path[256]; + FILE *f; + + if (s_lastCritterId == 0) { + fprintf(stderr, "[debug dump] no critter drawn yet\n"); + return; + } + + mkdir("dumps", 0755); // ignore EEXIST - fopen below reports any real failure + + prt = (RefTable *)ResLock(s_lastCritterId); + if (prt == NULL) { + fprintf(stderr, "[debug dump] ResLock(%d) failed\n", (int)s_lastCritterId); + return; + } + numFrames = prt->numRefs; + ResUnlock(s_lastCritterId); + + fprintf(stderr, "[debug dump] id=%d numFrames=%d\n", (int)s_lastCritterId, numFrames); + + for (frame = 0; frame < numFrames; frame++) { + FrameDesc *fd; + grs_bitmap decoded; + uchar *bits; + short w, h; + + fd = (FrameDesc *)RefLock(MKREF(s_lastCritterId, frame)); + if (fd == NULL) { + fprintf(stderr, "[debug dump] RefLock frame %d failed\n", frame); + continue; + } + + if (fd->bm.type == BMT_RSD8) { + if (gr_rsd8_convert(&fd->bm, &decoded) != 0 /* GR_UNPACK_RSD8_OK */) { + fprintf(stderr, "[debug dump] gr_rsd8_convert frame %d failed\n", frame); + RefUnlock(MKREF(s_lastCritterId, frame)); + continue; + } + bits = decoded.bits; + w = decoded.w; + h = decoded.h; + } else { + bits = fd->bm.bits; + w = fd->bm.w; + h = fd->bm.h; + } + + snprintf(path, sizeof(path), "dumps/id%d_frame%d.raw", (int)s_lastCritterId, frame); + f = fopen(path, "wb"); + if (f != NULL) { + uchar hdr[4]; + hdr[0] = (uchar)(w & 0xFF); + hdr[1] = (uchar)((w >> 8) & 0xFF); + hdr[2] = (uchar)(h & 0xFF); + hdr[3] = (uchar)((h >> 8) & 0xFF); + fwrite(hdr, 1, 4, f); + fwrite(bits, 1, (size_t)w * (size_t)h, f); + fclose(f); + fprintf(stderr, "[debug dump] wrote %s (%dx%d)\n", path, (int)w, (int)h); + } else { + fprintf(stderr, "[debug dump] failed to open %s for writing\n", path); + } + + RefUnlock(MKREF(s_lastCritterId, frame)); + } +} + errtype obj_set_secondary_properties() { char i, j; RefTable *prt; --- a/src/GameSrc/Headers/objsim.h +++ b/src/GameSrc/Headers/objsim.h @@ -109,6 +109,10 @@ errtype obj_create_player(ObjLoc *plr_loc); Ref ref_from_critter_data(ObjID oid, int triple, byte posture, short frame, short view); void get_critter_native_size(Id id, short frame, short *w, short *h); +// Debug-build only - see engine-debug-tools/, root README.md's "Debugging" +// section. +void debug_track_critter_draw(Id id); +void debug_dump_critter_animation(void); errtype obj_zero_unused(); grs_bitmap *obj_get_model_data(ObjID id, fix *x, fix *y, fix *z, grs_bitmap *bm2, Ref *ref1, Ref *ref2); errtype obj_model_hack(ObjID id, uchar *hack_x, uchar *hack_y, uchar *hack_z, uchar *hack_type); --- a/src/GameSrc/gameobj.c +++ b/src/GameSrc/gameobj.c @@ -1019,6 +1019,7 @@ { short native_w = 0, native_h = 0; get_critter_native_size(REFID(ref), REFINDEX(ref), &native_w, &native_h); + debug_track_critter_draw(REFID(ref)); _fr_draw_bitmap(tpdata, _fdt_dist, 0, anch.ul.x, anch.ul.y, native_w, native_h); } release_critter_bitmap_fast(ref); --- a/src/Libraries/INPUT/Source/sdl_events.c +++ b/src/Libraries/INPUT/Source/sdl_events.c @@ -481,6 +481,19 @@ SDL_Event ev; while (SDL_PollEvent(&ev)) { + // Debug-build only (engine-debug-tools/, root README.md's + // "Debugging" section): K dumps every frame of the last-drawn + // critter's current animation to dumps/. Checked here, before the + // switch below dispatches on event type at all, so it can never + // collide with an existing game keybinding - a plain letter rather + // than Insert, which some keyboards don't send at all (compact + // layouts, or routed through Fn). + if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_k) { + extern void debug_dump_critter_animation(void); + debug_dump_critter_animation(); + continue; + } + switch (ev.type) { case SDL_QUIT: // a bit hacky at this place, but this would allow exiting the game via the window's [x] button