Jump to content

I got Thrustmaster TARGET (T.16000M) to work with KSP


Recommended Posts

I came here to find an answer to my problem, and I found some clues, but no resolution. With some experimentation, I was able to figure out how to get the TARGET software working with KSP.

I have two Thrustmaster T.16000M sticks which weren't being recognized by the game properly when used together. But it turns out that the TARGET software can fool the game into seeing the two sticks as one virtual stick, since the virtual stick driver has exactly twice as many axes and twice as many buttons available as does a single T.16000M. However, the axes didn't work when running TARGET.

With the joystick centered, the in-game axes would start at the extreme low end of their range. When the stick was pushed to the up or right, the in-game axis could be moved past the center position to the extreme high end of its range. When the stick was moved left or down, the in-game axis would instantly snap from the low extreme to the high extreme and stay there until the stick was centered again.

I read more about TARGET and KSP on the web and came across information that KSP is based on the Unity engine, and the Unity engine does not accept negative input from joystick drivers. It expects all input to be positive. This fit with what I had experienced above.

The solution was to map the axis position that TARGET would normally output to the virtual driver to the axis position that Unity expects. This means forcing the normal output, which normally ranges between -32768 and 32767 (a signed, 2-byte integer) to be only between 0 and 32767. That can be done by dividing the TARGET default output by 2 and then adding a quarter of the range, resulting in the Unity's expected output. Fortunately, the TARGET scripting is powerful enough to handle this.

The upshot is that to make TARGET work, you have to use the TARGET Script Editor as normal, with a couple of small modifications. First, make two small modifications to the target.tmh "header" file (WITHOUT saving it):

1. Somewhere in target.tmh, preferably after the "include" lines and before anything else, put the following block of code:

// Conversion from TARGET axis value output to Unity axis value virtual output
// (TARGET only allows outputting signed virtual axis values, while
// the Unity engine apparently only accepts positive axis values; therefore,
// compress the range and put it entirely in the positives,
// resulting in the resolution being cut in half)
short UnityAxis (short TARGETAxis) { return TARGETAxis/2 + 0x4000; }

2. On line 620 of target.tmh, change the line

    if(index < MOUSE_X_AXIS) DX(index-1+OUT_ID_AXIS, value);

to

    if(index < MOUSE_X_AXIS) DX(index-1+OUT_ID_AXIS, UnityAxis(value));

And finally,

3. Save the modified target.tmh as some other filename, preferably in some other directory. Then, at the top of your .tmc script, which will include "int main()", make sure to change the filename in quotes after "include" to the new version of the header file you just changed and saved. (You might need to go to OPTIONS and the FILE PATHS tab and add an entry for the location where you saved it.)

You can verify that it's working as intended by using the script editor's DEVICE ANALYZER, and see that the "Thrustmaster Combined" joystick axes (after moving them around a little to calibrate) start with the stick centered at 16384 in the middle of the positive portion, and you can move it left all the way to the middle, near 0, and right all the way to the right end.

 

Link to comment
Share on other sites

  • 3 weeks later...
  • 9 months later...

@Jason Melancon Your implementation of UnityAxis compresses the axes into the range [1, 32767] rather than [0, 32767]. This is fine for joysticks, but when you're doing HOTAS or trying to use the slider on a T16000m to control throttle, this means KSP never gets a throttle value of 0 and never actually shuts off the engines. The resulting thrust is tiny but nonzero, which disallows time-warp and messes up finicky maneuvers. The following implementation of UnityAxis corrects this issue:

short UnityAxis (short TARGETAxis) {
  /* Move the value entirely into the positive domain */
  int intermediate = TARGETAxis;
  intermediate = intermediate + 0x8000;
  /* And then divide by two and truncate */
  return intermediate / 2;
}

Thanks for the code spelunking that was necessary to address this problem!

Edited by Vebyast
Link to comment
Share on other sites

  • 1 month later...

Can this be achieved using the TARGET GUI? I'm not really familiar at all with the TARGET script editor yet...

EDIT: Ok pretty sure it can't, managed to figure out where the target.tmh is and have added the above change, the bit about changing something on line 620 though.... It seams that TARGET has changed a bit since this was written and I'm not sure what I should change there if anything.....

Edited by Akira_R
Link to comment
Share on other sites

  • 1 year later...
On 8/13/2020 at 6:45 PM, Akira_R said:

managed to figure out where the target.tmh is and have added the above change, the bit about changing something on line 620 though.... It seams that TARGET has changed a bit since this was written and I'm not sure what I should change there if anything.....

