Jump to content

AXMAP3 (or rather AXMAP1 and AXMAP2 at the same time)


Recommended Posts

Posted

If you know what I mean by wanting the capabilities of AXMAP1 and AXMAP2 in a single function - an "AXMAP3", then read on. This goes back to something I wanted to do in FOXY a long time ago, but couldn't. I wanted to do this in TARGET as well, but it took me a while to figure a few things out because I wanted it to work in KeyAxis() like AXMAP1 and AXMAP2.

 

I was posting this in response to another topic, but it was quickly hijacking that thread, so I decided to just post it in a new topic.

 

I've been developing a script for Train Sim World (TSW) that is a glorified axis to keyboard mapper that actually works. TSW doesn't support game controllers and running a train with a keyboard is ridiculous. So regardless of TSW, this would be useful for mapping an axis to control anything that uses keyboard keys only, but where AXMAP1 or AXMAP2 doesn't cut it. I wanted to use my Warthog to run TSW. But trying to use KeyAxis() with AXMAP1 or AXMAP2 doesn't work very well either. You need an "AXMAP3", which effectively merges the capabilities of AXMAP1 and AXMAP2 in a single function.

 

You can see the full script on the Dovetail Train Sim World forums linked below including links to videos showing it working. But, I'll explain some of the details here for the sake of discussion if anyone cares.

https://forums.dovetailgames.com/threads/thurstmaster-target-script-for-warthog-throttle-saitek-tq-profile.3634/

 

In summary.... Yes this really is the summary - I am posting a little bit to see if anyone cares about stuff like this. If yes, I can post more explanation, or post it as a working library for others to use. If it doesn't make sense, don't worry, I don't expect anyone that isn't a programmer or a TARGET expert to understand this. But I am happy to discuss and teach it for anyone that wants to learn about this stuff.

 

This is what I did - my "AXMAP3" is really AXMAPEXEC(KeyAxisDirectional()), where AXMAPEXEC is a special version of EXEC that works in KeyAxis() without having to be enclosed in AXMAP1 or AXMAP2. Yes, if you haven't tried, you can't do the following:

 

KeyAxis(&Throttle, THR_RIGHT, 'o', EXEC("Myfunction();") );

 

So I created AXMAPEXEC() to solve that. You can use it as follows:

 

KeyAxis(&Throttle, THR_RIGHT, 'o', AXMAPEXEC("Myfunction();") );

 

AXMAPEXEC() calls KeyAxisDirectional(), which is performing the equivalent function of an AXMAP1 and AXMAP2 at the same time - presses one key in the up direction, another in the down direction, but I can define for every zone in the LIST() (as defined by the &...ZoneList variables in my example below) a different key press as well as a different key press time regardless of the axis moving up or down. Because of how TARGET works, it works amazingly well. No missed key presses which is typical with all the other axis to keyboard mapping functions I've seen for other joystick programming software.

 

Here is an example of the code from my TSW script. It uses the Warthog Right axis to control the locomotive throttle, and the left axis has a UMD state to control one of three different brake levers on the locomotive, and all this works within the framework of a standard KeyAxis() command.

 

// Warthog THR_RIGHT axis config - engine throttle
KeyAxis(&Throttle, THR_RIGHT, 'o', AXMAPEXEC("KeyAxisDirectional(&Throttle, THR_RIGHT, &pro_SD40GP38_ThrZoneList, &pro_SD40GP38_ThrActionUP, &pro_SD40GP38_ThrActionDN);"));
// Warthog THR_LEFT axis config using UMD Switch - U=dynamic brake, M=automatic brake, D=indepedent brake
KeyAxis(&Throttle, THR_LEFT, 'u', AXMAPEXEC("KeyAxisDirectional(&Throttle, THR_LEFT, &pro_SD40GP38_DynZoneList, &pro_SD40GP38_DynActionUP, &pro_SD40GP38_DynActionDN);"));
KeyAxis(&Throttle, THR_LEFT, 'm', AXMAPEXEC("KeyAxisDirectional(&Throttle, THR_LEFT, &pro_SD40GP38_AutoZoneList, &pro_SD40GP38_AutoActionUP, &pro_SD40GP38_AutoActionDN);"));
KeyAxis(&Throttle, THR_LEFT, 'd', AXMAPEXEC("KeyAxisDirectional(&Throttle, THR_LEFT, &pro_SD40GP38_IndZoneList, &pro_SD40GP38_IndActionUP, &pro_SD40GP38_IndActionDN);"));

 

An example of the parameters passed to KeyAxisDirectional():

 

&pro_SD40GP38_ThrZoneList references a list of zones by percentage of axis travel kind of like what we do with LIST() normally. Unlike LIST(), the resolution by percentage goes down to 0.1%, which is critical for an axis to keyboard mapper like this.

 

&pro_SD40GP38_ThrActionUP references a table of keypress actions and press times when the axis moves up (increasing value).

 

&pro_SD40GP38_ThrActionDN references a table of keypress actions and press times when the axis moves down (decreasing value).

 

I'll stop there because it is better if you want to understand this, go look at my TSW script, and then ask questions here.

 

Making the script forced me to understand TARGET to a very deep level. The above example may seem simple, but it took a lot of understanding about target.tmh to get there.

 

 

 

 

Here is the function for AXMAPEXEC(). It is very similar to EXEC(), but has a critical difference that allows it to work outside of AXMAP1() or AXMAP2() when inside a KeyAxis().

 

AXMAPEXEC():

