Main > Driving & Racing Cabinets

Mame hacks that make driving games play better with mouse/spinner/360wheel

(1/19) > >>

geecab:
Hi there!

Not sure if this is of interest to anyone, I hacked a ps2 mouse and built a mame driving cab recently. For games that used 360 wheels (pole position, change lanes, super sprint etc) it was great. For games that limited the turning circle of the wheel (outrun, wec le mans) it wasn't so good. I found that if I over steered, the wheel's central position would wander. I've kind of got used to it, but I notice when my friends have a go its takes them a while to adjust.

So I decided to see if I could hack the mame source code a bit. My hack works so that the position of the wheel when the game starts up will always be its central position (players car will travel in a straight forward direction). If you then spin the wheel, say, 3 spins clockwise (hard right), you'll be required to spin the wheel back anti-clockwise 3 times in order to get the car going straight again. Spinning your mouse/spinner/360wheel beyond the limits of what the actual arcade game wheel would allow just means the game's maximum left turn or right turn value is applied.

Hope that make sense, here's a youtube clip of me trying to show that working...



I'll post my mame source code changes shortly incase someone else is thinking of doing the same thing!

BadMouth:
Sounds good for those with 360 degree wheels.

Does each driver in MAME have to be modified, or will this work with with all the driving games?

On Hard Drivin', does the wheel do as many turns as the original arcade one before hitting the virtual end?
(it had a 10 turn pot, but IIRC the wheel turned 7+ times lock to lock)



FYI, years ago, bkenobi had a fix using glovepie.
http://forum.arcadecontrols.com/index.php/topic,92363.0.html

BadMouth:
I hadn't thought about this before (since I don't have a 360 degree wheel), but this is an option it would be nice to see in the official MAME source along with proper shifter support.

If you have the skills to make it optional, and in a way that MAMEDev finds acceptable, consider submitting it.

geecab:
Thanks for your replies Badmouth, really great 'Driving Cab Information' thread by the way. Really helped me out a lot when I was putting my cab together. I think I have read about that glovepie fix you mentioned, but I just fancied modifying the mame source rather than having other stuff running in the background. I think I will submit something to MAMEDev at some point, but to be honest, these are pretty nasty hacks at the moment, am just enjoying messing around with the source code. I haven't even made my modifications command line configurable either so while it will make outrun etc play better, it could well break other games. 

Getting hard drivin' going was tricky, I don't think that multiple turning pot thing (with some strange latching bit when the wheel turns 360 degrees past the central point) has ever been emulated correctly. My hack for hard drivin is different to my outrun/hang-on/weclemans hack, I shall post the hack I made specifically for hard drivin in another post a little later on.

So regarding the outrun/hang-on/weclemans change, you don't have to change all the individual game driver files, just one file (found in src/emu/ioport.c). Also, I'm editing mame source version 0145. You are looking for a function called "apply_analog_min_max", below is what the function looked like originally (before my hack):-



--- Code: ---INLINE INT32 apply_analog_min_max(const analog_field_state *analog, INT32 value)
{
/* take the analog minimum and maximum values and apply the inverse of the */
/* sensitivity so that we can clamp against them before applying sensitivity */
INT32 adjmin = APPLY_INVERSE_SENSITIVITY(analog->minimum, analog->sensitivity);
INT32 adjmax = APPLY_INVERSE_SENSITIVITY(analog->maximum, analog->sensitivity);

/* for absolute devices, clamp to the bounds absolutely */
if (!analog->wraps)
{
if (value > adjmax)
value = adjmax;
else if (value < adjmin)
value = adjmin;
}

/* for relative devices, wrap around when we go past the edge */
else
{
INT32 range = adjmax - adjmin;
/* rolls to other end when 1 position past end. */
value = (value - adjmin) % range;
if (value < 0)
value += range;
value += adjmin;
}

return value;
}

--- End code ---


Now, outrun/hang-on/weclemans used an absolute device for steering. 'value' is passed into the function as the position of where your mouse x axis is. 'value' is limited to adjmax or adjmin if you moved your mouse further than the arcade game allowed.

