Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add and integrate bone scanning for visibility checking and Implement the visibility check for non-silent Aimbot #75

Open
Billybishop opened this issue Sep 24, 2021 · 9 comments
Labels
enhancement New feature or request

Comments

@Billybishop
Copy link

Billybishop commented Sep 24, 2021

Updated this enhancement in respect to latest code changes and documented the current goals in comment #75.

Goals reference, as described in my update comment:

  1. Improve the visibility checking procedure by changing the way in which we get the player's target bone;
    This proposed improvement should add & implement a new method, or replace the existing method #TryGetHeadTransform, and name it #TryGetVisibleTransform.
    private static bool TryGetHeadTransform(Player player, [NotNullWhen(true)] out Transform? transform)
    • Method summary: Create a shortened list of vital bone types from a provided Player argument, then loop through each one in order of vitality and immediately return the first bone that is visible. If none are visible the Aimbot should skip the current Player in the hostiles list.
    • Considerations: Replace references to #TryGetHeadTransform. Specifically in the #TryGetNearestTarget method:
      if (!TryGetHeadTransform(hostile, out var hostileTransform))
  2. With the above proposed changes added, the Non-Silent Aimbot will then naturally be performing visibility checking through the new bone-scanning method and then the visibility check for the Silent Aimbot can be removed.
    if (!camera.IsTransformVisible(nearestTarget))
    In both mentioned cases this is necessary thanks to #TryGetNearestTarget now using the new bone scan method.

Example snippet of bone scanning for best visible transform, implemented and tested in an earlier branch:

PlayerBones bones = player.PlayerBones;

//Make a collection of target bones ordered by vitality (Head, Neck, Body, Pelvis, Upper Arms, Upper Legs)
IEnumerable<AimBone> target_bones = new AimBone[8] {
	new AimBone(bones.Head),
	new AimBone(bones.Neck),
	new AimBone(bones.Spine1),
	new AimBone(bones.Pelvis),
	new AimBone(bones.Upperarms[0]),
	new AimBone(bones.Upperarms[1]),
	new AimBone(bones.LeftThigh1),
	new AimBone(bones.RightThigh1)
}; //Used a hybrid AimBone class due to differing bone transform types with no mutual interface/inheritance

//LINQ is short and sweet but slows down execution due to the added overhead
//return target_bones.FirstOrDefault(bone => _camera.IsTransformVisible(bone.Position));

//Use foreach here instead since it is faster than LINQ and Aimbot needs to be optimized for speed
foreach (AimBone bone in target_bones)
	if (_camera.IsTransformVisible(bone.Position))
		return bone.Position;

Old description:

The Aimbot should only acquire targets that are visible from your camera and not behind geometry. We already have a way to verify if a world position is within your viewport so now we need a way to verify the player is physically visible. The visibility check for a player object should ideally consider the best bone position but in the event that only part of the player is visible, such as a limb, then the visibility method should be capable of returning the best bone out of what is visible...

If only a right arm and right leg are visible of a given player, the visibility check will return the best bone out of those visibly available which would be in this order as a general example : right upper arm > right thigh > right forearm.
So, in conclusion the visibility check would return the upper arm first in this visibility case.

The visibility check should do a physics raycast check from the player's camera position (in the main thread) to the provided bone position to determine if there is any blocking geometry. If the raycast fails, then there is no geometry in the way. The distance the ray cast travels should be limited by a small degree so as to not collide with the geometry of armor or items that the potential target player is wearing (such as armor, weapon, backpack, vests, etc) since understandably this geometry will always be present.

This design should suffice in 99% of cases to check for visibility, however the best 1:1 approach would be not to limit the ray cast's travel distance for checking geometry collision, and instead include player equipped items/armor as part of the potentially visible player component.

I started working on this enhancement and still need to test/debug - I will be sure to post in this thread with any updates or successes.

@Billybishop
Copy link
Author

Billybishop commented Sep 26, 2021

Hi all, great news! It's done.
(I was drunk and patching things together from other projects, and trying stuff on my own. Please forgive me if this is not well credited)

1. I finished implementing and testing a fast visibility check that is very accurate - I added the following to the CameraExtensions (Note the importance of the layer mask here):

private static RaycastHit _ray;
private static readonly LayerMask _layer_mask = 1 << 12 | 1 << 16 | 1 << 18 | 1 << 31 | 1 << 22;
public static bool IsPlayerBoneVisible(this Camera camera, Player player, Vector3 target_bone)
{
	return (Physics.Linecast(camera.transform.position, target_bone, out _ray, _layer_mask) && _ray.collider && _ray.collider.gameObject.transform.root.gameObject == player.gameObject.transform.root.gameObject);
}

