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