//******************************************************************
// AXMAPEXEC(), axmapexecproc()
//	Version of EXEC() for use in KeyAxis() by itself.  This is because EXEC() only works inside an AXMAP1 or AXMAP2, 
//	but not as the only command in KeyAxis(). So had to create a special version of EXEC() to solve this.  
//	AXMAPEXEC() works the same as EXEC() as far as syntax, but should only be used in KeyAxis() commands.
//	Examples:
//		KeyAxis(&Throttle, THR_LEFT, 'm', AXMAPEXEC("ActKey(...);"));		Execute ActKey() for M layer
//		KeyAxis(&Throttle, THR_LEFT, 0, AXMAPEXEC("YourFunc(...);"));		Execute YourFunc() for all layers IO and UMD
int AXMAPEXEC(alias cmd) { tmp[0]=&cmd; return ASMAlloc(1, &&tmp, &axmapexecproc); }
int axmapexecproc(alias v, int p, int x=0)
{
if (x < -AMAX) return 0;
p = v[(p+2 & 0xffff)];
if(p) execute(p); 
}

 

If you just want to use AXMAPEXEC() in your script, paste the code above into your .tmc or .ttm file and it is good to go. As in my KeyAxisDirectional() example above, you will want to pass in the device and axis names to your function. So you might do something like this:

 

KeyAxis(&Throttle, THR_LEFT, 'u', AXMAPEXEC("MyFunction(&Throttle, THR_LEFT);"));

int MyFunction(alias dev, int axis)
{
    if (dev[axis] > 20000) {
         // do something here
    }
    else if (dev[axis] <= 20000) {
         // do something else
    }
}

 

This is a ridiculous example because you can do the above using AXMAP1(), but my point is, with the above, you are able to do anything you can imagine, not just what AXMAP1 or AXMAP2 allows you to do.

 

And in my case, my imagination led me to KeyAxisDirectional(). To see how KeyAxisDirectional() works, it is best to go check out my posted script at the Train Sim World forums. There are a bunch of massive tables in the .ttm file which makes it work. If I tried to post it here, so much would be taken out of context, it would be confusing. But if there is interest, I can explain what I did better, and/or post full working examples here in the form of a library.

 

BTW, to try it out, you don't need Train Sim World. You can see everything my script does using the TARGET EventTester since all it is doing is press keys.

 

Also, the AXMAPEXEC() function is new to my script in v3.0. The TSW forum topic only have v2.x of my script which does not use AXMAPEXEC(). I will post the v3.0 script later tonight. Before using AXMAPEXEC() I had a huge if else statement which was getting ridiculous. It is much easier to remap an axis using KeyAxis(), so I created AXMAPEXEC() as this follows the nomenclature Thrustmaster intended with TARGET.

Posted

This sounds like the thing that might work for someone who wanted to map an airbrake to the X52 slider, except for the fact they were using an x52... :D

Jokes assides I've seen pleanty of times where a funciton like this might be usefull in one Flight sim or another, for a particular function that doesn't suport an axis for some reason etc. So thanks for sharing.

On another note, I was wondering if you think it's possible to use something like your "&pro_SD40GP38_ThrZoneList" and use it to make a "SetCustomCurve" more precise by beeing able to define the points down to a tenth of a percent?

Posted
Jokes assides I've seen pleanty of times where a funciton like this might be usefull in one Flight sim or another, for a particular function that doesn't suport an axis for some reason etc. So thanks for sharing.

 

Yes. Thankfully DCS supports almost everything we might want to map an axis to, and what isn't axis mappable is easily handled by an AXMAP1 or AXMAP2. In TSW, though, you sometimes have to press and hold a key for half a second to get over a notch on the lever and then press the same key with short taps (like 100ms each) to adjust the lever forward or back precisely. AXMAP1 or AXMAP2 won't do that. This is where KeyAxisDirectional() comes in.

I can't think of an example where we need to do this in DCS. But I've seen a need for it in a few other flight sims over the years.

 

 

On another note, I was wondering if you think it's possible to use something like your "&pro_SD40GP38_ThrZoneList" and use it to make a "SetCustomCurve" more precise by beeing able to define the points down to a tenth of a percent?

 

Sadly no. pro_SD40GP38_ThrZoneList is just an array of integers, not an actual function like LIST(). Here is the definition for pro_SD40GP38_ThrZoneList:

 

int pro_SD40GP38_ThrZoneList[] = {0,5,111,222,333,444,556,667,778,889,995,1000};		

 

I'm using shifted integers (e.g. 5 = 0.5%, 111 = 11.1%). &pro_SD40GP38_ThrZoneList (note the &) just allows me to pass the array to KeyAxisDirectional().

 

Making a version of LIST() to define points down to 0.1% would also require modifying a significant amount of target.tmh. I'm sure Thrustmaster avoided floating point for performance reasons, but on a modern computer, this isn't likely an issue. TARGET runs very fast, and could probably handle floating point without difficulty today. Regardless, the reason I used 111 = 11.1% is to avoid floating point.

 

I thought about making a version of LIST() for my own function, but using floating point or shifted integers instead. But how LIST (which is actually AXMAP2), CHAIN, and SEQ work still eludes me. These 3 functions use Map(..., MakeProcInstance(), MAP_IPTR_VPN) to create a reference to the primary function (e.g. axmap2proc, chainproc, and seqproc) and store that in the keyalloc[] array including the parameters (e.g. the numbers for LIST). But since MakeProcInstance() and Map() are built in functions, it is harder to understand exactly what they are doing.

 

I mean I could duplicate what they did and get a working result, but I don't like doing stuff like that until I understand what is really going on. It is too easy to clobber a patch of memory and crash TARGET stepping out of the box like that. So I made KeyAxisDirectional() to just accept an alias to an array of int for now.

Posted

Ah well thanks for that. In any case TARGET's percent precision isn't bad at all for my present needs. Beats DCS 10% intervals, and doesn't make the resulting curve a Spline. In fact it very much functions as straight lines between points, which is exactly what I want.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...