2. And then I wrote a nearly perfect method for scanning and returning the best bone to target out of only the parts of the Player that is currently visible:

public static Vector3 GetBestVisibleBone(Player player, float cached_distance = 0.0f)
{
	Vector3 null_position = Vector3.zero;

	if (Aimbot._camera == null)
		return null_position;

	PlayerBones bones = player.PlayerBones;
	if (bones == null)
		return null_position;

	float null_distance = 3.0f;
	float min_distance = 7.5f;
	float distance = (cached_distance != 0.0f) ? cached_distance : Vector3.Distance(Aimbot._camera.transform.position, player.Transform.position);
	//Don't check for visibility if player is extremely close to the camera because visibility has weird behavior at this distance
	if (distance <= null_distance)
	{
		render_text_player_visible = $"TARGET TOO CLOSE";
		return player.PlayerBones.Spine1.position; 
	}

	if (distance <= min_distance && Aimbot._camera.IsPlayerBoneVisible(player, player.PlayerBones.Spine1.position)) //Stick to one bone if we're very close to the player
	{
		//Visibility debug
		render_text_player_visible = $"TARGET: [{player.name}->{player.PlayerBones.Spine1.name}]";
		return player.PlayerBones.Spine1.position;
	}
	else if (distance > min_distance)
	{
		//Make a collection of target bones starting with the most important followed by the other bones in order of vitality
		IEnumerable<AimBone> target_bones = new AimBone[16] {
			new AimBone(bones.Neck),
			new AimBone(bones.Head),
			new AimBone(bones.Shoulders[0]),
			new AimBone(bones.Shoulders[1]),
			new AimBone(bones.Spine1),
			new AimBone(bones.Upperarms[0]),
			new AimBone(bones.Upperarms[1]),
			new AimBone(bones.Forearms[0]),
			new AimBone(bones.Forearms[1]),
			new AimBone(bones.Pelvis),
			new AimBone(bones.LeftPalm),
			new AimBone(bones.LeftThigh1),
			new AimBone(bones.RightThigh1),
			new AimBone(bones.LeftThigh2),
			new AimBone(bones.RightThigh2),
			new AimBone(bones.KickingFoot)
		}; //Hybrid AimBone class is necessary due to differing bone transform types that do not share interfaces/inheritance

		//Scan the desired bones for visibility
		foreach (AimBone bone_info in target_bones)
		{
			bool is_visible = Aimbot._camera.IsPlayerBoneVisible(player, bone_info.Position);
			if (is_visible)
			{
				//Visibility debug
				render_text_player_visible = $"TARGET: [{player.name}->{bone_info.Name}]";
				return bone_info.Position;
			}
		}
	}
	
	return null_position;
}

3. The above method uses a translation class I wrote that can accept the different Transform types (This is necessary because Transform and BifacialTransform do not share an interface or inheritance). So, this just simplifies the work needed to use the different types which represent the bones. Please note the comment about C# dynamic type, as it was tried and found not to be supported with the current EFT/AKI binaries - even with CSharp and ReflectionType libraries provided - which is why this simple class is so convoluted.

//NOTE: This used to use dynamic type originally but that requires Microsoft.CSharp.dll v4 and related dependencies which are not currently supported in EFT/AKI
public class AimBone
{
	private readonly string _name;
	public string Name { get { return _name; } }
	private readonly object _bone;
	public object Bone { get { return _bone; } }
	private readonly Vector3 _bone_position;
	public Vector3 Position { get { return _bone_position; } }

	public AimBone(object aim_bone)
	{
		if (aim_bone is Transform)
		{
			_name = ((Transform)aim_bone).name;
			_bone_position = ((Transform)aim_bone).position;
		}
		else if (aim_bone is BifacialTransform)
		{
			_name = ((BifacialTransform)aim_bone).Original.name;
			_bone_position = ((BifacialTransform)aim_bone).position;
		}
		else
		{
			_name = "Unsupported Bone Type";
			_bone_position = Vector3.zero;
		}

		_bone = aim_bone;
	}
}

@sailro, I would very much appreciate if this could be implemented in the master branch. Also, if anyone would like please feel free to optimize these methods if you find a faster/simpler way to do something, I gave it my best effort. Lastly, you may notice how I strongly type all of my variables, I prefer it this way for readability but it has no runtime impact either way AFAIK.

@sailro
Copy link
Owner