I'm sorry I'm just seeing this now! If you post your target.tmh, or send it to me some other way, I can take a look. The relevant code may have moved to some other file, so you may want to post/send multiple files. They may even have made TARGET compatible with Unity out of the box by now; I don't know. (One would hope, right? :)

Link to comment
Share on other sites

On 7/3/2020 at 10:35 PM, Vebyast said:

@Jason Melancon Your implementation of UnityAxis compresses the axes into the range [1, 32767] rather than [0, 32767]. ... The following implementation of UnityAxis corrects this issue:

		
	


Confirmed. Interesting! That must mean that the real initial range is [-32767 32767] rather than [-32768 32767], the full range of a signed integer in two's complement. And I see now that the TARGET Device Analyzer even clearly shows this range for each axis. Shucks, I should have caught that.

  -32767 / 2 = -16383.5, truncated to -16383 in integer division
  -16383 + 16384 = 1

Your version works because

  -32767 + 32768 = 1
  1 / 2 = 0.5, truncated to 0 in integer division

Nice, thanks!

Quote

This is fine for joysticks, but when you're doing HOTAS or trying to use the slider on a T16000m to control throttle, this means KSP never gets a throttle value of 0 and never actually shuts off the engines. The resulting thrust is tiny but nonzero, which disallows time-warp and messes up finicky maneuvers.

Strange that I never noticed a problem using the throttle to cut the engines, though I have been using the part window to deactivate an engine when necessary. I'll have to see whether I notice a difference in behavior now.

Link to comment
Share on other sites

On 7/3/2020 at 3:35 PM, Vebyast said:

@Jason Melancon Your implementation of UnityAxis compresses the axes into the range [1, 32767] rather than [0, 32767]. This is fine for joysticks, but when you're doing HOTAS or trying to use the slider on a T16000m to control throttle, this means KSP never gets a throttle value of 0 and never actually shuts off the engines. The resulting thrust is tiny but nonzero, which disallows time-warp and messes up finicky maneuvers. The following implementation of UnityAxis corrects this issue:

short UnityAxis (short TARGETAxis) {
  /* Move the value entirely into the positive domain */
  int intermediate = TARGETAxis;
  intermediate = intermediate + 0x8000;
  /* And then divide by two and truncate */
  return intermediate / 2;
}

Thanks for the code spelunking that was necessary to address this problem!

 

Edited by Ghostii_Space
quote was showing incorrectly
Link to comment
Share on other sites

On 6/11/2022 at 3:21 AM, Jason Melancon said:

I'm sorry I'm just seeing this now! If you post your target.tmh, or send it to me some other way, I can take a look. The relevant code may have moved to some other file, so you may want to post/send multiple files. They may even have made TARGET compatible with Unity out of the box by now; I don't know. (One would hope, right? :)

LOL, and 2 years later.... Any ways, yeah I actually figured it out, just took a couple hours of actually diving into the TARGET scripts to learn how the syntax works and what does what, I honestly have no memory of what I did at this point but I have it all working nicely, even implemented at swap yaw/roll button in TARGET for switching between flying planes in atmo and controlling space craft.

Thanks for the reply though!!

Link to comment
Share on other sites

15 hours ago, Akira_R said:

Thanks for the reply though!!

You're welcome! Nice job! I love swapping roll and yaw using TARGET too. Nothing like moving the stick as though moving the rocket itself, although I can't tell you how many craft I've crashed because I forgot to set the right roll/yaw axis mode for the craft before launch. I lost count a long time ago. I even have a note near the top of my launch checklist (right after checking that stick inputs are working at all after starting the game), and I still manage to screw it up sometimes. I kind of wish TARGET had a way of throwing up an Alert() box, or a hook into a KSP API to display a message about that mode.

Okay, that got me thinking, and now that I actually look, I see in fact there's a substantial KSP API. Hmmmm...

Link to comment
Share on other sites

  • 2 months later...

Words for google searches: thrustmaster 16000 stick kerbal space program ksp crazy haywire extreme target gui broken fix

With my Thrustmaster T16000M FCS HOTAS stick the full axis extension values range between -32767 and 32767.  That is to say the stick itself only ever outputs from -32766 to 32766 even though the extremes are labled -32767 and 32767.  In my case, instead of adding 32768 using 0x8000, I need to add 32766 using 0x7FFF.
Edit: I don't know why the device analyzer only shows 32766 at the extremes. I have no deadbands and the physical readouts along with the virtual readouts all show I am reaching the max value. Regardless of the why involving the device analyzer, vebyast is correct, and my code block has been updated to the correct hex value.

