Skip to content

Commit 84a317b

Browse files
finger563Copilot
andauthored
feat(doom): Add haptic feedback to doom (#102)
* feat(doom): Add haptic support to doom * Update doom to have callbacks for when the player is damaged and when they fire a weapon * Update doom implementation to trigger different haptic effects depending on the weapon fired and the amount of damage taken Haptics are awesome, and with doom we have the actual source code so we can do some fun stuff :) Build and run `main` on Box-Emu v0 which has haptics and ensure it feels good. * fix sa * improve maintainability * more haptics * add player interaction haptics and fix sa * update readme * Update components/doom/src/doom.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent ee3115e commit 84a317b

File tree

14 files changed

+209
-17
lines changed

14 files changed

+209
-17
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ ESP32-S3-BOX-3 which provides:
5757
- Genesis emulator (gwenesis) - full speed / buttery smooth when muted; unmuted it runs a little slower but has nice sound
5858
- 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)
5959
- 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.
60+
- Added haptic feedback to doom for when the player
61+
- Fires a weapon (depending on the weapon that is fired)
62+
- Receives damage (depending on amount of health / armor damage received)
63+
- Interacts, e.g. with doors
64+
- Picks up a weapon
65+
- Picks up ammo
66+
- Picks up health
67+
- Picks up armor
68+
- Picks up a power up
69+
- Picks up a card / key
6070
- LVGL main menu with rom select (including boxart display) and settings page
6171
(all generated from Squareline Studio)
6272
- LVGL emulation paused menu with save slot select, save slot image display,
@@ -122,6 +132,7 @@ This project has the following features (still WIP):
122132
- [x] MSX emulator
123133
- [x] Sega Mega Drive / Genesis emulator
124134
- [x] Doom
135+
- [x] Haptics :rocket:
125136
- [ ] Dark Forces (WIP)
126137
- [ ] SNES emulator (WIP)
127138
- [x] uSD card (FAT) filesystem over SPI

components/doom/prboom/p_inter.c

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ static boolean P_GiveAmmo(player_t *player, ammotype_t ammo, int num)
113113
else
114114
num = clipammo[ammo]/2;
115115

116+
// [WILLIAM] - trigger haptic effect for the player picking up ammo
117+
// printf("Player %d picked up ammo %d\n",
118+
// player - players, num);
119+
R_PlayerPickupAmmo(player, ammo, num);
120+
116121
// give double ammo in trainer mode, you'll need in nightmare
117122
if (gameskill == sk_baby || gameskill == sk_nightmare)
118123
num <<= 1;
@@ -210,6 +215,11 @@ static boolean P_GiveWeapon(player_t *player, weapontype_t weapon, boolean dropp
210215
gaveweapon = true;
211216
player->weaponowned[weapon] = true;
212217
player->pendingweapon = weapon;
218+
// [WILLIAM] - trigger haptic effect for the player picking up a weapon
219+
//
220+
// printf("Player %d picked up weapon %d\n",
221+
// player - players, weapon);
222+
R_PlayerPickupWeapon(player, weapon);
213223
}
214224
return gaveweapon || gaveammo;
215225
}
@@ -227,6 +237,10 @@ static boolean P_GiveBody(player_t *player, int num)
227237
if (player->health > maxhealth)
228238
player->health = maxhealth;
229239
player->mo->health = player->health;
240+
// [WILLIAM] - trigger haptic effect for the player picking up health
241+
// printf("Player %d picked up health %d\n",
242+
// player - players, num);
243+
R_PlayerPickupHealth(player, num);
230244
return true;
231245
}
232246

@@ -243,6 +257,10 @@ static boolean P_GiveArmor(player_t *player, int armortype)
243257
return false; // don't pick up
244258
player->armortype = armortype;
245259
player->armorpoints = hits;
260+
// [WILLIAM] - trigger haptic effect for the player picking up armor
261+
// printf("Player %d picked up armor %d\n",
262+
// player - players, armortype);
263+
R_PlayerPickupArmor(player, armortype);
246264
return true;
247265
}
248266

@@ -256,6 +274,11 @@ static void P_GiveCard(player_t *player, card_t card)
256274
return;
257275
player->bonuscount = BONUSADD;
258276
player->cards[card] = 1;
277+
278+
// [WILLIAM] - trigger haptic effect for the player picking up a card
279+
// printf("Player %d picked up card %d\n",
280+
// player - players, card);
281+
R_PlayerPickupCard(player, card);
259282
}
260283

261284
//
@@ -289,6 +312,12 @@ boolean P_GivePower(player_t *player, int power)
289312

290313
if (player->powers[power] >= 0)
291314
player->powers[power] = tics[power];
315+
316+
// [WILLIAM] - trigger haptic effect for the player picking up a powerup
317+
// printf("Player %d picked up powerup %d\n",
318+
// player - players, power);
319+
R_PlayerPickupPowerUp(player, power);
320+
292321
return true;
293322
}
294323

@@ -332,6 +361,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher)
332361

333362
// bonus items
334363
case SPR_BON1:
364+
R_PlayerPickupHealth(player, 1);
335365
player->health++; // can go over 100%
336366
if (player->health > (maxhealth * 2))
337367
player->health = (maxhealth * 2);
@@ -340,6 +370,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher)
340370
break;
341371

342372
case SPR_BON2:
373+
R_PlayerPickupArmor(player, 1);
343374
player->armorpoints++; // can go over 100%
344375
if (player->armorpoints > max_armor)
345376
player->armorpoints = max_armor;
@@ -349,6 +380,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher)
349380
break;
350381

351382
case SPR_SOUL:
383+
R_PlayerPickupHealth(player, soul_health);
352384
player->health += soul_health;
353385
if (player->health > max_soul)
354386
player->health = max_soul;
@@ -360,6 +392,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher)
360392
case SPR_MEGA:
361393
if (gamemode != commercial)
362394
return;
395+
R_PlayerPickupHealth(player, mega_health);
363396
player->health = mega_health;
364397
player->mo->health = player->health;
365398
P_GiveArmor (player,blue_armor_class);
@@ -808,9 +841,10 @@ void P_DamageMobj(mobj_t *target,mobj_t *inflictor, mobj_t *source, int damage)
808841
(player->cheats&CF_GODMODE || player->powers[pw_invulnerability]))
809842
return;
810843

844+
int saved = 0;
811845
if (player->armortype)
812846
{
813-
int saved = player->armortype == 1 ? damage/3 : damage/2;
847+
saved = player->armortype == 1 ? damage/3 : damage/2;
814848
if (player->armorpoints <= saved)
815849
{
816850
// armor is used up
@@ -825,6 +859,14 @@ void P_DamageMobj(mobj_t *target,mobj_t *inflictor, mobj_t *source, int damage)
825859
if (player->health < 0)
826860
player->health = 0;
827861

862+
// [WILLIAM]: trigger haptic effect for the player getting injured based
863+
// on the amount of damage received (armor/base). Use damage
864+
// (hits to health) and saved (hits to armor) to determine.
865+
//
866+
// printf("Player %d took %d damage (%d saved)\n",
867+
// player - players, damage, saved);
868+
R_PlayerHurt(player, damage, saved);
869+
828870
player->attacker = source;
829871
player->damagecount += damage; // add damage after armor / invuln
830872

components/doom/prboom/p_map.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1727,7 +1727,7 @@ boolean PTR_NoWayTraverse(intercept_t* in)
17271727
// Looks for special lines in front of the player to activate.
17281728
//
17291729
void P_UseLines (player_t* player)
1730-
{
1730+
{
17311731
int angle;
17321732
fixed_t x1;
17331733
fixed_t y1;
@@ -1749,10 +1749,12 @@ void P_UseLines (player_t* player)
17491749
//
17501750
// This added test makes the "oof" sound work on 2s lines -- killough:
17511751

1752-
if (P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse ))
1753-
if (!comp[comp_sound] && !P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_NoWayTraverse ))
1752+
if (P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse )) {
1753+
if (!comp[comp_sound] && !P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_NoWayTraverse )) {
17541754
S_StartSound (usething, sfx_noway);
1755+
}
17551756
}
1757+
}
17561758

17571759

17581760
//

components/doom/prboom/p_pspr.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,12 @@ static void P_FireWeapon(player_t *player)
282282
newstate = weaponinfo[player->readyweapon].atkstate;
283283
P_SetPsprite(player, ps_weapon, newstate);
284284
P_NoiseAlert(player->mo, player->mo);
285+
// [WILLIAM]: trigger haptic effect for firing the weapon based on the
286+
// weaponinfo[player->readyweapon]. player->readyweapon is the
287+
// weapon enum (e.g. 0 is fist, 1 is gun, 2 is shotgun, etc.).
288+
//
289+
// printf("P_FireWeapon: %d\n", player->readyweapon);
290+
R_PlayerFire(player);
285291
}
286292

