b0r3doom: b0r3d.org's changes to Dwasm (PrBoom+ / PrBoomX), GPL-2.0. Against https://github.com/GMH-Code/Dwasm at ddf0347a4fc115b11ffb1c5710768b7c47c46698 Apply from the source root with: git apply b0r3d-engine.patch The Sword. With -sword on the command line, the fist's slot becomes The Sword. The engine side adds the A_B0r3dSlash codepointer (a cut that kills whatever it reaches, plus a thrown wave, burning no ammo), a parry that turns aside half the blows aimed at the player while it is drawn, and starts the player with it out. Its frames, sprites and sounds come from sword.wad, a separate content patch. Without -sword the engine behaves exactly as upstream. Return to b0r3d. The main menu gets one more item, under Quit Game. Like Quit Game it asks first, then hands the tab back to the site through Module.returnToSite. Its graphic, M_B0R3D, is built into the engine's own wad (data/graphics/m_b0r3d.ppm). It is set in Freedoom's menu font (BSD-3-Clause), laid out the same way Freedoom lays out its own menu items. Page hooks. The engine tells the page when its menu opens and closes (Module.menuOpened / Module.menuClosed), including when answering a yes/no prompt closes it, so the page can free the mouse and then take it back. Three exports serve b0r3doom's dev menu: b0r3d_command_buffer and b0r3d_command run a console command as if typed, and b0r3d_state returns a JSON snapshot of the player and level. diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index f50d7b6..6116bd8 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -154,6 +154,7 @@ set(GRAPHICS graphics/cross1.ppm graphics/cross2.ppm graphics/cross3.ppm + graphics/m_b0r3d.ppm ) set(FLATS diff --git a/data/graphics/m_b0r3d.ppm b/data/graphics/m_b0r3d.ppm new file mode 100644 index 0000000000000000000000000000000000000000..8747fca443fd1aba4e378f54e5453d0f6f339786 GIT binary patch literal 6944 zcmeI#!Eqf&3Zrx^Bk z@$Kgsp7h*VDdChMud9z=YAYV6;-#?gJyL#^AL<7SVel};R*m*faW0Y;B+lYrQvI0L z3#VhpbRXVU_sn~q8aXA)UG(hIy;9Zp%kCb-_INr;fn5TCbzzuO=*_RbU17TSNcXE; zu;g)qNwDMhvjkkNFG=cG@MX&8^rm~iw=Sui2wU()3&8K$L!OZIDuuBhli`)Z;{S9y zxwyZF8S`NpU%4zRTCnQE2dLvE=gY*v^YvXK`@;H=fU7&$=fMzs$J8vGw@9kbpvW_e zW2ajDlD!DtD=AtRfmyrGe8>}$KgaTIUI}}c$GgGsN?}R;I7^Vg|N6_7rF|VYg@g|f zRRIXbfEgJC$M`vZKb>MqK8>uSD!#tnfh7_jl08f@NqroxpT-+Z@bmaK;^V0)%6W?< z82FIaXvoC{i)wMS)7PYQ0!Og9to>^%EZT=QOK0vp%gC0m+&gyr@*-X-l!{t~w8eZI zA=I5h1}35@*)Zu+Z+I!=NO=e59X92k$C&1lr7gSi#s-#1d`Pt3s#5OwIPbA`h|2zz zgn3!G<69(s8olxbqN2^bvB=odijb}Q{cP=sM-{athUU=XWR9{6bB&)~-Ziq667Mpp zm|lVepxqa0AxmWMnA;4wcNng&u0n|9c8I?pLxmxIqXC|Hm=eR0B0yZ0pN>7!eR#BK z2mCmG7BT{c2!m(Ewrsvyzaq&*-^VAB)+s@l0XYB*?OwKs!juGGw2|s)7J%68&@8wt z1W6xR*1Y6-v->f%TCkH!*_;NvxJ5UILIrwl`lFHc_luOx(Au6v?%eFl8x`p0*jazU8-GoE=#(bA8nD?RmgSWKkF~T zm$HFr!&3~LFyx)P6Xb}XJ+E}C3TzoUBpKFQZEWi@3&po5!Ui*p&FA5}$70|-U!{^s zcqW-u=Re!92q}C~JIdGiXe}6IvNxkk{A%RJ3zl|DqH|lgr@9|Hqx?{kV0xL|gH12~ zI(w&`CFcRhuqoLng511qu88E{3=?@4Jb9#)N5*+%BrhL@VM{n*k?lA;sx?jq5x#Ff z*FKf{TFxc&qMs#giTTvL^9wvDcJZ@@k*2D;Q*tv~)DkwSFGVII*rz*BRJ|7GlB7W% zDr_vD&3UB~3r~)d?L5dJNx6#cX)uKO$^AAQ#|3a{leTcw`6yRF^~NlB6?uEQbc|$V1EDo;lItlpe^)%OIB16{?a&B@VgI;<+@k!XM)w z?e(?+B;ikCq>6|W5DQE>b36E%xg5PA?}ridaahVxDLlMQu<)d$uObZMXmw#oyhW<2 zw~pXS1r|cVUfa9u{X>QhGi=tbdp6dyi;wMb$R^Kn$Z%v`UX=hNl-g&dvZ(#Y^wd3u zTq8>p$E;BBVSIZuaDwr6l!bvLELB=Nic_K&o)RO-7f~3iR7L>61E4m{HCh3rLIQw4 z$aEL)twWwvCrp?7S-Z4Yj{hnNUel;L)Z7WfW_B~i;$JEhUse>h@EGB6yX!Q<(1KPdvyv7MN3nhr!;W`)>pX)(a2)&er`y YaDpYfW7O7+iPy2ymS43M**k^#2c-{Ku>b%7 literal 0 HcmV?d00001 diff --git a/data/prboom.txt b/data/prboom.txt index 57c1664..1350d8d 100644 --- a/data/prboom.txt +++ b/data/prboom.txt @@ -149,6 +149,7 @@ M_LOKSEN 0 0 CROSS1 0 0 CROSS2 0 0 CROSS3 0 0 +M_B0R3D 0 0 # List of Sprites [sprites] diff --git a/src/WASM/wasm_io.c b/src/WASM/wasm_io.c index 94d7d95..9524f18 100644 --- a/src/WASM/wasm_io.c +++ b/src/WASM/wasm_io.c @@ -121,6 +121,33 @@ void wasm_capture_mouse(void) ); } +void wasm_menu_opened(void) +{ + // b0r3d.org: the menu is up (and single player is paused under it) + EM_ASM( + if (typeof Module.menuOpened === 'function') + Module.menuOpened(); + ); +} + +void wasm_menu_closed(void) +{ + // b0r3d.org: the menu has gone; wasm_capture_mouse follows + EM_ASM( + if (typeof Module.menuClosed === 'function') + Module.menuClosed(); + ); +} + +void wasm_return_to_site(void) +{ + // b0r3d.org: Return to b0r3d, from the main menu + EM_ASM( + if (typeof Module.returnToSite === 'function') + Module.returnToSite(); + ); +} + void wasm_soft_exit(int exit_code) { soft_exit_code = exit_code; diff --git a/src/WASM/wasm_io.h b/src/WASM/wasm_io.h index 57bd21f..f96b1e1 100644 --- a/src/WASM/wasm_io.h +++ b/src/WASM/wasm_io.h @@ -44,6 +44,9 @@ void wasm_hide_console(void); void wasm_show_console(void); void wasm_vid_resize(void); void wasm_capture_mouse(void); +void wasm_menu_opened(void); +void wasm_menu_closed(void); +void wasm_return_to_site(void); void wasm_soft_exit(int exit_code); void wasm_soft_exit_fs_check(void); diff --git a/src/c_cmd.c b/src/c_cmd.c index 0324b81..48625a1 100644 --- a/src/c_cmd.c +++ b/src/c_cmd.c @@ -2037,3 +2037,74 @@ const char* C_CommandComplete(const char* partial) /* CVAR completion returns null on failure */ return C_CvarComplete(partial); } + +#ifdef __EMSCRIPTEN__ +#include + +/* + * b0r3d.org: b0r3doom's dev menu drives the engine through these. The page + * writes a command into the buffer and calls b0r3d_command(), exactly as if + * it had been typed at the console. Refused outside a level, since several + * commands reach for the player's mobj. + */ + +static char b0r3d_cmdbuf[256]; + +EMSCRIPTEN_KEEPALIVE char* b0r3d_command_buffer(void) +{ + return b0r3d_cmdbuf; +} + +EMSCRIPTEN_KEEPALIVE int b0r3d_command(void) +{ + char cmd[sizeof(b0r3d_cmdbuf)]; + + if (gamestate != GS_LEVEL || !plyr->mo || demoplayback) + return 0; + + strncpy(cmd, b0r3d_cmdbuf, sizeof(cmd) - 1); + cmd[sizeof(cmd) - 1] = 0; + C_ConsoleCommand(cmd); + return 1; +} + +/* one JSON object, rebuilt on every call */ +EMSCRIPTEN_KEEPALIVE const char* b0r3d_state(void) +{ + extern int b0r3d_sword; + static char json[1024]; + const player_t* p = plyr; + const mobj_t* mo = p->mo; + int level = gamestate == GS_LEVEL && mo != NULL; + + snprintf(json, sizeof(json), + "{\"level\":%d,\"gamestate\":%d,\"menu\":%d,\"paused\":%d,\"demo\":%d," + "\"automap\":%d,\"freeze\":%d,\"sword\":%d,\"commercial\":%d," + "\"episode\":%d,\"map\":%d,\"skill\":%d,\"leveltime\":%d," + "\"alive\":%d,\"health\":%d,\"armor\":%d,\"weapon\":%d," + "\"ammo\":[%d,%d,%d,%d],\"maxammo\":[%d,%d,%d,%d]," + "\"owned\":[%d,%d,%d,%d,%d,%d,%d,%d,%d]," + "\"cards\":[%d,%d,%d,%d,%d,%d],\"cheats\":%d," + "\"kills\":%d,\"totalkills\":%d,\"items\":%d,\"totalitems\":%d," + "\"secrets\":%d,\"totalsecrets\":%d," + "\"x\":%d,\"y\":%d,\"z\":%d,\"angle\":%d}", + level, (int)gamestate, menuactive != mnact_inactive, paused != 0, + demoplayback != 0, (automapmode & am_active) != 0, freeze_mode != 0, + b0r3d_sword != 0, gamemode == commercial, + gameepisode, gamemap, (int)gameskill, leveltime, + p->playerstate == PST_LIVE, p->health, p->armorpoints, (int)p->readyweapon, + p->ammo[0], p->ammo[1], p->ammo[2], p->ammo[3], + p->maxammo[0], p->maxammo[1], p->maxammo[2], p->maxammo[3], + p->weaponowned[0], p->weaponowned[1], p->weaponowned[2], + p->weaponowned[3], p->weaponowned[4], p->weaponowned[5], + p->weaponowned[6], p->weaponowned[7], p->weaponowned[8], + p->cards[0], p->cards[1], p->cards[2], p->cards[3], p->cards[4], p->cards[5], + p->cheats, + p->killcount, totalkills, p->itemcount, totalitems, + p->secretcount, totalsecret, + level ? mo->x >> FRACBITS : 0, level ? mo->y >> FRACBITS : 0, + level ? mo->z >> FRACBITS : 0, + level ? (int)((double)mo->angle * 360.0 / 4294967296.0) : 0); + return json; +} +#endif // __EMSCRIPTEN__ diff --git a/src/d_deh.c b/src/d_deh.c index e5a2f7d..c614a07 100644 --- a/src/d_deh.c +++ b/src/d_deh.c @@ -1402,6 +1402,7 @@ static const deh_bexptr deh_bexptrs[] = // CPhipps - static const {A_LineEffect, "A_LineEffect"}, // killough 11/98 {A_FireOldBFG, "A_FireOldBFG"}, // killough 7/19/98: classic BFG firing function + {A_B0r3dSlash, "A_B0r3dSlash"}, // b0r3d.org: The Sword {A_BetaSkullAttack, "A_BetaSkullAttack"}, // killough 10/98: beta lost souls attacked different {A_Stop, "A_Stop"}, diff --git a/src/d_main.c b/src/d_main.c index 763ea02..3f834d8 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -123,6 +123,7 @@ dboolean clfastparm; // checkparm of -fast // jff 1/24/98 end definition of command line version of play mode switches dboolean nomonsters; // working -nomonsters +int b0r3d_sword; // b0r3d.org: The Sword dboolean respawnparm; // working -respawn dboolean fastparm; // working -fast @@ -1787,6 +1788,7 @@ static void D_DoomMainSetup(void) nomonsters = clnomonsters = M_CheckParm ("-nomonsters"); respawnparm = clrespawnparm = M_CheckParm ("-respawn"); fastparm = clfastparm = M_CheckParm ("-fast"); + b0r3d_sword = M_CheckParm ("-sword"); // b0r3d.org: The Sword (needs sword.wad for the frames) // jff 1/24/98 end of set to both working and command line value devparm = M_CheckParm ("-devparm"); diff --git a/src/doomstat.h b/src/doomstat.h index 3976067..9e3c828 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -52,6 +52,7 @@ // extern dboolean nomonsters; // checkparm of -nomonsters +extern int b0r3d_sword; // checkparm of -sword: The Sword from b0r3d.org is in the fist slot extern dboolean respawnparm; // checkparm of -respawn extern dboolean fastparm; // checkparm of -fast extern dboolean devparm; // DEBUG: launched with -devparm diff --git a/src/g_game.c b/src/g_game.c index 110d41a..7c465ae 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1445,7 +1445,7 @@ void G_PlayerReborn (int player) p->usedown = p->attackdown = true; // don't do anything immediately p->playerstate = PST_LIVE; p->health = initial_health; // Ty 03/12/98 - use dehacked values - p->readyweapon = p->pendingweapon = wp_pistol; + p->readyweapon = p->pendingweapon = b0r3d_sword ? wp_fist : wp_pistol; // b0r3d.org: The Sword comes out drawn p->weaponowned[wp_fist] = true; p->weaponowned[wp_pistol] = true; p->ammo[am_clip] = initial_bullets; // Ty 03/12/98 - use dehacked values diff --git a/src/m_menu.c b/src/m_menu.c index 1987bf5..6e73bff 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -229,6 +229,9 @@ void M_EndGame(int choice); void M_ReadThis(int choice); void M_ReadThis2(int choice); void M_QuitDOOM(int choice); +#ifdef __EMSCRIPTEN__ +void M_B0r3dReturn(int choice); // b0r3d.org +#endif // __EMSCRIPTEN__ void M_ChangeMessages(int choice); void M_ChangeSensitivity(int choice); @@ -338,6 +341,9 @@ enum options, readthis, quitdoom, +#ifdef __EMSCRIPTEN__ + b0r3dreturn, // b0r3d.org +#endif // __EMSCRIPTEN__ main_end } main_e; @@ -357,7 +363,12 @@ menuitem_t MainMenu[]= {1,"M_SAVEG", M_SaveGame,'s'}, // Another hickup with Special edition. {1,"M_RDTHIS",M_ReadThis,'r'}, - {1,"M_QUITG", M_QuitDOOM,'q'} + {1,"M_QUITG", M_QuitDOOM,'q'}, +#ifdef __EMSCRIPTEN__ + // b0r3d.org: Return to b0r3d. M_B0R3D is in prboomx.wad, drawn in + // Freedoom's menu font. + {1,"M_B0R3D", M_B0r3dReturn,'b'}, +#endif // __EMSCRIPTEN__ }; menu_t MainDef = @@ -1262,6 +1273,24 @@ void M_QuitDOOM(int choice) M_StartMessage(endstring,M_QuitResponse,true); } +#ifdef __EMSCRIPTEN__ +// b0r3d.org: Return to b0r3d leaves the game for the site, so it asks first, +// the same as Quit Game. + +static void M_B0r3dReturnResponse(int ch) +{ + if (ch != 'y') + return; + wasm_return_to_site(); +} + +void M_B0r3dReturn(int choice) +{ + M_StartMessage("return to b0r3d?\n\n(press y to leave)", + M_B0r3dReturnResponse, true); +} +#endif // __EMSCRIPTEN__ + ///////////////////////////// // // SOUND VOLUME MENU @@ -4884,6 +4913,10 @@ dboolean M_Responder (event_t* ev) { messageRoutine(ch); menuactive = mnact_inactive; +#ifdef __EMSCRIPTEN__ + wasm_menu_closed(); // b0r3d.org + wasm_capture_mouse(); +#endif // __EMSCRIPTEN__ S_StartSound(NULL,sfx_swtchx); return true; } @@ -6100,6 +6133,10 @@ void M_StartControlPanel (void) currentMenu = &MainDef; // JDC itemOn = currentMenu->lastOn; // JDC print_warning_about_changes = false; // killough 11/98 + +#ifdef __EMSCRIPTEN__ + wasm_menu_opened(); // b0r3d.org +#endif // __EMSCRIPTEN__ } // @@ -6200,6 +6237,7 @@ void M_ClearMenus (void) // sendpause = true; #ifdef __EMSCRIPTEN__ + wasm_menu_closed(); // b0r3d.org wasm_capture_mouse(); #endif // __EMSCRIPTEN__ } @@ -6465,6 +6503,9 @@ void M_Init(void) // page. I use CREDIT as second page now, but // kept this hack for educational purposes. MainMenu[readthis] = MainMenu[quitdoom]; +#ifdef __EMSCRIPTEN__ + MainMenu[quitdoom] = MainMenu[b0r3dreturn]; // b0r3d.org +#endif // __EMSCRIPTEN__ MainDef.numitems--; MainDef.y += 8; if (!EpiCustom) diff --git a/src/m_random.h b/src/m_random.h index 4481ee4..299e447 100644 --- a/src/m_random.h +++ b/src/m_random.h @@ -123,6 +123,7 @@ typedef enum { pr_dropoff, // #60 pr_randomjump, // #61 pr_defect, // #62 // Start new entries -- add new entries below + pr_b0r3dsword, // #63 // b0r3d.org: The Sword's parry // End of new entries NUMPRCLASS // MUST be last item in list diff --git a/src/p_inter.c b/src/p_inter.c index dc53927..b6542d6 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -878,6 +878,41 @@ static void P_KillMobj(mobj_t *source, mobj_t *target) // and other environmental stuff. // +// +// B0r3d_Parry +// +// b0r3d.org: while The Sword is drawn, half of what comes at the player is +// turned aside. Only blows with something behind them (not lava, crushers or +// telefrags), and nothing scripted-sized (>= 1000, the god-mode threshold). +// + +static dboolean B0r3d_Parry(mobj_t *target, mobj_t *inflictor, int damage) +{ + player_t *player = target->player; + angle_t an; + + if (!b0r3d_sword || !player || !inflictor || damage >= 1000) + return false; + if (player->readyweapon != wp_fist) + return false; + if (P_Random(pr_b0r3dsword) & 1) + return false; + + S_StartSound(target, sfx_fre001); + player->bonuscount += 6; + + // a spark where the blade met it, if the wad brought one + if (mobjinfo[MT_EXTRA01].spawnstate != S_NULL) + { + an = R_PointToAngle2(target->x, target->y, inflictor->x, inflictor->y) + >> ANGLETOFINESHIFT; + P_SpawnMobj(target->x + FixedMul(48*FRACUNIT, finecosine[an]), + target->y + FixedMul(48*FRACUNIT, finesine[an]), + target->z + 34*FRACUNIT, MT_EXTRA01); + } + return true; +} + void P_DamageMobj(mobj_t *target,mobj_t *inflictor, mobj_t *source, int damage) { player_t *player; @@ -897,6 +932,9 @@ void P_DamageMobj(mobj_t *target,mobj_t *inflictor, mobj_t *source, int damage) if (player && gameskill == sk_baby) damage >>= 1; // take half damage in trainer mode + if (B0r3d_Parry(target, inflictor, damage)) // b0r3d.org: The Sword + return; + // Some close combat weapons should not // inflict thrust and push the victim out of reach, // thus kick away unless using the chainsaw. diff --git a/src/p_pspr.c b/src/p_pspr.c index 3394acd..ca8c57a 100644 --- a/src/p_pspr.c +++ b/src/p_pspr.c @@ -617,6 +617,40 @@ void A_Saw(player_t *player, pspdef_t *psp) R_SmoothPlaying_Reset(player); // e6y } +// +// A_B0r3dSlash +// +// The Sword from b0r3d.org (-sword, with sword.wad supplying the frames). +// One cut kills whatever is in reach, and a wave of the same carries on +// down the line. Burns no ammo, so it never touches player->ammo[]. +// + +#define B0R3D_SWORDRANGE (96*FRACUNIT) + +void A_B0r3dSlash(player_t *player, pspdef_t *psp) +{ + angle_t angle; + int slope; + + CHECK_WEAPON_CODEPOINTER("A_B0r3dSlash", player); + + S_StartSound(player->mo, sfx_fre000); + + angle = player->mo->angle; + + /* prefer enemies, as A_Saw does */ + if (!mbf_features || + (slope = P_AimLineAttack(player->mo, angle, B0R3D_SWORDRANGE, MF_FRIEND), + !linetarget)) + slope = P_AimLineAttack(player->mo, angle, B0R3D_SWORDRANGE, 0); + + P_LineAttack(player->mo, angle, B0R3D_SWORDRANGE, slope, 10000); + + // the wave; only if the wad defined it, so -sword alone still runs + if (mobjinfo[MT_EXTRA00].spawnstate != S_NULL) + P_SpawnPlayerMissile(player->mo, MT_EXTRA00); +} + // // A_FireMissile // diff --git a/src/p_pspr.h b/src/p_pspr.h index f1fbad0..ba6294c 100644 --- a/src/p_pspr.h +++ b/src/p_pspr.h @@ -126,5 +126,6 @@ void A_BFGsound(); void A_FireBFG(); void A_BFGSpray(); void A_FireOldBFG(); +void A_B0r3dSlash(); #endif