HOW TO DO THIS without reading this whole thread and getting confused:

The code in target.tmh has changed with updates. You should edit the code again and overwrite your custom version of target.tmh after every update just in case. Put the code below or the code above before the first function of your target.tmh file. IDK if different sticks have different extremes. You could always just add a small deadband at both ends. It should look something like this.

// This is the code you insert at the beginning
short UnityAxis (short TARGETAxis) {
  /* Move the value entirely into the positive domain */
  int intermediate = TARGETAxis;
  /* Add 36766 which is 0x7FFF in hex and then divide by two */
  intermediate = (intermediate + 0x8000) / 2;
  
  return intermediate;
}
// Below is the first function of my target.tmh file at the time of writing. You don't need to insert this, as it is just and example.
struct sAxis
{
	char dxmap;
	char dir;
	int curvemode;	// 0=none, 1=S, 2=J, else=custom
	char lower, center, upper, curve;	 		// S curve parameters
	float ab;									// J curve parameter, zoom for Scurve
	char locked;
	char relative;
	int trim;
	int val, relpos;
	int key[6];		// ou, iu, om, im, od, id
}

Now you have to change one line buried in that code deep down somewhere so it is time for CTRL+F. Use this or some portion of it:

if(index < MOUSE_X_AXIS) DX(index-1+OUT_ID_AXIS, value);

replace it with:

if(index < MOUSE_X_AXIS) DX(index-1+OUT_ID_AXIS, UnityAxis(value));
The above line is number 646 for me as of this writing. Save your file as something.tmh and put it where your profile files are, or in the same directory as the script file you are about to make. The script and your customized target.tmh have to be in the same directory or where the original target.tmh is.

The function name and parameter list where that line resided was int DXSetAxis(int index, int value) at the time of this writing.

 

It's extremely easy to convert a GUI profile to a script, so that will be my contribution. Though in fiddling with this I accidentally learned how to write target scripts. Until then users who can't write scripts can:

  1. Make and save the profile with the GUI.
  2. Navigate through the profile to the page with "1. Select Control 2. Assign Parameters 3. Select Event" at the top. Make sure you saved.
  3. Click view script. That's your script.
  4. Copy paste that into a text editor.
  5. Close the GUI editor and open the script editor.
  6. Save it where you save your profiles, but replace the beginning line include "target.tmh" with include "newfilename.tmh" Not literally newfilename, whatever you named it. I named mine targetMXY.tmh
  7. Put that file in the same place you put your new script file. Don't forget to make a new custom target.tmh with every Thrustmaster software update just in case.
  8. Make sure you are running the script before you launch KSP when you assign controls in KSP.


Want a button to switch to controlling planes to controlling rockets and back? You are probably going to have to read the TARGET script PDF depending on what device you are using. I'm using a stick identified internally T16000. I want button 14, defined B14 internally, to be my switching button. If you want to set up deadbands anywhere in the script without going back to the GUI, you can edit the SetSCuve commands, for example SetSCurve(&T16000, JOYX, 5, 3, 5, 0, 0); creates a deadband of 5 on either extreme and 3 surrounding center position.

	MapKey(&T16000, B14,
					SEQ(
						EXEC("printf(\"    Switching to Plane Controls (Twist = Yaw\; X = Roll) \\xa\");"
							"MapAxis(&T16000, RUDDER, DX_ZROT_AXIS, AXIS_NORMAL, MAP_ABSOLUTE);"
							"SetSCurve(&T16000, RUDDER, 0, 0, 0, 0, 0);"
							"MapAxis(&T16000, JOYX, DX_X_AXIS, AXIS_NORMAL, MAP_ABSOLUTE);"
							"SetSCurve(&T16000, JOYX, 0, 0, 0, 0, 0);"
							),
						EXEC("printf(\"    Switching to Rocket Controls (Twist = Roll; X = Yaw) \\xa\");"
							"MapAxis(&T16000, RUDDER, DX_X_AXIS, AXIS_NORMAL, MAP_ABSOLUTE);"
							"SetSCurve(&T16000, RUDDER, 0, 0, 0, 0, 0);"
							"MapAxis(&T16000, JOYX, DX_ZROT_AXIS, AXIS_NORMAL, MAP_ABSOLUTE);"
							"SetSCurve(&T16000, JOYX, 0, 0, 0, 0, 0);"
							)
					)
			);


Much thanks to Jason Melancon and Vebyast for finding and providing answers to problems I'm sure that has plagued many people. You guys are the real MVPs.