sailro commented Sep 28, 2021

Could you show how you integrate this with the current code?

@Billybishop
Copy link
Author

Could you show how you integrate this with the current code?

Sure thing! Here is how my Update method looks like for the Aimbot class, which acquires the Vec3 target position 'target_pos' through usage of #GetBestVisibleBone. I'm still in the process of adding a FOV calculation so this will surely change, but I will document that as another enhancement soon:

protected override void UpdateWhenHold()
{
	GameStateSnapshot? state = GameState.Current;
	if (state == null)
		return;

	Player? localPlayer = state.LocalPlayer;
	if (localPlayer == null)
		return;

	Aimbot._camera = state.Camera;
	if (Aimbot._camera == null)
		return;

	if (localPlayer.Weapon.IsMeleeWeapon())
		return;

	Vector3 nearestTarget = Vector3.zero;
	float nearestTargetDistance = float.MaxValue;

	//GameState ensures 'Hostiles' list will not include your own Player object, so no need to check for it here
	foreach (var player in state.Hostiles)
	{
		if (player == null)
			continue;

		float distance = Vector3.Distance(Aimbot._camera.transform.position, player.Transform.position);
		if (distance >= nearestTargetDistance || distance > this.MaximumDistance)
			continue;

		Vector3 target_pos = GetBestVisibleBone(player, distance); //Find the best visible bone for this potential target, also behaves as a full visibility check
		if (target_pos == Vector3.zero) //Empty Vec3 means no bone was visible, therefore this target is completely behind geometry
		{
			render_text_player_visible = (render_text_player_visible != "TARGET TOO CLSOE") ? "NO VISIBLE TARGET" : render_text_player_visible;
			continue;
		}

		//I'm 82% sure this isn't necessary but do it anyway just incase Target magically teleports off screen
		Vector3 screenPosition = Aimbot._camera.WorldPointToScreenPoint(target_pos);
		if (!Aimbot._camera.IsScreenPointVisible(screenPosition))
			continue;

		//Used to get the speed of the Ammo we're currently using - still need to account for ballistic arc and deceleration
		AmmoTemplate? template = localPlayer.Weapon?.CurrentAmmoTemplate;
		if (template == null)
			continue;

		//This needs some work - it is under-estimating the destination when target is farther away and slowing down or over-estimating when target is farther away but stationary or moving quickly - we should check when target becomes stationary and reset the target position
		nearestTargetDistance = distance;
		float travelTime = (distance / template.InitialSpeed);
		target_pos.x += (player.Velocity.x * travelTime);
		target_pos.y += (player.Velocity.y * travelTime);

		nearestTarget = target_pos;
	}

	if (nearestTarget != Vector3.zero)
		AimAtPosition(localPlayer, nearestTarget, this.SmoothPercent);
}

Also, on another note, I like your improvement to the #NormalizeAngle that was included in the Aim smoothing enhancement! Looks good 👍

I'll try to get a fork up soon and keep it up to date w/ your Master branch since I plan on supporting only latest version EFT/AKI - making proper pull requests where applicable - and then that fork can act as an experimental features test bed if we'd like.

@sailro sailro added the enhancement New feature or request label Sep 29, 2021
@Billybishop
Copy link
Author

I made some significant improvements to the visibility check that I will get up on Github here in the next few days. Basically I added another visibility check method that does as I originally planned - casts a ray with a magnitude of 90% - instead of line casting. I kept the original visibility check because it can be useful in other circumstances. Anyway, please disregard the above code and maybe I'll do an official pull request this time ;)

@Billybishop
Copy link
Author

Since it's been 8 days I think that's longer than ""in the next few days"" lol, so here is my complete source on visibility checks. I reconstructed IsPlayerBoneVisibile3 from looking at EFT source in dnSpy, it is too accurate sometimes and I think the layer masks need adjusted, otherwise it could be better than IsPlayerBoneVisible2...

//This is faster and somehow more accurate
public static bool IsPlayerBoneVisible2(this Camera camera, Vector3 target_bone)
{
	GameStateSnapshot? current = GameState.Current;
	if (current == null) return false;

	Player? local_player = current.LocalPlayer;
	if (local_player == null) return false;

	//Vector3 camera_position = camera.transform.position;
	Vector3 fireport = local_player.Fireport.position;
	fireport.y = (fireport.y + Aimbot._sight_adjustment);
	Vector3 direction = (target_bone - fireport);
	float distance = (Vector3.Magnitude(direction) * 0.975f);

	return !(Physics.Raycast(fireport, direction, distance, GClass503.HighPolyWithTerrainMask.value, QueryTriggerInteraction.UseGlobal));
}