So now onto my fix, basically, if 'value' is limited by adjmax or adjmin, I make a note of the difference (I called it "spin_history" for some reason). Then the next time I'm in the apply_analog_min_max, I add the spin_history (which maybe positive or negative depending on which way the wheel was turned) to the 'value' passed in, which then may be limited again, which I then save the spin_history again, etc.. etc...  Finally, I only want this to happen for the IPT_PADDLE type because that is the steering wheel device, other devices also go through this function too (IPT_PEDALS for example) which I don't want to alter:-



--- Code: ---INT32 spin_history = 0;
INLINE INT32 apply_analog_min_max(const analog_field_state *analog, INT32 value)
{
/* take the analog minimum and maximum values and apply the inverse of the */
/* sensitivity so that we can clamp against them before applying sensitivity */
INT32 adjmin = APPLY_INVERSE_SENSITIVITY(analog->minimum, analog->sensitivity);
INT32 adjmax = APPLY_INVERSE_SENSITIVITY(analog->maximum, analog->sensitivity);

/* for absolute devices, clamp to the bounds absolutely */
if (!analog->wraps)
{
if(analog->field->type == IPT_PADDLE)
{
value = value + spin_history;

spin_history = 0;

if(value > adjmax)
{
spin_history = value - adjmax;
value = adjmax;
}
else if(value < adjmin)
{
spin_history = value - adjmin;
value = adjmin;
}
}
else
{
if (value > adjmax)
value = adjmax;
else if (value < adjmin)
value = adjmin;
}
}
/* for relative devices, wrap around when we go past the edge */
else
{
INT32 range = adjmax - adjmin;

/* rolls to other end when 1 position past end. */
value = (value - adjmin) % range;
if (value < 0)
value += range;
value += adjmin;
}

return value;
}

--- End code ---


There is an obvious bug with this, like if you span your wheel enough (moved you mouse in one direction enough) spin_history would get so large (or so small) that it too would wrap around. I could sort this out at some point though.

Anyways, I think that's it for now as I think I'm going on a bit, hope some of this made sense  ;)

geecab:
Here is my hard drivin' / race drivin hack. By the way, its the Compact British version of hard drivin' (harddrivcb) I am currently using (because it runs much faster than the cockpit version). Important to note though as the wheel does not use a Potentiometer in the compact version, so I'm not sure my hack would work on the cockpit version. I'm using the 0145 mame sources to build from. This has all been pretty much a case of trial an error but it does appear to work really nicely now. Be good to hear from anyone who knows anything about hard drivin's bit latching setting (set when the wheel passes 360 degrees) as I'm probably barking up the wrong tree!

Ok so first off, I modified the apply_analog_min_max function in src/emu/ioport.c agian. The big comment should hopefully explain what I'm up to...

--- Code: ---INLINE INT32 apply_analog_min_max(const analog_field_state *analog, INT32 value)
{
/* take the analog minimum and maximum values and apply the inverse of the */
/* sensitivity so that we can clamp against them before applying sensitivity */
INT32 adjmin = APPLY_INVERSE_SENSITIVITY(analog->minimum, analog->sensitivity);
INT32 adjmax = APPLY_INVERSE_SENSITIVITY(analog->maximum, analog->sensitivity);

/* for absolute devices, clamp to the bounds absolutely */
if (!analog->wraps)
{
if(analog->field->type == IPT_PADDLE)
{
//Limit the turning circle so that you can not turn the wheel more than 270 degrees to
//the left or 270 degrees to the right. I limited the turning circle because things
//seemed to mess up if I turned more that 360 degress left or right. The values I've
//hardcoded adjmax/admin may need to be altered depending on your analogue PADDLE settings.
//Press the Left Shift key in game and a small debug window appears for a few seconds
//(one of the mame developers must have added this), it shows you what position mame thinks the
//arcade wheel is in. It should be the same value as reported by the game if you Press F2
//(invoke the service menu), then pressing 5/6, and turning key (1) on CONTROL SIGNALS.
//From what I can work out, the value 0x800 in the debug window should be
//dead center, 0xC00 is about 270 degress to the right of the central position, 0x400 is
//about 270 degrees to the left of the central position. If you use a different
//PADDLE SENSITIVITY, the values shown in the debug window may exceed the 0x400 to 0x800
//limit in which case you probably what to a adjust the hard coded limits of adjmax/adjmix
//(by trial and error, holding the left shift key down in game and see how things look/play).
//
//This is what worked for me with my analogue settings set to:
//    PADDLE DIGITAL SPEED = 0
//    PADDLE AUTOCENTER SPEED = 0
//    PADDLE SENSITIVITY = 25
adjmax = 150016; //Stop the steering going over 0x0C00, before the latch bit is set
//Central position seems to be 0x0800
adjmin = -150016; //Stops the steering going under 0x0400, before the latch bit is set


value = value + spin_history;

spin_history = 0;

if(value > adjmax)
{
spin_history = value - adjmax;
value = adjmax;
}
else if(value < adjmin)
{
spin_history = value - adjmin;
value = adjmin;
}
}
else
{
if (value > adjmax)
value = adjmax;
else if (value < adjmin)
value = adjmin;
}
}

/* for relative devices, wrap around when we go past the edge */
else
{
INT32 range = adjmax - adjmin;
/* rolls to other end when 1 position past end. */
value = (value - adjmin) % range;
if (value < 0)
value += range;
value += adjmin;
}

return value;
}

