1 #include "burnint.h"
2 #include "burn_gun.h"
3
4 // Generic Light Gun support for FBA
5 // written by Barry Harris (Treble Winner) based on the code in Kev's opwolf driver
6 // Trackball section by dink
7
8 INT32 nBurnGunNumPlayers = 0;
9 bool bBurnGunAutoHide = 1;
10 static bool bBurnGunDrawTargets = true;
11
12 static INT32 Using_Trackball = 0;
13
14 static INT32 nBurnGunMaxX = 0;
15 static INT32 nBurnGunMaxY = 0;
16
17 INT32 BurnGunX[MAX_GUNS];
18 INT32 BurnGunY[MAX_GUNS];
19
20 struct GunWrap { INT32 xmin; INT32 xmax; INT32 ymin; INT32 ymax; };
21 static GunWrap BurnGunWrapInf[MAX_GUNS]; // Paddle/Dial use
22
23 #define a 0,
24 #define b 1,
25
26 UINT8 BurnGunTargetData[18][18] = {
27 { a a a a a a a a b a a a a a a a a a },
28 { a a a a a a b b b b b a a a a a a a },
29 { a a a a b b a a b a a b b a a a a a },
30 { a a a b a a a a b a a a a b a a a a },
31 { a a b a a a a a b a a a a a b a a a },
32 { a a b a a a a b b b a a a a b a a a },
33 { a b a a a a b b b b b a a a a b a a },
34 { a b a a a b b a a a a b a a a b a a },
35 { b b b b b b b a a a b b b b b b b a },
36 { a b a a a b b a a a a b b a a b a a },
37 { a b a a a a b a b a b b a a a b a a },
38 { a a b a a a a b b b b a a a b a a a },
39 { a a b a a a a a b b a a a a b a a a },
40 { a a a b a a a a b a a a a b a a a a },
41 { a a a a b b a a b a a b b a a a a a },
42 { a a a a a a b b b b b a a a a a a a },
43 { a a a a a a a a b a a a a a a a a a },
44 { a a a a a a a a a a a a a a a a a a },
45 };
46 #undef b
47 #undef a
48
49 #define GunTargetHideTime (60 * 4) /* 4 seconds @ 60 fps */
50 static INT32 GunTargetTimer[MAX_GUNS] = {0, 0, 0, 0};
51 static INT32 GunTargetLastX[MAX_GUNS] = {0, 0, 0, 0};
52 static INT32 GunTargetLastY[MAX_GUNS] = {0, 0, 0, 0};
53
GunTargetUpdate(INT32 player)54 static void GunTargetUpdate(INT32 player)
55 {
56 if (GunTargetLastX[player] != BurnGunReturnX(player) || GunTargetLastY[player] != BurnGunReturnY(player)) {
57 GunTargetLastX[player] = BurnGunReturnX(player);
58 GunTargetLastY[player] = BurnGunReturnY(player);
59 GunTargetTimer[player] = nCurrentFrame;
60 }
61 }
62
GunTargetShouldDraw(INT32 player)63 static UINT8 GunTargetShouldDraw(INT32 player)
64 {
65 return ((INT32)nCurrentFrame < GunTargetTimer[player] + GunTargetHideTime);
66 }
67 #undef GunTargetHideTime
68
BurnGunIsActive()69 INT32 BurnGunIsActive()
70 {
71 return (Debug_BurnGunInitted && Using_Trackball == 0);
72 }
73
BurnGunSetCoords(INT32 player,INT32 x,INT32 y)74 void BurnGunSetCoords(INT32 player, INT32 x, INT32 y)
75 {
76 if (!Debug_BurnGunInitted) return; // callback for Libretro (fail nicely)
77
78 //BurnGunX[player] = (x * nBurnGunMaxX / 0xff) << 8; // based on 0 - 255
79 //BurnGunY[player] = (y * nBurnGunMaxY / 0xff) << 8;
80 BurnGunX[player] = (x - 8) << 8; // based on emulated resolution
81 BurnGunY[player] = (y - 8) << 8;
82 }
83
BurnGunReturnX(INT32 num)84 UINT8 BurnGunReturnX(INT32 num)
85 {
86 #if defined FBA_DEBUG
87 if (!Debug_BurnGunInitted) bprintf(PRINT_ERROR, _T("BurnGunReturnX called without init\n"));
88 if (num >= nBurnGunNumPlayers) bprintf(PRINT_ERROR, _T("BurnGunReturnX called with invalid player %x\n"), num);
89 #endif
90
91 if (num > MAX_GUNS - 1) return 0xff;
92
93 float temp = (float)((BurnGunX[num] >> 8) + 8) / nBurnGunMaxX * 0xff;
94 return (UINT8)temp;
95 }
96
BurnGunReturnY(INT32 num)97 UINT8 BurnGunReturnY(INT32 num)
98 {
99 #if defined FBA_DEBUG
100 if (!Debug_BurnGunInitted) bprintf(PRINT_ERROR, _T("BurnGunReturnY called without init\n"));
101 if (num >= nBurnGunNumPlayers) bprintf(PRINT_ERROR, _T("BurnGunReturnY called with invalid player %x\n"), num);
102 #endif
103
104 if (num > MAX_GUNS - 1) return 0xff;
105
106 float temp = (float)((BurnGunY[num] >> 8) + 8) / nBurnGunMaxY * 0xff;
107 return (UINT8)temp;
108 }
109
110 // Paddle/Dial stuff
111 static INT32 PaddleLastA[MAX_GUNS];
112 static INT32 PaddleLastB[MAX_GUNS];
113
BurnPaddleReturnA(INT32 num)114 BurnDialINF BurnPaddleReturnA(INT32 num)
115 {
116 #if defined FBA_DEBUG
117 if (!Debug_BurnGunInitted) bprintf(PRINT_ERROR, _T("BurnPaddleReturnA called without init\n"));
118 if (num >= nBurnGunNumPlayers) bprintf(PRINT_ERROR, _T("BurnPaddleReturnA called with invalid player %x\n"), num);
119 #endif
120
121 BurnDialINF dial = { 0, 0, 0 };
122
123 if (num > MAX_GUNS - 1) return dial;
124
125 INT32 PaddleA = ((BurnGunX[num] >> 8) / 0x4);
126
127 if (PaddleA < PaddleLastA[num]) {
128 dial.Velocity = (PaddleLastA[num] - PaddleA);
129 dial.Backward = 1;
130 }
131 else if (PaddleA > PaddleLastA[num]) {
132 dial.Velocity = (PaddleA - PaddleLastA[num]);
133 dial.Forward = 1;
134 }
135
136 PaddleLastA[num] = PaddleA;
137
138 return dial;
139 }
140
BurnPaddleReturnB(INT32 num)141 BurnDialINF BurnPaddleReturnB(INT32 num)
142 {
143 #if defined FBA_DEBUG
144 if (!Debug_BurnGunInitted) bprintf(PRINT_ERROR, _T("BurnPaddleReturnB called without init\n"));
145 if (num >= nBurnGunNumPlayers) bprintf(PRINT_ERROR, _T("BurnPaddleReturnB called with invalid player %x\n"), num);
146 #endif
147
148 BurnDialINF dial = { 0, 0, 0 };
149
150 if (num > MAX_GUNS - 1) return dial;
151
152 INT32 PaddleB = ((BurnGunY[num] >> 8) / 0x4);
153
154 if (PaddleB < PaddleLastB[num]) {
155 dial.Velocity = (PaddleLastB[num] - PaddleB);
156 dial.Backward = 1;
157 }
158 else if (PaddleB > PaddleLastB[num]) {
159 dial.Velocity = (PaddleB - PaddleLastB[num]);
160 dial.Forward = 1;
161 }
162
163 PaddleLastB[num] = PaddleB;
164
165 return dial;
166 }
167
168 // Trackball Helpers
169 static UINT16 TrackA[MAX_GUNS]; // trackball counters
170 static UINT16 TrackB[MAX_GUNS];
171
172 static INT32 DIAL_INC[MAX_GUNS * 2]; // velocity counter
173 static UINT8 DrvJoyT[MAX_GUNS * 4]; // direction bytemask
174 static UINT8 TrackRev[MAX_GUNS * 2]; // reversed counter logic?
175
BurnTrackballFrame(INT32 dev,INT16 PortA,INT16 PortB,INT32 VelocityStart,INT32 VelocityMax)176 void BurnTrackballFrame(INT32 dev, INT16 PortA, INT16 PortB, INT32 VelocityStart, INT32 VelocityMax)
177 {
178 memset(&DrvJoyT[dev*4], 0, 4);
179
180 DIAL_INC[(dev*2) + 0] = VelocityStart;
181 DIAL_INC[(dev*2) + 1] = VelocityStart;
182 BurnPaddleMakeInputs(dev, AnalogDeadZone(PortA), AnalogDeadZone(PortB));
183
184 BurnDialINF dial = BurnPaddleReturnA(dev);
185 if (dial.Backward) DrvJoyT[(dev*4) + 0] = 1;
186 if (dial.Forward) DrvJoyT[(dev*4) + 1] = 1;
187 DIAL_INC[(dev*2) + 0] += ((dial.Velocity > VelocityMax) ? VelocityMax : dial.Velocity);
188
189 dial = BurnPaddleReturnB(dev);
190 if (dial.Backward) DrvJoyT[(dev*4) + 2] = 1;
191 if (dial.Forward) DrvJoyT[(dev*4) + 3] = 1;
192 DIAL_INC[(dev*2) + 1] += ((dial.Velocity > VelocityMax) ? VelocityMax : dial.Velocity);
193 }
194
BurnTrackballUpdate(INT32 dev)195 void BurnTrackballUpdate(INT32 dev)
196 {
197 // PortA (usually X-Axis)
198 if (DrvJoyT[(dev*4) + 0]) { // Backward
199 if (TrackRev[(dev*2) + 0])
200 TrackA[dev] += DIAL_INC[(dev*2) + 0] / 2;
201 else
202 TrackA[dev] -= DIAL_INC[(dev*2) + 0] / 2;
203 }
204 if (DrvJoyT[(dev*4) + 1]) { // Forward
205 if (TrackRev[(dev*2) + 0])
206 TrackA[dev] -= DIAL_INC[(dev*2) + 0] / 2;
207 else
208 TrackA[dev] += DIAL_INC[(dev*2) + 0] / 2;
209 }
210 // PortB (usually Y-Axis)
211 if (DrvJoyT[(dev*4) + 2]) { // Backward
212 if (TrackRev[(dev*2) + 1])
213 TrackB[dev] += DIAL_INC[(dev*2) + 1] / 2;
214 else
215 TrackB[dev] -= DIAL_INC[(dev*2) + 1] / 2;
216 }
217 if (DrvJoyT[(dev*4) + 3]) { // Forward
218 if (TrackRev[(dev*2) + 1])
219 TrackB[dev] -= DIAL_INC[(dev*2) + 1] / 2;
220 else
221 TrackB[dev] += DIAL_INC[(dev*2) + 1] / 2;
222 }
223 }
224
BurnTrackballUpdateSlither(INT32 dev)225 void BurnTrackballUpdateSlither(INT32 dev)
226 {
227 // simulate the divider circuit on down + right(V) for Slither (taito/d_qix.cpp)
228 static INT32 flippy[2] = { 0, 0 };
229 // PortA (usually X-Axis)
230 if (DrvJoyT[(dev*4) + 0]) { // Backward
231 flippy[0] ^= 1;
232 if (flippy[0]) return;
233 if (TrackRev[(dev*2) + 0])
234 TrackA[dev] += DIAL_INC[(dev*2) + 0] / 2;
235 else
236 TrackA[dev] -= DIAL_INC[(dev*2) + 0] / 2;
237 }
238 if (DrvJoyT[(dev*4) + 1]) { // Forward
239 if (TrackRev[(dev*2) + 0])
240 TrackA[dev] -= DIAL_INC[(dev*2) + 0] / 2;
241 else
242 TrackA[dev] += DIAL_INC[(dev*2) + 0] / 2;
243 }
244 // PortB (usually Y-Axis)
245 if (DrvJoyT[(dev*4) + 2]) { // Backward
246 if (TrackRev[(dev*2) + 1])
247 TrackB[dev] += DIAL_INC[(dev*2) + 1] / 2;
248 else
249 TrackB[dev] -= DIAL_INC[(dev*2) + 1] / 2;
250 }
251 if (DrvJoyT[(dev*4) + 3]) { // Forward
252 flippy[1] ^= 1;
253 if (flippy[1]) return;
254 if (TrackRev[(dev*2) + 1])
255 TrackB[dev] -= DIAL_INC[(dev*2) + 1] / 2;
256 else
257 TrackB[dev] += DIAL_INC[(dev*2) + 1] / 2;
258 }
259 }
260
BurnTrackballRead(INT32 dev,INT32 isB)261 UINT8 BurnTrackballRead(INT32 dev, INT32 isB)
262 {
263 if (isB)
264 return TrackB[dev] & 0xff;
265 else
266 return TrackA[dev] & 0xff;
267 }
268
BurnTrackballReadWord(INT32 dev,INT32 isB)269 UINT16 BurnTrackballReadWord(INT32 dev, INT32 isB)
270 {
271 if (isB)
272 return TrackB[dev] & 0xffff;
273 else
274 return TrackA[dev] & 0xffff;
275 }
276
BurnTrackballUDLR(INT32 dev,INT32 u,INT32 d,INT32 l,INT32 r)277 void BurnTrackballUDLR(INT32 dev, INT32 u, INT32 d, INT32 l, INT32 r)
278 {
279 DrvJoyT[(dev*4) + 0] |= l;
280 DrvJoyT[(dev*4) + 1] |= r;
281 DrvJoyT[(dev*4) + 2] |= u;
282 DrvJoyT[(dev*4) + 3] |= d;
283 }
284
BurnTrackballConfig(INT32 dev,INT32 PortA_rev,INT32 PortB_rev)285 void BurnTrackballConfig(INT32 dev, INT32 PortA_rev, INT32 PortB_rev)
286 {
287 TrackRev[(dev*2) + 0] = PortA_rev;
288 TrackRev[(dev*2) + 1] = PortB_rev;
289 }
290 // end Trackball Helpers
291
BurnPaddleSetWrap(INT32 num,INT32 xmin,INT32 xmax,INT32 ymin,INT32 ymax)292 void BurnPaddleSetWrap(INT32 num, INT32 xmin, INT32 xmax, INT32 ymin, INT32 ymax)
293 {
294 BurnGunWrapInf[num].xmin = xmin * 0x10; BurnGunWrapInf[num].xmax = xmax * 0x10;
295 BurnGunWrapInf[num].ymin = ymin * 0x10; BurnGunWrapInf[num].ymax = ymax * 0x10;
296 }
297
BurnPaddleMakeInputs(INT32 num,INT16 x,INT16 y)298 void BurnPaddleMakeInputs(INT32 num, INT16 x, INT16 y)
299 {
300 #if defined FBA_DEBUG
301 if (!Debug_BurnGunInitted) bprintf(PRINT_ERROR, _T("BurnGunMakeInputs called without init\n"));
302 if (num >= nBurnGunNumPlayers) bprintf(PRINT_ERROR, _T("BurnGunMakeInputs called with invalid player %x\n"), num);
303 #endif
304
305 if (num > MAX_GUNS - 1) return;
306
307 if (y == 1 || y == -1) y = 0;
308 if (x == 1 || x == -1) x = 0; // prevent walking crosshair
309
310 BurnGunX[num] += x;
311 BurnGunY[num] += y;
312
313 // Wrapping (for dial/paddle use)
314 if (BurnGunWrapInf[num].xmin != -1)
315 if (BurnGunX[num] < BurnGunWrapInf[num].xmin * 0x100) {
316 BurnGunX[num] = BurnGunWrapInf[num].xmax * 0x100;
317 BurnPaddleReturnA(num); // rebase PaddleLast* on wrap
318 }
319 if (BurnGunWrapInf[num].xmax != -1)
320 if (BurnGunX[num] > BurnGunWrapInf[num].xmax * 0x100) {
321 BurnGunX[num] = BurnGunWrapInf[num].xmin * 0x100;
322 BurnPaddleReturnA(num); // rebase PaddleLast* on wrap
323 }
324
325 if (BurnGunWrapInf[num].ymin != -1)
326 if (BurnGunY[num] < BurnGunWrapInf[num].ymin * 0x100) {
327 BurnGunY[num] = BurnGunWrapInf[num].ymax * 0x100;
328 BurnPaddleReturnB(num); // rebase PaddleLast* on wrap
329 }
330 if (BurnGunWrapInf[num].ymax != -1)
331 if (BurnGunY[num] > BurnGunWrapInf[num].ymax * 0x100) {
332 BurnGunY[num] = BurnGunWrapInf[num].ymin * 0x100;
333 BurnPaddleReturnB(num); // rebase PaddleLast* on wrap
334 }
335 }
336
BurnGunMakeInputs(INT32 num,INT16 x,INT16 y)337 void BurnGunMakeInputs(INT32 num, INT16 x, INT16 y)
338 {
339 #if defined FBA_DEBUG
340 if (!Debug_BurnGunInitted) bprintf(PRINT_ERROR, _T("BurnGunMakeInputs called without init\n"));
341 if (num >= nBurnGunNumPlayers) bprintf(PRINT_ERROR, _T("BurnGunMakeInputs called with invalid player %x\n"), num);
342 #endif
343
344 if (num > MAX_GUNS - 1) return;
345
346 const INT32 MinX = -8 * 0x100;
347 const INT32 MinY = -8 * 0x100;
348
349 if (y == 1 || y == -1) y = 0;
350 if (x == 1 || x == -1) x = 0; // prevent walking crosshair
351
352 BurnGunX[num] += x;
353 BurnGunY[num] += y;
354
355 if (BurnGunX[num] < MinX) BurnGunX[num] = MinX;
356 if (BurnGunX[num] > MinX + nBurnGunMaxX * 0x100) BurnGunX[num] = MinX + nBurnGunMaxX * 0x100;
357 if (BurnGunY[num] < MinY) BurnGunY[num] = MinY;
358 if (BurnGunY[num] > MinY + nBurnGunMaxY * 0x100) BurnGunY[num] = MinY + nBurnGunMaxY * 0x100;
359
360 for (INT32 i = 0; i < nBurnGunNumPlayers; i++)
361 GunTargetUpdate(i);
362 }
363
BurnTrackballInit(INT32 nNumPlayers)364 void BurnTrackballInit(INT32 nNumPlayers)
365 {
366 Using_Trackball = 1;
367 BurnGunInit(nNumPlayers, false);
368 }
369
BurnGunInit(INT32 nNumPlayers,bool bDrawTargets)370 void BurnGunInit(INT32 nNumPlayers, bool bDrawTargets)
371 {
372 Debug_BurnGunInitted = 1;
373
374 if (nNumPlayers > MAX_GUNS) nNumPlayers = MAX_GUNS;
375 nBurnGunNumPlayers = nNumPlayers;
376 bBurnGunDrawTargets = bDrawTargets;
377
378 if (BurnDrvGetFlags() & BDF_ORIENTATION_VERTICAL) {
379 BurnDrvGetVisibleSize(&nBurnGunMaxY, &nBurnGunMaxX);
380 } else {
381 BurnDrvGetVisibleSize(&nBurnGunMaxX, &nBurnGunMaxY);
382 }
383
384 for (INT32 i = 0; i < MAX_GUNS; i++) {
385 BurnGunX[i] = ((nBurnGunMaxX >> 1) - 7) << 8;
386 BurnGunY[i] = ((nBurnGunMaxY >> 1) - 8) << 8;
387
388 BurnPaddleSetWrap(i, 0, 0xf0, 0, 0xf0); // Paddle/dial stuff
389 }
390
391 // Trackball stuff
392 memset(&TrackA, 0, sizeof(TrackA));
393 memset(&TrackB, 0, sizeof(TrackB));
394 memset(&DrvJoyT, 0, sizeof(DrvJoyT));
395 memset(&DIAL_INC, 0, sizeof(DIAL_INC));
396 memset(&TrackRev, 0, sizeof(TrackRev));
397 }
398
BurnGunExit()399 void BurnGunExit()
400 {
401 #if defined FBA_DEBUG
402 if (!Debug_BurnGunInitted) bprintf(PRINT_ERROR, _T("BurnGunExit called without init\n"));
403 #endif
404
405 nBurnGunNumPlayers = 0;
406 bBurnGunDrawTargets = true;
407
408 nBurnGunMaxX = 0;
409 nBurnGunMaxY = 0;
410
411 for (INT32 i = 0; i < MAX_GUNS; i++) {
412 BurnGunX[i] = 0;
413 BurnGunY[i] = 0;
414 }
415
416 Using_Trackball = 0;
417 Debug_BurnGunInitted = 0;
418 }
419
BurnGunScan()420 void BurnGunScan()
421 {
422 #if defined FBA_DEBUG
423 if (!Debug_BurnGunInitted) bprintf(PRINT_ERROR, _T("BurnGunScan called without init\n"));
424 #endif
425
426 SCAN_VAR(BurnGunX);
427 SCAN_VAR(BurnGunY);
428
429 if (Using_Trackball) {
430 SCAN_VAR(TrackA);
431 SCAN_VAR(TrackB);
432
433 SCAN_VAR(PaddleLastA);
434 SCAN_VAR(PaddleLastB);
435
436 SCAN_VAR(DIAL_INC);
437 SCAN_VAR(DrvJoyT);
438 SCAN_VAR(TrackRev);
439 }
440 }
441
BurnGunDrawTarget(INT32 num,INT32 x,INT32 y)442 void BurnGunDrawTarget(INT32 num, INT32 x, INT32 y)
443 {
444 #if defined FBA_DEBUG
445 if (!Debug_BurnGunInitted) bprintf(PRINT_ERROR, _T("BurnGunDrawTarget called without init\n"));
446 if (num >= nBurnGunNumPlayers) bprintf(PRINT_ERROR, _T("BurnGunDrawTarget called with invalid player %x\n"), num);
447 #endif
448
449 if (bBurnGunDrawTargets == false) return;
450
451 if (num > MAX_GUNS - 1) return;
452
453 if (bBurnGunAutoHide && !GunTargetShouldDraw(num)) return;
454
455 UINT8* pTile = pBurnDraw + nBurnGunMaxX * nBurnBpp * (y - 1) + nBurnBpp * x;
456
457 UINT32 nTargetCol = 0;
458 if (num == 0) nTargetCol = BurnHighCol(0xfc, 0x12, 0xee, 0);
459 if (num == 1) nTargetCol = BurnHighCol(0x1c, 0xfc, 0x1c, 0);
460 if (num == 2) nTargetCol = BurnHighCol(0x15, 0x93, 0xfd, 0);
461 if (num == 3) nTargetCol = BurnHighCol(0xf7, 0xfa, 0x0e, 0);
462
463 for (INT32 y2 = 0; y2 < 17; y2++) {
464
465 pTile += nBurnGunMaxX * nBurnBpp;
466
467 if ((y + y2) < 0 || (y + y2) > nBurnGunMaxY - 1) {
468 continue;
469 }
470
471 for (INT32 x2 = 0; x2 < 17; x2++) {
472
473 if ((x + x2) < 0 || (x + x2) > nBurnGunMaxX - 1) {
474 continue;
475 }
476
477 if (BurnGunTargetData[y2][x2]) {
478 if (nBurnBpp == 2) {
479 ((UINT16*)pTile)[x2] = (UINT16)nTargetCol;
480 } else {
481 ((UINT32*)pTile)[x2] = nTargetCol;
482 }
483 }
484 }
485 }
486 }
487
BurnGunDrawTargets()488 void BurnGunDrawTargets()
489 {
490 for (INT32 i = 0; i < nBurnGunNumPlayers; i++) {
491 BurnGunDrawTarget(i, BurnGunX[i] >> 8, BurnGunY[i] >> 8);
492 }
493 }
494