//This is experimental
public static bool IsPlayerBoneVisible3(this Camera camera, Vector3 target_bone)
{
	return !GClass420.LinecastPrecise(camera.transform.position, target_bone, out _ray, GClass1974.HitMask, true, _rays, new Func<RaycastHit, bool>(CameraExtensions.IsIgnored));
}

@sailro
Copy link
Owner

sailro commented Oct 17, 2021

GClass420 will break on the next update. EFT is using a simple obfuscator changing non public names for every release.

So the best way is to avoid depending on those generated names (perhaps next time it will be GClass421 instead of GClass420), you should find an alternative way to get access, perhaps using Reflection.

Like I did here: https://github.com/sailro/EscapeFromTarkov-Trainer/blob/master/Features/Commands.cs#L138

@Billybishop
Copy link
Author

GClass420 will break on the next update. EFT is using a simple obfuscator changing non public names for every release.

So the best way is to avoid depending on those generated names (perhaps next time it will be GClass421 instead of GClass420), you should find an alternative way to get access, perhaps using Reflection.

Like I did here: https://github.com/sailro/EscapeFromTarkov-Trainer/blob/master/Features/Commands.cs#L138

Ah thanks for the tip, I'll look into this.

@sailro
Copy link
Owner

sailro commented Feb 6, 2022

Silent-aim is now using visibility check

@Billybishop Billybishop changed the title Add visibility check to Aimbot that is enforced when 'Wallshoot' is off Add bone scan to Aimbot and Implement the visibility check for non-silent Aimbot Mar 4, 2022
@Billybishop Billybishop changed the title Add bone scan to Aimbot and Implement the visibility check for non-silent Aimbot Add and integrate bone scanning to visibility checking and Implement the visibility check for non-silent Aimbot Mar 4, 2022
@Billybishop Billybishop changed the title Add and integrate bone scanning to visibility checking and Implement the visibility check for non-silent Aimbot Add and integrate bone scanning for visibility checking and Implement the visibility check for non-silent Aimbot Mar 4, 2022
@Billybishop
Copy link
Author

Billybishop commented Mar 4, 2022

I updated this issue's title with respect to your latest additions.

Now the two goals of this enhancement are as follows:

  1. Improve the visibility checking procedure by changing the way in which we get the player's target bone;
    This proposed improvement should add & implement a new method, or replace the existing method #TryGetHeadTransform,
    and name it #TryGetVisibleTransform.
    private static bool TryGetHeadTransform(Player player, [NotNullWhen(true)] out Transform? transform)
    • Method summary: Create a shortened list of vital bone types from a provided Player argument, then loop through each one in order of vitality and immediately return the first bone that is visible. If none are visible the Aimbot should skip the current Player in the hostiles list.
    • Considerations: Replace references to #TryGetHeadTransform. Specifically in the #TryGetNearestTarget method:
      if (!TryGetHeadTransform(hostile, out var hostileTransform))
  2. With the above proposed changes added, the Non-Silent Aimbot will then naturally be performing visibility checking through the new bone-scanning method and then the visibility check for the Silent Aimbot can be removed.
    if (!camera.IsTransformVisible(nearestTarget))
    In both mentioned cases this is necessary thanks to #TryGetNearestTarget now using the new bone scan method.

Example snippet of bone-scanning for visible transform, implemented and tested in earlier versions:

PlayerBones bones = player.PlayerBones;

//Make a collection of target bones ordered by vitality (Head, Neck, Body, Pelvis, Upper Arms, Upper Legs)
IEnumerable<AimBone> target_bones = new AimBone[8] {
	new AimBone(bones.Head),
	new AimBone(bones.Neck),
	new AimBone(bones.Spine1),
	new AimBone(bones.Pelvis),
	new AimBone(bones.Upperarms[0]),
	new AimBone(bones.Upperarms[1]),
	new AimBone(bones.LeftThigh1),
	new AimBone(bones.RightThigh1)
}; //Used a hybrid AimBone class due to differing bone transform types with no mutual interface/inheritance

//LINQ is short and sweet but slows down execution due to the added overhead
//return target_bones.FirstOrDefault(bone => _camera.IsTransformVisible(bone.Position));

//Use foreach here instead since it is faster than LINQ and Aimbot needs to be optimized for speed
foreach (AimBone bone in target_bones)
	if (_camera.IsTransformVisible(bone.Position))
		return bone.Position;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants