Shoulder Surfing Reloaded

Shoulder Surfing Reloaded

26M Downloads

Add IPlayerStateCallback to support custom input binds and state

EchoEllet opened this issue Β· 20 comments

commented

Support custom input and player state

Add a callback to allow third-party mods to register custom keybinds, rather than being limited to the vanilla minecraft.options.keyAttack.isDown() in ShoulderSurfingImpl.isAttacking (for example). This helps avoid hacks like this (see issue epicfight#2111).

commented

Very interesting find. While reviewing your PR and my own code, I was asking myself why this cannot be implemented using the already existing IAdaptiveItemCallback.

I was already able to solve the Epic Fight custom attack keybind issue with ICameraCouplingCallback callback.

This line indicates that it temporarily disables camera decoupling.

As for shouldEntityAimAtTargetInternal, it seems to have turningLockTime where the ICameraCouplingCallback method does not.

In ShoulderSurfingImpl.shouldEntityAimAtTargetInternal, the || !this.isAiming has no effect as far as I can tell:

It does have an effect; removing it will not play the turningLockTime effect, so it will look at crosshair directly. I would suggest refactoring shouldEntityAimAtTargetInternal to simplify the line reading.

Though, the name of the callback is not very suitable in this specific case.

Should we deprecate it and keep it backward compatible? Maybe with a bit more planning we can figure something out. The name isn't the only change though.

What is the difference between setting isCameraDecoupled to false or causing shouldEntityAimAtTargetInternal to return true which will set turningLockTime and call lookAtCrosshairTargetInternal (done inside tick)?

I would avoid the case where we have 3 different callbacks to achieve the exact same thing but with slight differences, maybe just a one callback that provides a record that contains many useful APIs such as TurningMode. It might be still useful to have a dedicated attack detection callback, even if it's not useful today, Shoulder Surfing could use of that info in future updates.

Thoughts?

commented

This helps avoid hacks like this (see Epic-Fight/epicfight#2111).

Very interesting find. While reviewing your PR and my own code, I was asking myself why this cannot be implemented using the already existing IAdaptiveItemCallback.

What do you think of the following:

In ShoulderSurfingImpl.shouldEntityAimAtTargetInternal, the || !this.isAiming has no effect as far as I can tell:

private boolean shouldEntityAimAtTargetInternal(LivingEntity cameraEntity, Minecraft minecraft)
{
return this.isAiming && Config.CLIENT.getCrosshairType().isAimingDecoupled() || !this.isAiming && this.isCameraDecoupled() &&
(isUsingItem(cameraEntity, minecraft) || !cameraEntity.isFallFlying() && (isInteracting(cameraEntity, minecraft) &&
!(Config.CLIENT.getPickVector() == PickVector.PLAYER && Config.CLIENT.getCrosshairType() == CrosshairType.DYNAMIC) ||
isAttacking(minecraft) || isPicking(minecraft) || cameraEntity instanceof Player player && player.isScoping()));
}

We could change the method to:

private boolean shouldEntityAimAtTargetInternal(LivingEntity cameraEntity, Minecraft minecraft)
{
- 	return this.isAiming && Config.CLIENT.getCrosshairType().isAimingDecoupled() || !this.isAiming && this.isCameraDecoupled() && 
- 		(isUsingItem(cameraEntity, minecraft) || !cameraEntity.isFallFlying() && (isInteracting(cameraEntity, minecraft) && 
+	return this.isAiming && Config.CLIENT.getCrosshairType().isAimingDecoupled() || this.isCameraDecoupled() && (this.isAiming ||
+		isUsingItem(cameraEntity, minecraft) || !cameraEntity.isFallFlying() && (isInteracting(cameraEntity, minecraft) &&
			!(Config.CLIENT.getPickVector() == PickVector.PLAYER && Config.CLIENT.getCrosshairType() == CrosshairType.DYNAMIC) ||
			isAttacking(minecraft) || isPicking(minecraft) || cameraEntity instanceof Player player && player.isScoping()));
}

This would trigger the player to aim at a target when isAiming is true (and the camera is decoupled), which can be controlled using the IAdaptiveItemCallback. Though, the name of the callback is not very suitable in this specific case.

commented

What is the difference between setting isCameraDecoupled to false or causing shouldEntityAimAtTargetInternal to return true which will set turningLockTime and call lookAtCrosshairTargetInternal (done inside tick)?

I don't fully understand your question πŸ˜…. isCameraDecoupled contronls whether the camera is coupled and shouldEntityAimAtTargetInternal is for checking whether the camera entity should aim at the position of the crosshair. The latter is called internal, because it is not affected by the turningLockTime, and not intended for external use.

I would avoid the case where we have 3 different callbacks to achieve the exact same thing but with slight differences, maybe just a one callback that provides a record that contains many useful APIs such as TurningMode.

Yes, this sounds like a good idea. Something like IPlayerInputCallback or IUserInputCallback, which has several methods for controlling the aiming, interaction, etc. I will give it more thought when I find some time (most likely the weekend).

commented

I don't fully understand your question πŸ˜…. isCameraDecoupled contronls whether the camera is coupled and shouldEntityAimAtTargetInternal is for checking whether the camera entity should aim at the position of the crosshair.

Yes, but when the camera is not decoupled (using ICameraCouplingCallback), the player already looks at the crosshair target, right? I understand there is a difference, but I don’t notice it during gameplay.

My suggestion is to refactor the new interface introduced in #346 to control shouldEntityAimAtTarget (or shouldEntityAimAtTargetInternal) by splitting it into multiple specialized methods:

  • isAttacking
  • isAiming
  • isInteracting
  • isPicking
  • A final method that intercepts the computed result, allowing full control over the returned value. This method can either return the original result, override it with a custom boolean, or force a constant value of true or false.

This design minimizes assumptions about how mods will use the Shoulder Surfing API, allowing the API itself to determine the player’s state more accurately.

commented

Yes, but when the camera is not decoupled (using ICameraCouplingCallback), the player already looks at the crosshair target, right? I understand there is a difference, but I don’t notice it during gameplay.

Not necessarily. If you disable camera coupling using ICameraCouplingCallback, the camera is coupled, but the player will still look straight (not at the position of your crosshair).

My suggestion is to refactor the new interface introduced in #346 to control shouldEntityAimAtTarget (or shouldEntityAimAtTargetInternal) by splitting it into multiple specialized methods:

Sounds good!

commented

the camera is coupled, but the player will still look straight (not at the position of your crosshair).

That makes sense! Now I understand it, I discovered a bug in my mod when the camera is coupled, and the fix is to use IAdaptiveItemCallback rather than ICameraCouplingCallback.

With ICameraCouplingCallback

Before.mp4

With IAdaptiveItemCallback

After.mp4

The callback name IAdaptiveItemCallback might be a bit confusing? Since isAiming is updated depending on IAdaptiveItemCallback.isHoldingAdaptiveItem

this.isAiming = isHoldingAdaptiveItem(minecraft, minecraft.getCameraEntity());

My suggestion is to refactor the new interface introduced in #346 to control shouldEntityAimAtTarget (or shouldEntityAimAtTargetInternal) by splitting it into multiple specialized methods:

isAttacking
isAiming

Should we deprecate IAdaptiveItemCallback.isHoldingAdaptiveItem to replace it with isAiming? Maybe the new callback could be called IPlayerStateCallback? With all these 5 methods.

commented

Should we deprecate IAdaptiveItemCallback.isHoldingAdaptiveItem to replace it with isAiming? Maybe the new callback could be called IPlayerStateCallback? With all these 5 methods.

I think we can do that, but we need to make sure it does not change current behavior, which might be tricky, since the code is not that well.

commented

I just discovered ShoulderSurfingImpl.isAiming isn't specifically about bow or crossbow aiming.

A player can use a crossbow item by interacting with it (right-click), which will cause isInteracting to return true.

isAiming
A final method that intercepts the computed result, allowing full control over the returned value. This method can either return the original result, override it with a custom boolean, or force a constant value of true or false.

This means we shouldn't add both of theseβ€”one is enough. It should indicate whether the player is looking at the crosshair target, rather than actual aiming, since the term "aiming" can be confused with bow aiming.

commented

IMO, we need:

  • isAttacking -> left click
  • isInteracting -> right click
  • isPicking -> middle click

Looking at the code, there may be one additional method:

  • isUsingItem

And maybe one method that overrides the final result, as you have already suggested, for any other edge cases. But maybe that one is also not needed.

commented

Yeah, that makes more sense. Though, what we should call the last callback? Maybe isAiming with a clarification that this isn't the vanilla bow aiming, so if the player is using a bow, this doesn't mean that this will be true. It looks like this is mainly added to support IAdaptiveItemCallback.

commented

I think we can integrate the old IAdaptiveItemCallback in the new isAttacking() callback.

Maybe isAiming with a clarification that this isn't the vanilla bow aiming, so if the player is using a bow, this doesn't mean that this will be true. It looks like this is mainly added to support IAdaptiveItemCallback.

Maybe finalize? I don't know. We can come up with a better name later.

commented
Proposed API design

/**
 * This callback allows implementing custom logic to let Shoulder Surfing determine whether the player is attacking, aiming, interacting with an item, or picking (mouse middle click).
 * <p>
 * The exact behavior of what happens when any of these is <code>true</code> is controlled by Shoulder Surfing and depends on the corresponding method. For example, when <code>isAttacking</code> is <code>true</code>, the player entity will aim at the target. The final result is calculated from all partial results using a logical OR. If no callback provides a definitive result, the default logic is used.
 */
public interface IPlayerStateCallback
{
	/**
	 * Determines whether the player is currently attacking.
	 * <p>
	 * Default value expression:
	 * <pre>{@code
	 * minecraft.options.keyAttack.isDown() &&
	 * Config.CLIENT.getTurningModeWhenAttacking().shouldTurn(minecraft.hitResult)
	 * }</pre>
	 * <p>
	 * <p>
	 * An example use case is to support custom keybinds that differ from the vanilla attack key, or for a controller mod that supports only input/button binds.
	 *
	 * @param context The arguments of this callback.
	 *                <ul>
	 *                  <li>{@link Result#TRUE} – forces the attack state to <code>true</code></li>
	 *                  <li>{@link Result#FALSE} – forces the attack state to <code>false</code></li>
	 *                  <li>{@link Result#PASS} – ignores this callback and lets others or the default logic decide</li>
	 *                </ul>
	 */
	Result isAttacking(IsAttackingContext context);
	
	record IsAttackingContext(Minecraft minecraft, TurningMode turningModeWhenAttacking, boolean defaultIsAttacking)
	{
	}
	
	/**
	 * Determines whether the player is currently interacting with an item.
	 * <p>
	 * Default value expression:
	 * <pre>{@code
	 * minecraft.options.keyUse.isDown() && !cameraEntity.isUsingItem() &&
	 * Config.CLIENT.getTurningModeWhenInteracting().shouldTurn(minecraft.hitResult)
	 * }</pre>
	 * <p>
	 *
	 * An example use case is to support custom keybinds that differ from the vanilla use key, or for a controller mod that supports only input/button binds.
	 *
	 * @param context The arguments of this callback.
	 *                <ul>
	 *                  <li>{@link Result#TRUE} – forces the interacting state to <code>true</code></li>
	 *                  <li>{@link Result#FALSE} – forces the interacting state to <code>false</code></li>
	 *                  <li>{@link Result#PASS} – ignores this callback and lets others or the default logic decide</li>
	 *                </ul>
	 */
	Result IsInteracting(IsInteractingContext context);
	
	record IsInteractingContext(Minecraft minecraft, LivingEntity cameraEntity, TurningMode turningModeWhenInteracting, CrosshairType crosshairType, boolean defaultIsInteracting)
	{
	}
	
	/**
	 * Determines whether the player is currently picking with an item.
	 * <p>
	 * Default value expression:
	 * <pre>{@code
	 * minecraft.options.keyPickItem.isDown() && Config.CLIENT.getTurningModeWhenPicking().shouldTurn(minecraft.hitResult)
	 * }</pre>
	 * <p>
	 *
	 * An example use case is to support custom keybinds that differ from the vanilla pick key (mouse middle button), or for a controller mod that supports only input/button binds.
	 *
	 * @param context The arguments of this callback.
	 *                <ul>
	 *                  <li>{@link Result#TRUE} – forces the picking state to <code>true</code></li>
	 *                  <li>{@link Result#FALSE} – forces the picking state to <code>false</code></li>
	 *                  <li>{@link Result#PASS} – ignores this callback and lets others or the default logic decide</li>
	 *                </ul>
	 */
	Result isPicking(IsPickingContext context);
	
	record IsPickingContext(Minecraft minecraft, TurningMode turningModeWhenPicking, boolean defaultIsPicking)
	{
	}
	
	/**
	 * Determines whether the player is currently using an item.
	 * <p>
	 * Default value expression:
	 * <pre>{@code
	 * cameraEntity.isUsingItem() && Config.CLIENT.getTurningModeWhenUsingItem().shouldTurn(minecraft.hitResult) &&
	 * !cameraEntity.getUseItem().has(DataComponents.FOOD)
	 * }</pre>
	 * <p>
	 *
	 * @param context The arguments of this callback.
	 *                <ul>
	 *                  <li>{@link Result#TRUE} – forces the using state to <code>true</code></li>
	 *                  <li>{@link Result#FALSE} – forces the using state to <code>false</code></li>
	 *                  <li>{@link Result#PASS} – ignores this callback and lets others or the default logic decide</li>
	 *                </ul>
	 */
	Result isUsingItem(IsInteractingContext context);
	
	record IsUsingContext(Minecraft minecraft, LivingEntity cameraEntity, TurningMode turningModeWhenUsingItem, boolean defaultUsInteracting)
	{
	}

	/**
	 * Represents the possible outcomes of an {@link IPlayerStateCallback}.
	 */
	enum Result
	{
		TRUE,
		FALSE,
		/** Defers to other callbacks or the default logic. */
		PASS
	}
}

The name IPlayerStateCallback suggests it's about the player state (attacking, using, picking, interacting).

If we introduce finalize to IPlayerStateCallback, would it override shouldEntityAimAtTargetInternal?

I'm not entirely sure what this callback is meant for. It doesn't actually determine whether the player should look at an enemy; it only provides the player state to Shoulder Surfing. Shoulder Surfing then decides the behavior, which might change in future versions or if the user modifies their configuration.

The consumers of this API should probably not be aware of what will Shoulder Surfing do with this state, or maybe even the behavior. IPlayerStateCallback is the perfect candidate to solve issues like #347 and Epic-Fight/epicfight#2111. These mods just register their custom keybind, controller mod integration, some non-vanilla standard state, they shouldn't care about what will Shoulder Surfing do, they just provide their custom state and that's all.

Regarding shouldEntityAimAtTargetInternal, IAdaptiveItemCallback works fine. The only issue is that the name is a bit confusing. I suggest to deprecate it in exchange for a more flexible API that does the same thing with a better name.

So, IPlayerStateCallback and another callback that replaces IAdaptiveItemCallback rather than mixing these two concerns into one callback.

I think we can integrate the old IAdaptiveItemCallback in the new isAttacking() callback.

IAdaptiveItemCallback is different than IPlayerStateCallback, it allows consumers to actually override shouldEntityAimAtTargetInternal and it makes sense to let the consumers be aware of that, the name is misleading, but it seems that ShoulderSurfingImpl.isAiming was added for this callback only. This isn't just about fixing the isAttacking issue anymore, there will be likely more compatibility issues with other mods, so we should address them all at once.

Note

Apologies for the confusing again!

It looks like isAiming doesn't just affect the value shouldEntityAimAtTargetInternal, but also the crosshair, so I would add IPlayerStateCallback.isAimingAtTarget and explain it well in docs. What do you think?

commented

Yes, sounds good. Thanks for the effort!

commented

One last issue: the method isAttacking currently mixes input state with gameplay logic:

// This should probably be renamed to shouldTurnWhenAttacking
private static boolean isAttacking(Minecraft minecraft)
	{
		return minecraft.options.keyAttack.isDown() && Config.CLIENT.getTurningModeWhenAttacking().shouldTurn(minecraft.hitResult);
	}

The problem is that if a player uses a mod that provides a custom attack keybind, or a controller mod with custom input handling, minecraft.options.keyAttack.isDown() will always return false. Ideally, we should allow third-party mods to register their own input bindings without duplicating this logic, while still supporting the vanilla key attack:

private static boolean isAttacking(Minecraft minecraft)
	{
		return (minecraft.options.keyAttack.isDown() || My_CUSTOM_INPUT_BINDING.digitalNow()) && Config.CLIENT.getTurningModeWhenAttacking().shouldTurn(minecraft.hitResult);
	}

Third-party mods should not need to know about internal logic like Config.CLIENT.getTurningModeWhenAttacking().shouldTurn(minecraft.hitResult). The purpose of the method should be to provide the raw attack state, not whether the player should turn (that's business logic).

In my opinion, IPlayerStateCallback.isAttacking should simply represent the attack state, independent of turning logic, and be implemented as a logical OR between minecraft.options.keyAttack.isDown() and any custom input bindings.

Though I'm not sure if renaming this callback to IPlayerInputStateCallback either, because in vanilla game, when we're pressing the attack key, we're attacking, and it's as simple as that, but in modded environments, it's really hard to assume that, maybe a mod don't want to allow attacking at all in certain scenario or it has some custom state that can't be predicated, nor we should try to predicate anything.

Updated code

private static boolean isAttacking(Minecraft minecraft)
	{
		return minecraft.options.keyAttack.isDown();
	}
	
	private static boolean shouldTurnWhenAttacking(Minecraft minecraft)
	{
		return isAttacking(minecraft) && Config.CLIENT.getTurningModeWhenAttacking().shouldTurn(minecraft.hitResult);
	}

// Updated to use shouldTurnWhenAttacking instead of isAttacking
private boolean shouldEntityAimAtTargetInternal(LivingEntity cameraEntity, Minecraft minecraft)
	{
		return this.isAiming && Config.CLIENT.getCrosshairType().isAimingDecoupled() || !this.isAiming && this.isCameraDecoupled() &&
			(isUsingItem(cameraEntity, minecraft) || !cameraEntity.isFallFlying() && (isInteracting(cameraEntity, minecraft) &&
				!(Config.CLIENT.getPickVector() == PickVector.PLAYER && Config.CLIENT.getCrosshairType() == CrosshairType.DYNAMIC) ||
				shouldTurnWhenAttacking(minecraft) || isPicking(minecraft) || cameraEntity instanceof Player player && player.isScoping()));
	}

It's probably better to clean these private internals first, before even sending this PR. Thoughts?

If you agree that's the desired behavior, even if it's implemented, it will be a good candidate for fixing a lot of issues, but not the Epic Fight one.

Reason

Regarding this Epic Fight PR: Epic-Fight/epicfight#2112, we want to override the TurningMode so that it is ignored and the player always turns when attacking, as turning mode behavior aligns well with vanilla mechanics, but not Epic fight mechanics.

In this case, we could either force camera coupling or use IAdaptiveItemCallback to make isAiming return true. Using IAdaptiveItemCallback does not seem ideal, as it modifies crosshair rendering, so I opted to force camera coupling instead.

This fix addresses a different issue β€” it's no longer just about raw player state feeding, but about gameplay mechanics. Therefore, it makes sense not to register an IPlayerStateCallback.

It might seem this issue is getting complicated. To simplify it, we need the following:

  • Custom aiming implementation that forces the entity to aim at the target but also supports Shoulder Surfing crosshair. Should replace IAdaptiveItemCallback.
  • Custom player state and input state, for example:
    • Custom player state cameraEntity.isUsingItem()
    • Custom input state minecraft.options.keyAttack.isDown()
  • Custom callback to override shouldEntityAimAtTargetInternal without affecting isAiming, so that it does not affect crosshair rendering or state.

This issue is becoming increasingly difficult to follow, as it combines multiple problems with different objectives. Should we close this one and open new, more focused issues?

commented

My suggestion would be to compute the values of isAiming, isInteracting, etc. in the tick method, and perhaps encapsulate it in a new PlayerInputState class.

Yes, but that callback would control decision-making and is no longer about feeding the player state to Shoulder Surfing.

Custom callback to override shouldEntityAimAtTargetInternal without affecting isAiming, so that it does not affect crosshair rendering or state.

This is what I suggested. Please let me know what you prefer.

commented

It's probably better to clean these private internals first, before even sending this PR. Thoughts?

Yes, this is probably a good idea. My suggestion would be to compute the values of isAiming, isInteracting, etc. in the tick method, and perhaps encapsulate it in a new PlayerInputState class. The shouldEntityAimAtTargetInternal then reads that state and applies the business logic to compute the final result. We should be able to replace the current isAiming field entirely, as it is a bit redundant IMO. Thoughts?

This issue is becoming increasingly difficult to follow, as it combines multiple problems with different objectives. Should we close this one and open new, more focused issues?

You can do that if you want, but we can also continue discussing it in this issue.

commented

Custom callback to override shouldEntityAimAtTargetInternal without affecting isAiming, so that it does not affect crosshair rendering or state.

This one makes more sense. Thanks for the dedication!

commented

Thanks for the update. I agree with everything you said in the previous two comments.

commented

This is the initial design:

API design

/**
 * This callback allows providing the raw input state that Shoulder Surfing uses to determine whether the player is attacking, interacting with an item, or picking (mouse middle click).
 * <p>
 * It does not define what should happen β€” Shoulder Surfing decides that based on its own logic.
 * <p>
 * For example, a controller mod can report that the attack input is pressed using <code>isAttacking</code>, without caring what Shoulder Surfing does in response.
 * <p>
 * Consider using a different API if you want to control the exact behavior. The exact behavior when any of these is <code>true</code> is handled entirely by Shoulder Surfing.
 * <p>
 * The final result is calculated from all partial results using a logical OR.
 * <p>
 * If no callback provides a definitive result, the default logic is used.
 */
public interface IPlayerStateCallback
{
	/**
	 * Determines whether the player is currently attacking.
	 * <p>
	 * Default value expression:
	 * <pre>{@code
	 * minecraft.options.keyAttack.isDown()
	 * }</pre>
	 * <p>
	 * <p>
	 * An example use case is to support custom keybinds that differ from the vanilla attack key, or for a controller mod that supports only input/button binds.
	 *
	 * @param context The arguments of this callback.
	 *                <ul>
	 *                  <li>{@link Result#TRUE} – forces the attack state to <code>true</code></li>
	 *                  <li>{@link Result#FALSE} – forces the attack state to <code>false</code></li>
	 *                  <li>{@link Result#PASS} – ignores this callback and lets others or the default logic decide</li>
	 *                </ul>
	 */
	Result isAttacking(@NotNull IsAttackingContext context);
	
	record IsAttackingContext(@NotNull Minecraft minecraft, boolean defaultIsAttacking)
	{
	}
	
	/**
	 * Determines whether the player is currently interacting with an item.
	 * <p>
	 * Default value expression:
	 * <pre>{@code
	 * minecraft.options.keyUse.isDown() && !cameraEntity.isUsingItem()
	 * }</pre>
	 * <p>
	 *
	 * An example use case is to support custom keybinds that differ from the vanilla use key, or for a controller mod that supports only input/button binds.
	 *
	 * @param context The arguments of this callback.
	 *                <ul>
	 *                  <li>{@link Result#TRUE} – forces the interacting state to <code>true</code></li>
	 *                  <li>{@link Result#FALSE} – forces the interacting state to <code>false</code></li>
	 *                  <li>{@link Result#PASS} – ignores this callback and lets others or the default logic decide</li>
	 *                </ul>
	 */
	Result isInteracting(@NotNull IsInteractingContext context);
	
	record IsInteractingContext(@NotNull Minecraft minecraft, @NotNull LivingEntity cameraEntity, boolean defaultIsInteracting)
	{
	}
	
	/**
	 * Determines whether the player is currently picking with an item.
	 * <p>
	 * Default value expression:
	 * <pre>{@code
	 * minecraft.options.keyPickItem.isDown()
	 * }</pre>
	 * <p>
	 *
	 * An example use case is to support custom keybinds that differ from the vanilla pick key (mouse middle button), or for a controller mod that supports only input/button binds.
	 *
	 * @param context The arguments of this callback.
	 *                <ul>
	 *                  <li>{@link Result#TRUE} – forces the picking state to <code>true</code></li>
	 *                  <li>{@link Result#FALSE} – forces the picking state to <code>false</code></li>
	 *                  <li>{@link Result#PASS} – ignores this callback and lets others or the default logic decide</li>
	 *                </ul>
	 */
	Result isPicking(@NotNull IsPickingContext context);
	
	record IsPickingContext(@NotNull Minecraft minecraft, boolean defaultIsPicking)
	{
	}
	
	/**
	 * Determines whether the player is currently using an item.
	 * <p>
	 * Default value expression:
	 * <pre>{@code
	 * cameraEntity.isUsingItem() && !cameraEntity.getUseItem().has(DataComponents.FOOD)
	 * }</pre>
	 * <p>
	 *
	 * @param context The arguments of this callback.
	 *                <ul>
	 *                  <li>{@link Result#TRUE} – forces the using state to <code>true</code></li>
	 *                  <li>{@link Result#FALSE} – forces the using state to <code>false</code></li>
	 *                  <li>{@link Result#PASS} – ignores this callback and lets others or the default logic decide</li>
	 *                </ul>
	 */
	Result isUsingItem(@NotNull IsUsingContext context);
	
	record IsUsingContext(@NotNull Minecraft minecraft, @NotNull LivingEntity cameraEntity, boolean defaultIsUsing)
	{
	}
	
	/**
	 * Represents the possible outcomes of an {@link IPlayerStateCallback}.
	 */
	enum Result
	{
		TRUE,
		FALSE,
		/** Defers to other callbacks or the default logic. */
		PASS
	}
}

I'm thinking of removing TurningMode because why would consumers need it if they want to provide the raw state and don't care about the behavior?
Providing it feels like violating the core idea. I will remove it for now, and if there is ever an issue with it, we could always add it later without a breaking change since we use a record for the arguments.

Note

The main motivation of IPlayerStateCallback is to avoid hacky mixins and breaking changes, making things more predictable and manageable.
Other features should be added in a separate PR to keep things organized.

commented

I'm back now to work on this issue. IMO, it would be better not to add IsAimingAtTarget as part of the new IPlayerStateCallback.
Since it's not vanilla behavior or input, and the primary goal of IPlayerStateCallback is to provide raw player input state to Shoulder Surfing, it's not about deciding on what should happen.
And IsAimingAtTarget does not seem to be vanilla behavior as shown in the docs. Minecraft does not have a state to track whether the player is aiming, even for the bow/crossbow; it just knows whether the player is interacting with the item, and I have already designed isUsingItem for that.

IsAimingAtTarget Javadocs

/**
 * Determines whether the player is aiming at a target.
 * <p>
 * Note: This does not refer to bow or crossbow aiming in vanilla Minecraft. Instead, it represents a custom aiming state for the player. In vanilla Minecraft, aiming with a bow requires interacting with the item by holding the right mouse button, which causes isInteracting to return {@code true}.
 * <p>
 * By default, the result is <code>true</code> if one of the {@link IAdaptiveItemCallback} callbacks is <code>true</code>, otherwise <code>false</code>.
 * <p>
 *
 * An example use case is to support continuous spell casting for a magic mod.
 *
 * @param context The arguments of this callback.
 *                <ul>
 *                  <li>{@link Result#TRUE} – forces the using state to <code>true</code></li>
 *                  <li>{@link Result#FALSE} – forces the using state to <code>false</code></li>
 *                  <li>{@link Result#PASS} – ignores this callback and lets others or the default logic decide</li>
 *                </ul>
 */
Result IsAimingAtTarget(IsInteractingContext context);

record IsUsingContext(Minecraft minecraft, LivingEntity cameraEntity, TurningMode turningModeWhenUsingItem, boolean defaultUsInteracting)
{
}

Adding both to the same IPlayerStateCallback would be either misleading or confusing. What do you think?