P.S. Is your stick twist going bonkers like mine was? The fix is simply cleaning the twisting mechanism with 91% isopropyl alcohol. This guy shows you how. Read the comments section for more tips. 


Here's my script, partially generated by the GUI if anyone is interested. printf simply prints things to the script window. It was useful and i just left it. If you copy this be sure to switch to plane mode before assigning controls. Things will make more sense in the KSP input options window.

include "targetMXY.tmh"
int main()
{
	Configure(&HCougar, MODE_EXCLUDED);
	Configure(&Joystick, MODE_EXCLUDED);
	Configure(&JoystickF18, MODE_EXCLUDED);
	Configure(&Throttle, MODE_EXCLUDED);
	Configure(&A320Pilot, MODE_EXCLUDED);
	Configure(&A320Copilot, MODE_EXCLUDED);
	Configure(&TCAQuadrant12, MODE_EXCLUDED);
	Configure(&TCAQuadrant34, MODE_EXCLUDED);
	Configure(&TCAYokeBoeing, MODE_EXCLUDED);
	Configure(&TCAQBoeing12, MODE_EXCLUDED);
	Configure(&TCAQBoeing34, MODE_EXCLUDED);
	Configure(&T16000L, MODE_EXCLUDED);
	Configure(&LMFD, MODE_EXCLUDED);
	Configure(&RMFD, MODE_EXCLUDED);
	Configure(&TFRPRudder, MODE_EXCLUDED);
	Configure(&TWCSThrottle, MODE_EXCLUDED);
	Configure(&TFRPHARudder, MODE_EXCLUDED);
	if(Init(&EventHandle)) return 1;
	SetKBRate(32, 50);
	SetKBLayout(KB_ENG);
	SetShiftButton(0, 0, 0, 0, 0, 0);
	MapKeyIOUMD(&T16000, H1U, L_ALT+'w', L_ALT+'w', L_ALT+'w', L_ALT+'w', L_ALT+'w', L_ALT+'w');
	MapKeyIOUMD(&T16000, H1D, L_ALT+'s', L_ALT+'s', L_ALT+'s', L_ALT+'s', L_ALT+'s', L_ALT+'s');
	MapAxis(&T16000, JOYY, DX_Y_AXIS, AXIS_NORMAL, MAP_ABSOLUTE);
	SetSCurve(&T16000, JOYY, 0, 0, 0, 0, 0);
	MapAxis(&T16000, RUDDER, DX_X_AXIS, AXIS_NORMAL, MAP_ABSOLUTE);
	SetSCurve(&T16000, RUDDER, 0, 0, 0, 0, 0);
	MapAxis(&T16000, JOYX, DX_ZROT_AXIS, AXIS_NORMAL, MAP_ABSOLUTE);
	SetSCurve(&T16000, JOYX, 0, 0, 0, 0, 0);
	MapAxis(&T16000, THR, DX_SLIDER_AXIS, AXIS_NORMAL, MAP_ABSOLUTE);
	SetSCurve(&T16000, THR, 0, 0, 0, 0, 0);
	MapKey(&T16000, B14,
					SEQ(
						EXEC("printf(\"    Switching to Plane Controls (Twist = Yaw\; X = Roll) \\xa\");"
							"MapAxis(&T16000, RUDDER, DX_ZROT_AXIS, AXIS_NORMAL, MAP_ABSOLUTE);"
							"SetSCurve(&T16000, RUDDER, 0, 0, 0, 0, 0);"
							"MapAxis(&T16000, JOYX, DX_X_AXIS, AXIS_NORMAL, MAP_ABSOLUTE);"
							"SetSCurve(&T16000, JOYX, 0, 0, 0, 0, 0);"
							),
						EXEC("printf(\"    Switching to Rocket Controls (Twist = Roll; X = Yaw) \\xa\");"
							"MapAxis(&T16000, RUDDER, DX_X_AXIS, AXIS_NORMAL, MAP_ABSOLUTE);"
							"SetSCurve(&T16000, RUDDER, 0, 0, 0, 0, 0);"
							"MapAxis(&T16000, JOYX, DX_ZROT_AXIS, AXIS_NORMAL, MAP_ABSOLUTE);"
							"SetSCurve(&T16000, JOYX, 0, 0, 0, 0, 0);"
							)
					)
			);							
	printf("\xa    Written in part by Nocturnalverse\xa");
	printf("    Defaulting to Rocket Controls (Twist = Yaw) \xa");
}
int EventHandle(int type, alias o, int x)
{
	DefaultMapping(&o, x);
}

 

Edited by Jikahn
Link to comment
Share on other sites

  • 2 weeks later...
This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...