--- End code ---


With the hack above, things worked quite well, but I found the steering would occasionally wander away from dead center after doing a few erratic turns. I guessed it might be something to do with the wheel edge/latching processing which I don't really understand. So I found in src/mame/machine/harddriv.c a function which appears to do something with this latching bit each time the wheel goes past its central position and I modified it and it fixed the wandering problem.

This is what the function originally looked like...

--- Code: ---READ16_HANDLER( hdc68k_wheel_r )
{
harddriv_state *state = space->machine().driver_data<harddriv_state>();

/* grab the new wheel value and upconvert to 12 bits */
UINT16 new_wheel = input_port_read(space->machine(), "12BADC0") << 4;

/* hack to display the wheel position */
if (space->machine().input().code_pressed(KEYCODE_LSHIFT))
{
popmessage("%04X", new_wheel);
}

/* if we crossed the center line, latch the edge bit */
if ((state->m_hdc68k_last_wheel / 0xf0) != (new_wheel / 0xf0))
state->m_hdc68k_wheel_edge = 1;

/* remember the last value and return the low 8 bits */
state->m_hdc68k_last_wheel = new_wheel;
return (new_wheel << 8) | 0xff;
}

--- End code ---

And this is what it looked like after my modification (Once again big comment should hopefully explain what I'm up to)...

--- Code: ---UINT16 g_latchpoint = 0x860; //Ajust this g_latchpoint by trial and error. You want it so that
//when your wheel is dead center and you press left shift key in game, the value reported is 0x800.
//Setting the latchpoint as 0x860 was dead center for me (not sure why this is, I had calibrated
//everything correctly in the F2 service/configuration menu).
READ16_HANDLER( hdc68k_wheel_r )
{
harddriv_state *state = space->machine().driver_data<harddriv_state>();

/* grab the new wheel value and upconvert to 12 bits */
UINT16 new_wheel = input_port_read(space->machine(), "12BADC0") << 4;

/* hack to display the wheel position */
if (space->machine().input().code_pressed(KEYCODE_LSHIFT))
{
popmessage("%04X", new_wheel);
}

if(new_wheel == g_latchpoint)
{
if(state->m_hdc68k_last_wheel != g_latchpoint)
{
state->m_hdc68k_wheel_edge = 1;
}
}
else
{
//I noticed after many harsh turns of the wheel, mame's perception of where
//the arcade wheel is (shows by pressing the left shift key in game) would
//wander from what was actually reported in the hard drivin' serivce menu
//(Pressing F2 in-game and view the CONTROL SIGNALS page). I guessed this was
//something to do with the edge/latching bit stuff that should get set when
//the wheel passes the central position. My modification is to make sure the
//m_hdc68k_wheel_edge thing is set when we pass the central position, even
//if we don't hit the value exactly.
if(new_wheel > g_latchpoint)
{
if(state->m_hdc68k_last_wheel < g_latchpoint)
{
state->m_hdc68k_wheel_edge = 1;
}
}
else if (new_wheel < g_latchpoint)
{
if(state->m_hdc68k_last_wheel > g_latchpoint)
{
state->m_hdc68k_wheel_edge = 1;
}
}
}

/* remember the last value and return the low 8 bits */
state->m_hdc68k_last_wheel = new_wheel;
return (new_wheel << 8) | 0xff;
}

--- End code ---


Finally, I only have a high/low shifter and wanted to use the 4 speed manual gear option. As I've got shift up and shift down firebuttons on my CP I decided to try and use them. So this is my shifter hack (I've done similar hacks for games like night driver, Sprint 1), let me know if you're interested in seeing them.

