diff --git a/README.md b/README.md index 21067ef..c47d902 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,16 @@ ESP32-S3-BOX-3 which provides: - Genesis emulator (gwenesis) - full speed / buttery smooth when muted; unmuted it runs a little slower but has nice sound - Regular Controls (D-Pad/A/B/C/Start/Select) (note: A is mapped to B, B is mapped to A, and C is mapped to Y) - Doom engine (prboom) - full speed with audio and control inputs. A is fire/enter, B is strafe/backspace, X is use, Y is weapontoggle, START is escape, and SELECT is map. + - Added haptic feedback to doom for when the player + - Fires a weapon (depending on the weapon that is fired) + - Receives damage (depending on amount of health / armor damage received) + - Interacts, e.g. with doors + - Picks up a weapon + - Picks up ammo + - Picks up health + - Picks up armor + - Picks up a power up + - Picks up a card / key - LVGL main menu with rom select (including boxart display) and settings page (all generated from Squareline Studio) - LVGL emulation paused menu with save slot select, save slot image display, @@ -122,6 +132,7 @@ This project has the following features (still WIP): - [x] MSX emulator - [x] Sega Mega Drive / Genesis emulator - [x] Doom + - [x] Haptics :rocket: - [ ] Dark Forces (WIP) - [ ] SNES emulator (WIP) - [x] uSD card (FAT) filesystem over SPI diff --git a/components/doom/prboom/p_inter.c b/components/doom/prboom/p_inter.c index be4341b..2a3e27a 100644 --- a/components/doom/prboom/p_inter.c +++ b/components/doom/prboom/p_inter.c @@ -113,6 +113,11 @@ static boolean P_GiveAmmo(player_t *player, ammotype_t ammo, int num) else num = clipammo[ammo]/2; + // [WILLIAM] - trigger haptic effect for the player picking up ammo + // printf("Player %d picked up ammo %d\n", + // player - players, num); + R_PlayerPickupAmmo(player, ammo, num); + // give double ammo in trainer mode, you'll need in nightmare if (gameskill == sk_baby || gameskill == sk_nightmare) num <<= 1; @@ -210,6 +215,11 @@ static boolean P_GiveWeapon(player_t *player, weapontype_t weapon, boolean dropp gaveweapon = true; player->weaponowned[weapon] = true; player->pendingweapon = weapon; + // [WILLIAM] - trigger haptic effect for the player picking up a weapon + // + // printf("Player %d picked up weapon %d\n", + // player - players, weapon); + R_PlayerPickupWeapon(player, weapon); } return gaveweapon || gaveammo; } @@ -227,6 +237,10 @@ static boolean P_GiveBody(player_t *player, int num) if (player->health > maxhealth) player->health = maxhealth; player->mo->health = player->health; + // [WILLIAM] - trigger haptic effect for the player picking up health + // printf("Player %d picked up health %d\n", + // player - players, num); + R_PlayerPickupHealth(player, num); return true; } @@ -243,6 +257,10 @@ static boolean P_GiveArmor(player_t *player, int armortype) return false; // don't pick up player->armortype = armortype; player->armorpoints = hits; + // [WILLIAM] - trigger haptic effect for the player picking up armor + // printf("Player %d picked up armor %d\n", + // player - players, armortype); + R_PlayerPickupArmor(player, armortype); return true; } @@ -256,6 +274,11 @@ static void P_GiveCard(player_t *player, card_t card) return; player->bonuscount = BONUSADD; player->cards[card] = 1; + + // [WILLIAM] - trigger haptic effect for the player picking up a card + // printf("Player %d picked up card %d\n", + // player - players, card); + R_PlayerPickupCard(player, card); } // @@ -289,6 +312,12 @@ boolean P_GivePower(player_t *player, int power) if (player->powers[power] >= 0) player->powers[power] = tics[power]; + + // [WILLIAM] - trigger haptic effect for the player picking up a powerup + // printf("Player %d picked up powerup %d\n", + // player - players, power); + R_PlayerPickupPowerUp(player, power); + return true; } @@ -332,6 +361,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher) // bonus items case SPR_BON1: + R_PlayerPickupHealth(player, 1); player->health++; // can go over 100% if (player->health > (maxhealth * 2)) player->health = (maxhealth * 2); @@ -340,6 +370,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher) break; case SPR_BON2: + R_PlayerPickupArmor(player, 1); player->armorpoints++; // can go over 100% if (player->armorpoints > max_armor) player->armorpoints = max_armor; @@ -349,6 +380,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher) break; case SPR_SOUL: + R_PlayerPickupHealth(player, soul_health); player->health += soul_health; if (player->health > max_soul) player->health = max_soul; @@ -360,6 +392,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher) case SPR_MEGA: if (gamemode != commercial) return; + R_PlayerPickupHealth(player, mega_health); player->health = mega_health; player->mo->health = player->health; P_GiveArmor (player,blue_armor_class); @@ -808,9 +841,10 @@ void P_DamageMobj(mobj_t *target,mobj_t *inflictor, mobj_t *source, int damage) (player->cheats&CF_GODMODE || player->powers[pw_invulnerability])) return; + int saved = 0; if (player->armortype) { - int saved = player->armortype == 1 ? damage/3 : damage/2; + saved = player->armortype == 1 ? damage/3 : damage/2; if (player->armorpoints <= saved) { // armor is used up @@ -825,6 +859,14 @@ void P_DamageMobj(mobj_t *target,mobj_t *inflictor, mobj_t *source, int damage) if (player->health < 0) player->health = 0; + // [WILLIAM]: trigger haptic effect for the player getting injured based + // on the amount of damage received (armor/base). Use damage + // (hits to health) and saved (hits to armor) to determine. + // + // printf("Player %d took %d damage (%d saved)\n", + // player - players, damage, saved); + R_PlayerHurt(player, damage, saved); + player->attacker = source; player->damagecount += damage; // add damage after armor / invuln diff --git a/components/doom/prboom/p_map.c b/components/doom/prboom/p_map.c index 8db3443..52994d8 100644 --- a/components/doom/prboom/p_map.c +++ b/components/doom/prboom/p_map.c @@ -1727,7 +1727,7 @@ boolean PTR_NoWayTraverse(intercept_t* in) // Looks for special lines in front of the player to activate. // void P_UseLines (player_t* player) - { +{ int angle; fixed_t x1; fixed_t y1; @@ -1749,10 +1749,12 @@ void P_UseLines (player_t* player) // // This added test makes the "oof" sound work on 2s lines -- killough: - if (P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse )) - if (!comp[comp_sound] && !P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_NoWayTraverse )) + if (P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse )) { + if (!comp[comp_sound] && !P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_NoWayTraverse )) { S_StartSound (usething, sfx_noway); + } } +} // diff --git a/components/doom/prboom/p_pspr.c b/components/doom/prboom/p_pspr.c index 8b4579e..bb59de3 100644 --- a/components/doom/prboom/p_pspr.c +++ b/components/doom/prboom/p_pspr.c @@ -282,6 +282,12 @@ static void P_FireWeapon(player_t *player) newstate = weaponinfo[player->readyweapon].atkstate; P_SetPsprite(player, ps_weapon, newstate); P_NoiseAlert(player->mo, player->mo); + // [WILLIAM]: trigger haptic effect for firing the weapon based on the + // weaponinfo[player->readyweapon]. player->readyweapon is the + // weapon enum (e.g. 0 is fist, 1 is gun, 2 is shotgun, etc.). + // + // printf("P_FireWeapon: %d\n", player->readyweapon); + R_PlayerFire(player); } // diff --git a/components/doom/prboom/p_switch.c b/components/doom/prboom/p_switch.c index 05883fa..aa6220d 100644 --- a/components/doom/prboom/p_switch.c +++ b/components/doom/prboom/p_switch.c @@ -318,9 +318,11 @@ P_UseSpecialLine linefunc = EV_DoGenCrusher; } - if (linefunc) + if (linefunc) { + // [WILLIAM] - Callback to trigger haptics when using a switch + R_PlayerInteract(thing->player, line->special); switch((line->special & TriggerType) >> TriggerTypeShift) - { + { case PushOnce: if (!side) if (linefunc(line)) @@ -340,7 +342,8 @@ P_UseSpecialLine return true; default: // if not a switch/push type, do nothing here return false; - } + } + } } // Switches that other things can activate. @@ -372,6 +375,11 @@ P_UseSpecialLine if (!P_CheckTag(line)) //jff 2/27/98 disallow zero tag on some types return false; + if (thing->player) { + // [WILLIAM] - Callback to trigger haptics when using a switch + R_PlayerInteract(thing->player, line->special); + } + // Dispatch to handler according to linedef type switch (line->special) { diff --git a/components/doom/prboom/r_main.h b/components/doom/prboom/r_main.h index 89a36ee..0845cdb 100644 --- a/components/doom/prboom/r_main.h +++ b/components/doom/prboom/r_main.h @@ -117,4 +117,29 @@ void R_Init(void); // Called by startup code. void R_SetViewSize(int blocks); // Called by M_Responder. void R_ExecuteSetViewSize(void); // cph - called by D_Display to complete a view resize +// +// HAPTICS - functions to call for various events that should trigger haptics +// + +// called when the player fires a weapon, can get player->readyweapon to know +// which weapon to use for the haptic feedback +void R_PlayerFire(player_t *player); + +// called when the player picks up a weapon, can get player->readyweapon to know +// which weapon +void R_PlayerPickupWeapon(player_t *player, int weapon); + +void R_PlayerPickupAmmo(player_t *player, ammotype_t ammo, int num); +void R_PlayerPickupHealth(player_t *player, int health); +void R_PlayerPickupArmor(player_t *player, int armor); +void R_PlayerPickupCard(player_t *player, card_t card); +void R_PlayerPickupPowerUp(player_t *player, int powerup); + +void R_PlayerInteract(player_t *player, int special); + +// called when the player is hurt. damage is the amount of health lost, saved is +// the amount of health saved by armor (which is the same as the amount of armor +// lost) +void R_PlayerHurt(player_t *player, int damage, int saved); + #endif diff --git a/components/doom/src/doom.cpp b/components/doom/src/doom.cpp index f080672..c3c4ea4 100644 --- a/components/doom/src/doom.cpp +++ b/components/doom/src/doom.cpp @@ -21,6 +21,32 @@ static std::unique_ptr audio_task; static const char *doom_argv[10]; +enum class WeaponHaptics : int { + FIST = 3, + PISTOL = 2, + SHOTGUN = 10, + CHAINGUN = 12, + ROCKET_LAUNCHER = 27, + PLASMA_RIFLE = 14, + BFG9000 = 47, + CHAINSAW = 15, + SUPER_SHOTGUN = 52 +}; + +// NOTE: The order of the enum values must match the order of the weapons in the +// game, which is the wp_* enum values defined in doomdef.h +static constexpr int WeaponHapticLookup[] = { + (int)WeaponHaptics::FIST, + (int)WeaponHaptics::PISTOL, + (int)WeaponHaptics::SHOTGUN, + (int)WeaponHaptics::CHAINGUN, + (int)WeaponHaptics::ROCKET_LAUNCHER, + (int)WeaponHaptics::PLASMA_RIFLE, + (int)WeaponHaptics::BFG9000, + (int)WeaponHaptics::CHAINSAW, + (int)WeaponHaptics::SUPER_SHOTGUN +}; + // prboom includes extern "C" { ///////////////////////////////////////////// @@ -96,6 +122,76 @@ extern "C" { {(int)GamepadState::Button::Y, &key_weapontoggle}, }; + void R_PlayerFire(player_t *player) { + static auto& box = BoxEmu::get(); + int weapon_fired = player->readyweapon; + if (weapon_fired >= 0 && weapon_fired < sizeof(WeaponHapticLookup) / sizeof(WeaponHapticLookup[0])) { + int haptic_effect_index = WeaponHapticLookup[weapon_fired]; + box.play_haptic_effect(haptic_effect_index); + } else { + // Handle invalid weapon index (e.g., log an error or use a default effect) + // For now, we skip playing the haptic effect. + // Example: box.play_haptic_effect(DEFAULT_HAPTIC_EFFECT); + } + } + + void R_PlayerHurt(player_t *player, int damage, int saved) { + static auto& box = BoxEmu::get(); + int haptic_effect_index = 0; + if (damage > 5) { + // 70 - transition ramp down long smooth 1 - 100 to 0% + // 75 - transition ramp down short smooth 2 - 100 to 0% + haptic_effect_index = saved > 0 ? 70 : 75; + } else if (damage > 0) { + // 78 - transition ramp down medium sharp 1 - 100 to 0% + // 64 - transition hum 100% + haptic_effect_index = saved > 0 ? 78 : 64; + } + box.play_haptic_effect(haptic_effect_index); + } + + void R_PlayerInteract(player_t *player, int special) { + static auto& box = BoxEmu::get(); + // play 4 (sharp click - 100%) + box.play_haptic_effect(4); + } + + void R_PlayerPickupWeapon(player_t *player, int weapon) { + static auto& box = BoxEmu::get(); + // play 29 (short double click strong 3 - 60%) + box.play_haptic_effect(29); + } + + void R_PlayerPickupAmmo(player_t *player, ammotype_t ammo, int num) { + static auto& box = BoxEmu::get(); + // play 34 (short double sharp tick 1 - 100%) + box.play_haptic_effect(34); + } + + void R_PlayerPickupHealth(player_t *player, int health) { + static auto& box = BoxEmu::get(); + // play 18 (strong click 2 - 80%) + box.play_haptic_effect(18); + } + + void R_PlayerPickupArmor(player_t *player, int armor) { + static auto& box = BoxEmu::get(); + // play 19 (strong click 3 - 60%) + box.play_haptic_effect(19); + } + + void R_PlayerPickupCard(player_t *player, card_t card) { + static auto& box = BoxEmu::get(); + // play 5 (sharp click - 60%) + box.play_haptic_effect(5); + } + + void R_PlayerPickupPowerUp(player_t *player, int powerup) { + static auto& box = BoxEmu::get(); + // play 12 (triple click - 100%) + box.play_haptic_effect(12); + } + void I_StartFrame(void) { } @@ -244,7 +340,7 @@ extern "C" { if (haveSFX) { int16_t *audioBuffer = (int16_t *)mixbuffer; - int16_t *audioBufferEnd = audioBuffer + AUDIO_BUFFER_LENGTH; + const int16_t *audioBufferEnd = audioBuffer + AUDIO_BUFFER_LENGTH; while (audioBuffer < audioBufferEnd) { int totalSample = 0; int totalSources = 0; diff --git a/components/gui/include/gui.hpp b/components/gui/include/gui.hpp index 1294f19..eb6b35d 100644 --- a/components/gui/include/gui.hpp +++ b/components/gui/include/gui.hpp @@ -103,6 +103,10 @@ class Gui { paused_ = false; } + int get_haptic_waveform() const { + return haptic_waveform_; + } + void set_haptic_waveform(int new_waveform) { if (new_waveform > 123) { new_waveform = 1; diff --git a/components/msx/src/msx.cpp b/components/msx/src/msx.cpp index a24bcad..d6f7d7e 100644 --- a/components/msx/src/msx.cpp +++ b/components/msx/src/msx.cpp @@ -274,8 +274,6 @@ void TrashMachine(void) void SetColor(byte N, byte R, byte G, byte B) { uint16_t color = make_color(R, G, B); - // color = (color >> 8) | (color << 8); - color = color; if (N) XPal[N] = color; else @@ -362,7 +360,6 @@ unsigned int WaitKey(void) // wait no more than 100ms for the user to press any key while (!wait_for_key(GamepadState::Button::ANY, true, 100)) { // wait for key - continue; } return GetKey(); diff --git a/components/pool_allocator/include/pool_allocator.h b/components/pool_allocator/include/pool_allocator.h index 86ec5d4..70a6069 100644 --- a/components/pool_allocator/include/pool_allocator.h +++ b/components/pool_allocator/include/pool_allocator.h @@ -16,7 +16,7 @@ extern "C" { void pool_create(void* region, size_t size); void pool_destroy(); -int pool_contains(void* ptr); +int pool_contains(const void* ptr); void* pool_alloc(size_t size); void pool_free(void* ptr); diff --git a/components/pool_allocator/src/pool_allocator.c b/components/pool_allocator/src/pool_allocator.c index 96851d3..c6c67eb 100644 --- a/components/pool_allocator/src/pool_allocator.c +++ b/components/pool_allocator/src/pool_allocator.c @@ -28,7 +28,7 @@ void pool_create(void* mem, size_t size) { free_list->next = NULL; } -int pool_contains(void* ptr) { +int pool_contains(const void* ptr) { return (ptr >= memory_pool && ptr < memory_pool + memory_pool_size); } diff --git a/components/shared_memory/src/shared_memory.c b/components/shared_memory/src/shared_memory.c index a7457c7..94a1b3b 100644 --- a/components/shared_memory/src/shared_memory.c +++ b/components/shared_memory/src/shared_memory.c @@ -69,7 +69,7 @@ void* shared_mem_allocate(const shared_mem_request_t* request) { } void shared_mem_clear(void) { - printf("Num bytes allocated: %d\n", current_offset_); + printf("Num bytes allocated: %d\n", (int)current_offset_); // TODO: Use SIMD-accelerated memset from ESP32s3 vector instructions memset(memory_pool_, 0, TOTAL_MEMORY_SIZE); current_offset_ = 0; diff --git a/main/cart.hpp b/main/cart.hpp index 8d406e4..d198965 100644 --- a/main/cart.hpp +++ b/main/cart.hpp @@ -284,7 +284,7 @@ class Cart { return menu_->get_selected_slot(); } - virtual std::string get_save_path(bool bypass_exist_check=false) const { + std::string get_save_path(bool bypass_exist_check=false) const { namespace fs = std::filesystem; auto save_path = savedir_ + "/" + @@ -297,7 +297,7 @@ class Cart { return ""; } - virtual std::string get_paused_image_path() const { + std::string get_paused_image_path() const { namespace fs = std::filesystem; auto save_path = savedir_ + "/paused" + @@ -305,7 +305,7 @@ class Cart { return save_path; } - virtual std::string get_screenshot_path(bool bypass_exist_check=false) const { + std::string get_screenshot_path(bool bypass_exist_check=false) const { auto save_path = get_save_path(bypass_exist_check); if (!save_path.empty()) { return save_path + get_screenshot_extension(); diff --git a/main/main.cpp b/main/main.cpp index 963d3c9..25f3372 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -88,6 +88,7 @@ extern "C" void app_main(void) { } // have broken out of the loop, let the user know we're processing... + emu.set_haptic_effect(gui.get_haptic_waveform()); emu.play_haptic_effect(); gui.pause();