1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
/**
* @file sys_rumble.c
*
* This file implements a manager for storing and processing rumble pak requests made by the game state. Despite some
* parts of the system appearing to accommodate all four controller ports, only controller 1 will rumble according to
* the processed requests.
* This file is half of the system that runs on the padmgr thread alongside controller communications. The rest of the
* system that receives the requests from the game state runs on the graph thread and is implemented in `z_rumble.c`.
*
* @see RumbleMgr
* @see z_rumble.c
*
* @note Original filename is likely sys_vibrate.c or similar as it is ordered after sys_ucode.c
*/
#include "global.h"
/**
* Rumble manager update, runs on Vertical Retrace on the padmgr thread.
*/
void RumbleMgr_Update(RumbleMgr* rumbleMgr) {
static u8 sWasEnabled = true;
s32 i;
s32 strength;
s32 strongestIndex = -1;
// Clear enable status for all controllers
for (i = 0; i < MAXCONTROLLERS; i++) {
rumbleMgr->rumbleEnable[i] = false;
}
if (!rumbleMgr->updateEnabled) {
if (sWasEnabled) {
// If it was previously enabled, reset pak type
for (i = 0; i < MAXCONTROLLERS; i++) {
gPadMgr.pakType[i] = CONT_PAK_NONE;
}
}
sWasEnabled = rumbleMgr->updateEnabled;
return;
}
sWasEnabled = rumbleMgr->updateEnabled;
if (rumbleMgr->state == RUMBLE_STATE_RESET) {
// Reset
for (i = 0; i < MAXCONTROLLERS; i++) {
gPadMgr.pakType[i] = CONT_PAK_NONE;
}
for (i = 0; i < RUMBLE_MAX_REQUESTS; i++) {
rumbleMgr->reqAccumulators[i] = 0;
rumbleMgr->reqDecreaseRates[i] = 0;
rumbleMgr->reqDurations[i] = 0;
rumbleMgr->reqStrengths[i] = 0;
}
rumbleMgr->onTimer = rumbleMgr->offTimer = rumbleMgr->overrideStrength = rumbleMgr->overrideDuration =
rumbleMgr->overrideDecreaseRate = rumbleMgr->overrideAccumulator = 0;
rumbleMgr->state = RUMBLE_STATE_RUNNING;
}
if (rumbleMgr->state != RUMBLE_STATE_CLEAR) {
// Search for index with largest strength
for (i = 0; i < RUMBLE_MAX_REQUESTS; i++) {
if (rumbleMgr->reqStrengths[i] != 0) {
// Non-empty request slot
if (rumbleMgr->reqDurations[i] > 0) {
rumbleMgr->reqDurations[i]--;
} else {
// After duration, decrease the strength by the decrease rate
strength = rumbleMgr->reqStrengths[i] - rumbleMgr->reqDecreaseRates[i];
rumbleMgr->reqStrengths[i] = MAX(strength, 0);
}
// Increment accumulator by the strength
strength = rumbleMgr->reqAccumulators[i] + rumbleMgr->reqStrengths[i];
rumbleMgr->reqAccumulators[i] = strength;
if (strongestIndex == -1) {
strongestIndex = i;
// Rumble is enabled on the controller only when there is overflow of the accumulator, overflow
// will happen more often for larger request strengths making it feel stronger to the player
rumbleMgr->rumbleEnable[0] = strength > 255;
} else if (rumbleMgr->reqStrengths[i] > rumbleMgr->reqStrengths[strongestIndex]) {
strongestIndex = i;
rumbleMgr->rumbleEnable[0] = strength > 255;
}
}
}
if (rumbleMgr->overrideStrength != 0) {
// Set override
if (rumbleMgr->overrideDuration > 0) {
rumbleMgr->overrideDuration--;
} else {
// Once the duration is over, start decrementing the strength
strength = rumbleMgr->overrideStrength - rumbleMgr->overrideDecreaseRate;
rumbleMgr->overrideStrength = MAX(strength, 0);
}
// Increment accumulator, set rumble enabled on overflow
strength = rumbleMgr->overrideAccumulator + rumbleMgr->overrideStrength;
rumbleMgr->overrideAccumulator = strength;
rumbleMgr->rumbleEnable[0] = strength > 255;
}
if (rumbleMgr->overrideStrength != 0) {
strength = rumbleMgr->overrideStrength;
} else {
strength = (strongestIndex == -1) ? 0 : rumbleMgr->reqStrengths[strongestIndex];
}
if (strength == 0) {
// No rumble
if ((++rumbleMgr->offTimer) > 5) {
// After 5 VIs with no rumble, reset the rumble on timer
rumbleMgr->onTimer = 0;
rumbleMgr->offTimer = 5;
}
} else {
// Rumble
rumbleMgr->offTimer = 0;
if ((++rumbleMgr->onTimer) > 7200) { // 2 minutes at 60 VI/s, 2 minutes 24 seconds at 50 VI/s
// Clear all requests if rumble has been on for too long
rumbleMgr->state = RUMBLE_STATE_CLEAR;
}
}
} else {
// Clear all requests
for (i = 0; i < RUMBLE_MAX_REQUESTS; i++) {
rumbleMgr->reqAccumulators[i] = 0;
rumbleMgr->reqDecreaseRates[i] = 0;
rumbleMgr->reqDurations[i] = 0;
rumbleMgr->reqStrengths[i] = 0;
}
// Clear override request
rumbleMgr->onTimer = rumbleMgr->offTimer = rumbleMgr->overrideStrength = rumbleMgr->overrideDuration =
rumbleMgr->overrideDecreaseRate = rumbleMgr->overrideAccumulator = 0;
}
}
void RumbleMgr_Init(RumbleMgr* rumbleMgr) {
bzero(rumbleMgr, sizeof(RumbleMgr));
rumbleMgr->state = RUMBLE_STATE_RESET;
rumbleMgr->updateEnabled = true;
}
void RumbleMgr_Destroy(RumbleMgr* rumbleMgr) {
#if OOT_DEBUG
bzero(rumbleMgr, sizeof(RumbleMgr));
#endif
}
|