Once again, in src/mame/machine/harddriv.c I found the function that takes care of the gears, this is what it originally looked like...

--- Code: ---READ16_HANDLER( hdc68k_port1_r )
{
harddriv_state *state = space->machine().driver_data<harddriv_state>();
UINT16 result = input_port_read(space->machine(), "a80000");
UINT16 diff = result ^ state->m_hdc68k_last_port1;

/* if a new shifter position is selected, use it */
/* if it's the same shifter position as last time, go back to neutral */
if ((diff & 0x0100) && !(result & 0x0100))
state->m_hdc68k_shifter_state = (state->m_hdc68k_shifter_state == 1) ? 0 : 1;
if ((diff & 0x0200) && !(result & 0x0200))
state->m_hdc68k_shifter_state = (state->m_hdc68k_shifter_state == 2) ? 0 : 2;
if ((diff & 0x0400) && !(result & 0x0400))
state->m_hdc68k_shifter_state = (state->m_hdc68k_shifter_state == 4) ? 0 : 4;
if ((diff & 0x0800) && !(result & 0x0800))
state->m_hdc68k_shifter_state = (state->m_hdc68k_shifter_state == 8) ? 0 : 8;

/* merge in the new shifter value */
result = (result | 0x0f00) ^ (state->m_hdc68k_shifter_state << 8);

/* merge in the wheel edge latch bit */
if (state->m_hdc68k_wheel_edge)
result ^= 0x4000;

state->m_hdc68k_last_port1 = result;
return result;
}

--- End code ---


And this is what it looked like after my modification...

--- Code: ---int g_gear = 1;
int g_have_seen_shift = 0;
READ16_HANDLER( hdc68k_port1_r )
{
harddriv_state *state = space->machine().driver_data<harddriv_state>();
UINT16 result = input_port_read(space->machine(), "a80000");
int has_changed = 0;

if(!g_have_seen_shift)
{
if(result != 0xFFFF)
{
if (result & 0x0100) //SHIFT UP
{
//printf("shift up\n");
g_have_seen_shift = 1;
g_gear++;
if(g_gear > 4) g_gear = 4;

has_changed = 1;
}
if (result & 0x0200) //SHIFT DOWN
{
//printf("shift down\n");
g_have_seen_shift = 1;
g_gear--;
if(g_gear < 1) g_gear = 1;

has_changed = 1;
}
}
}
else
{
if ((result != 0xFEFF) && (result != 0xFDFF))
{
//printf("shift OFF result=0x%X\n", result);
g_have_seen_shift = 0;
}
else
{
//printf("shift STILL ON! result=0x%X\n", result);
}
}


if(has_changed)
{
if (g_gear == 1)
{
//printf("gear1\n");
state->m_hdc68k_shifter_state = 1;
}
if (g_gear == 2)
{
//printf("gear2\n");
state->m_hdc68k_shifter_state = 2;
}
if (g_gear == 3)
{
//printf("gear3\n");
state->m_hdc68k_shifter_state = 4;
}
if (g_gear == 4)
{
//printf("gear4\n");
state->m_hdc68k_shifter_state = 8;
}
}

/* merge in the new shifter value */
result = (result | 0x0f00) ^ (state->m_hdc68k_shifter_state << 8);

/* merge in the wheel edge latch bit */
if (state->m_hdc68k_wheel_edge)
result ^= 0x4000;

state->m_hdc68k_last_port1 = result;
return result;
}

--- End code ---

Then in mame the Key you define for '2nd gear' will act like a shift up, and the key that you define for '1st gear' will act like a shift down. Also, while your defining keys in mame, define the clutch key as the same keys as you chose for the '1st gear', '2nd gear' and 'turn key'. That way you should be able to make really quick smooth shift changes whilst automatically pressing the clutch down.

I think that is everything  :)

Navigation

[0] Message Index

[#] Next page

Go to full version