287293
//

components/doom/prboom/p_switch.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,11 @@ P_UseSpecialLine
318318
linefunc = EV_DoGenCrusher;
319319
}
320320

321-
if (linefunc)
321+
if (linefunc) {
322+
// [WILLIAM] - Callback to trigger haptics when using a switch
323+
R_PlayerInteract(thing->player, line->special);
322324
switch((line->special & TriggerType) >> TriggerTypeShift)
323-
{
325+
{
324326
case PushOnce:
325327
if (!side)
326328
if (linefunc(line))
@@ -340,7 +342,8 @@ P_UseSpecialLine
340342
return true;
341343
default: // if not a switch/push type, do nothing here
342344
return false;
343-
}
345+
}
346+
}
344347
}
345348

346349
// Switches that other things can activate.
@@ -372,6 +375,11 @@ P_UseSpecialLine
372375
if (!P_CheckTag(line)) //jff 2/27/98 disallow zero tag on some types
373376
return false;
374377

378+
if (thing->player) {
379+
// [WILLIAM] - Callback to trigger haptics when using a switch
380+
R_PlayerInteract(thing->player, line->special);
381+
}
382+
375383
// Dispatch to handler according to linedef type
376384
switch (line->special)
377385
{

components/doom/prboom/r_main.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,29 @@ void R_Init(void); // Called by startup code.
117117
void R_SetViewSize(int blocks); // Called by M_Responder.
118118
void R_ExecuteSetViewSize(void); // cph - called by D_Display to complete a view resize
119119

120+
//
121+
// HAPTICS - functions to call for various events that should trigger haptics
122+
//
123+
124+
// called when the player fires a weapon, can get player->readyweapon to know
125+
// which weapon to use for the haptic feedback
126+
void R_PlayerFire(player_t *player);
127+
128+
// called when the player picks up a weapon, can get player->readyweapon to know
129+
// which weapon
130+
void R_PlayerPickupWeapon(player_t *player, int weapon);
131+
132+
void R_PlayerPickupAmmo(player_t *player, ammotype_t ammo, int num);
133+
void R_PlayerPickupHealth(player_t *player, int health);
134+
void R_PlayerPickupArmor(player_t *player, int armor);
135+
void R_PlayerPickupCard(player_t *player, card_t card);
136+
void R_PlayerPickupPowerUp(player_t *player, int powerup);
137+
138+
void R_PlayerInteract(player_t *player, int special);
139+
140+
// called when the player is hurt. damage is the amount of health lost, saved is
141+
// the amount of health saved by armor (which is the same as the amount of armor
142+
// lost)
143+
void R_PlayerHurt(player_t *player, int damage, int saved);
144+
120145
#endif

components/doom/src/doom.cpp

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,32 @@ static std::unique_ptr<espp::Task> audio_task;
2121

2222
static const char *doom_argv[10];
2323

24+
enum class WeaponHaptics : int {
25+
FIST = 3,
26+
PISTOL = 2,
27+
SHOTGUN = 10,
28+
CHAINGUN = 12,
29+
ROCKET_LAUNCHER = 27,
30+
PLASMA_RIFLE = 14,
31+
BFG9000 = 47,
32+
CHAINSAW = 15,
33+
SUPER_SHOTGUN = 52
34+
};
35+
36+
// NOTE: The order of the enum values must match the order of the weapons in the
37+
// game, which is the wp_* enum values defined in doomdef.h
38+
static constexpr int WeaponHapticLookup[] = {
39+
(int)WeaponHaptics::FIST,
40+
(int)WeaponHaptics::PISTOL,
41+
(int)WeaponHaptics::SHOTGUN,
42+
(int)WeaponHaptics::CHAINGUN,
43+
(int)WeaponHaptics::ROCKET_LAUNCHER,
44+
(int)WeaponHaptics::PLASMA_RIFLE,
45+
(int)WeaponHaptics::BFG9000,
46+
(int)WeaponHaptics::CHAINSAW,
47+
(int)WeaponHaptics::SUPER_SHOTGUN
48+
};
49+
2450
// prboom includes
2551
extern "C" {
2652
/////////////////////////////////////////////
@@ -96,6 +122,76 @@ extern "C" {
96122
{(int)GamepadState::Button::Y, &key_weapontoggle},
97123
};
98124

125+
void R_PlayerFire(player_t *player) {
126+
static auto& box = BoxEmu::get();
127+
int weapon_fired = player->readyweapon;
128+
if (weapon_fired >= 0 && weapon_fired < sizeof(WeaponHapticLookup) / sizeof(WeaponHapticLookup[0])) {
129+
int haptic_effect_index = WeaponHapticLookup[weapon_fired];
130+
box.play_haptic_effect(haptic_effect_index);
131+
} else {
132+
// Handle invalid weapon index (e.g., log an error or use a default effect)
133+
// For now, we skip playing the haptic effect.
134+
// Example: box.play_haptic_effect(DEFAULT_HAPTIC_EFFECT);
135+
}
136+
}
137+
138+
void R_PlayerHurt(player_t *player, int damage, int saved) {
139+
static auto& box = BoxEmu::get();
140+
int haptic_effect_index = 0;
141+
if (damage > 5) {
142+
// 70 - transition ramp down long smooth 1 - 100 to 0%
143+
// 75 - transition ramp down short smooth 2 - 100 to 0%
144+
haptic_effect_index = saved > 0 ? 70 : 75;
145+
} else if (damage > 0) {
146+
// 78 - transition ramp down medium sharp 1 - 100 to 0%
147+
// 64 - transition hum 100%
148+
haptic_effect_index = saved > 0 ? 78 : 64;
149+
}
150+
box.play_haptic_effect(haptic_effect_index);
151+
}
152+
153+
void R_PlayerInteract(player_t *player, int special) {
154+
static auto& box = BoxEmu::get();
155+
// play 4 (sharp click - 100%)
156+
box.play_haptic_effect(4);
157+
}
158+
159+
void R_PlayerPickupWeapon(player_t *player, int weapon) {
160+
static auto& box = BoxEmu::get();
161+
// play 29 (short double click strong 3 - 60%)
162+
box.play_haptic_effect(29);
163+
}
164+
165+
void R_PlayerPickupAmmo(player_t *player, ammotype_t ammo, int num) {
166+
static auto& box = BoxEmu::get();
167+
// play 34 (short double sharp tick 1 - 100%)
168+
box.play_haptic_effect(34);
169+
}
170+
171+
void R_PlayerPickupHealth(player_t *player, int health) {
172+
static auto& box = BoxEmu::get();
173+
// play 18 (strong click 2 - 80%)
174+
box.play_haptic_effect(18);
175+
}
176+
177+
void R_PlayerPickupArmor(player_t *player, int armor) {
178+
static auto& box = BoxEmu::get();
179+
// play 19 (strong click 3 - 60%)
180+
box.play_haptic_effect(19);
181+
}
182+
183+
void R_PlayerPickupCard(player_t *player, card_t card) {
184+
static auto& box = BoxEmu::get();
185+
// play 5 (sharp click - 60%)
186+
box.play_haptic_effect(5);
187+
}
188+
189+
void R_PlayerPickupPowerUp(player_t *player, int powerup) {
190+
static auto& box = BoxEmu::get();
191+
// play 12 (triple click - 100%)
192+
box.play_haptic_effect(12);
193+
}
194+
99195
void I_StartFrame(void) {
100196
}
101197

@@ -244,7 +340,7 @@ extern "C" {
244340

245341
if (haveSFX) {
246342
int16_t *audioBuffer = (int16_t *)mixbuffer;
247-
int16_t *audioBufferEnd = audioBuffer + AUDIO_BUFFER_LENGTH;
343+
const int16_t *audioBufferEnd = audioBuffer + AUDIO_BUFFER_LENGTH;
248344
while (audioBuffer < audioBufferEnd) {
249345
int totalSample = 0;
250346
int totalSources = 0;

components/gui/include/gui.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ class Gui {
103103
paused_ = false;
104104
}
105105

106+
int get_haptic_waveform() const {
107+
return haptic_waveform_;
108+
}
109+
106110
void set_haptic_waveform(int new_waveform) {
107111
if (new_waveform > 123) {
108112
new_waveform = 1;

0 commit comments

Comments
 (0)