# Enhanced Input System

## Architecture

Enhanced Input replaces the legacy input system. Three core asset types work together:

1. **InputAction (IA_)** — defines a single input intent (move, jump, fire). Each IA has a ValueType that determines the data shape.
2. **InputMappingContext (IMC_)** — maps hardware keys to InputActions. Multiple IMCs can be active with priority ordering.
3. **Triggers & Modifiers** — attached per-mapping to control when actions fire and how raw input is transformed.

Data flow: Hardware Key → IMC mapping → Modifiers (transform value) → Triggers (gate firing) → InputAction → C++ callback.

## Value Types

Each InputAction has a ValueType that determines the data passed to callbacks:

- **Boolean** — press/release (jump, fire, interact). Default if not set.
- **Axis1D** — single scalar (throttle, zoom)
- **Axis2D** — 2D vector (movement WASD, mouse look)
- **Axis3D** — 3D vector (rare, motion controllers)

IMPORTANT: Always set ValueType when creating an IA. Boolean is the default — forgetting to change it for movement/look causes silent zero-vector input.

## Trigger Types

Triggers control WHEN an InputAction fires:

- **Pressed** — fires once on key down
- **Released** — fires once on key up
- **Down** — fires every tick while held
- **Hold** — fires after held for a duration
- **Hold and Release** — fires on release only if held long enough
- **Tap** — fires on release only if pressed briefly
- **Chorded** — requires another InputAction to be active simultaneously
- **Pulse** — repeating fire at interval while held

Use `query_ue_api('InputTrigger')` and `query_ue_api('ETriggerEvent')` to discover trigger classes and enums.

## Creating Input Assets via Python

CRITICAL — Factory class paths (models get these wrong constantly):
- InputAction: `/Script/InputEditor.InputAction_Factory` (NOT EnhancedInput, NOT InputActionFactory)
- InputMappingContext: `/Script/InputEditor.InputMappingContext_Factory` (NOT EnhancedInput)

Both factories are in the `InputEditor` module (editor-only part of EnhancedInput plugin).

CRITICAL — Asset creation ORDER matters:
1. Create ALL InputActions FIRST and save them
2. THEN create InputMappingContext
3. THEN add key mappings via `map_key()`
If you load an InputAction that doesn't exist yet, UE returns None and the mapping silently fails.

```python
import unreal
helpers = unreal.UNDBlueprintHelpers

# ── Step 1: Create InputActions FIRST ──────────────────────────────
ia_move = helpers.create_asset_with_factory(
    '/Game/Input/IA_Move',
    '/Script/InputEditor.InputAction_Factory',
    '{}'
)
ia_move.set_editor_property('value_type', unreal.InputActionValueType.AXIS2D)

ia_look = helpers.create_asset_with_factory(
    '/Game/Input/IA_Look',
    '/Script/InputEditor.InputAction_Factory',
    '{}'
)
ia_look.set_editor_property('value_type', unreal.InputActionValueType.AXIS2D)

ia_jump = helpers.create_asset_with_factory(
    '/Game/Input/IA_Jump',
    '/Script/InputEditor.InputAction_Factory',
    '{}'
)
# Jump uses Boolean (default) — no need to set value_type

# Save all actions before using them in IMC
unreal.EditorAssetLibrary.save_loaded_asset(ia_move)
unreal.EditorAssetLibrary.save_loaded_asset(ia_look)
unreal.EditorAssetLibrary.save_loaded_asset(ia_jump)

# ── Step 2: Create InputMappingContext ─────────────────────────────
imc = helpers.create_asset_with_factory(
    '/Game/Input/IMC_Default',
    '/Script/InputEditor.InputMappingContext_Factory',
    '{}'
)
```

IMPORTANT: The Python enum name is `unreal.InputActionValueType` (WITHOUT `E` prefix).
Do NOT use a string like `'Axis2D'` — it will fail with "Cannot nativize 'str'".
```python
# CORRECT:
ia.set_editor_property('value_type', unreal.InputActionValueType.AXIS2D)
# WRONG — will error:
ia.set_editor_property('value_type', 'Axis2D')
```

## Adding Key Mappings to IMC (UE5.7 API)

CRITICAL: The `mappings` property is DEPRECATED in UE5.7. Use `add_imc_mapping()` (preferred) or `map_key()`.
DO NOT use `imc.get_editor_property('mappings')` — it will NOT work correctly.

### add_imc_mapping() — Simplified Key Mapping (PREFERRED)

`helpers.add_imc_mapping(imc, input_action, key_name_string)` — maps an InputAction to a key by name. No need to construct `unreal.Key()`.

```python
import unreal
helpers = unreal.UNDBlueprintHelpers

# Simple — just pass the key name as a string
helpers.add_imc_mapping(imc, ia_jump, 'SpaceBar')
helpers.add_imc_mapping(imc, ia_move, 'W')
helpers.add_imc_mapping(imc, ia_move, 'A')
helpers.add_imc_mapping(imc, ia_move, 'S')
helpers.add_imc_mapping(imc, ia_move, 'D')
helpers.add_imc_mapping(imc, ia_look, 'Mouse2D')
helpers.add_imc_mapping(imc, ia_move, 'Gamepad_LeftStick2D')
helpers.add_imc_mapping(imc, ia_look, 'Gamepad_RightStick2D')
helpers.add_imc_mapping(imc, ia_interact, 'E')

unreal.EditorAssetLibrary.save_loaded_asset(imc)
```

Common key names: `W`, `A`, `S`, `D`, `SpaceBar`, `LeftShift`, `LeftMouseButton`, `RightMouseButton`, `MouseX`, `MouseY`, `Mouse2D`, `E`, `F`, `Q`, `R`, `Tab`, `Escape`, `Gamepad_LeftX`, `Gamepad_LeftY`, `Gamepad_LeftStick2D`, `Gamepad_RightStick2D`.

NOTE: `add_imc_mapping` creates the mapping but does NOT add modifiers (Negate, Swizzle). For simple boolean actions (jump, interact, fire) and gamepad sticks this is sufficient. For WASD Axis2D movement with modifiers, use the `map_key()` method below.

### map_key() Method — Full Control with Modifiers

`imc.map_key(action, key)` — adds a key mapping and returns the `EnhancedActionKeyMapping` struct. Use this when you need to attach modifiers (Negate, SwizzleAxis, DeadZone, etc.).

```python
# ── Step 3: Add key mappings ──────────────────────────────────────

# Simple boolean action — just map the key
imc.map_key(ia_jump, unreal.Key('SpaceBar'))

# ── WASD Movement (Axis2D) — requires modifiers ──────────────────
# W = Forward (+Y axis)
mapping_w = imc.map_key(ia_move, unreal.Key('W'))
swizzle_w = unreal.InputModifierSwizzleAxis()
swizzle_w.set_editor_property('order', unreal.EInputAxisSwizzle.YXZ)
mapping_w.set_editor_property('modifiers', [swizzle_w])

# S = Backward (-Y axis) — Negate + SwizzleAxis
mapping_s = imc.map_key(ia_move, unreal.Key('S'))
negate_s = unreal.InputModifierNegate()
swizzle_s = unreal.InputModifierSwizzleAxis()
swizzle_s.set_editor_property('order', unreal.EInputAxisSwizzle.YXZ)
mapping_s.set_editor_property('modifiers', [negate_s, swizzle_s])

# A = Left (-X axis) — Negate only
mapping_a = imc.map_key(ia_move, unreal.Key('A'))
negate_a = unreal.InputModifierNegate()
mapping_a.set_editor_property('modifiers', [negate_a])

# D = Right (+X axis) — no modifiers needed (raw X = +1)
imc.map_key(ia_move, unreal.Key('D'))

# ── Mouse Look (Axis2D) ──────────────────────────────────────────
# Mouse2D produces (DeltaX, DeltaY) — maps directly to Axis2D
mapping_look = imc.map_key(ia_look, unreal.Key('Mouse2D'))
negate_look = unreal.InputModifierNegate()
# Negate Y so look-up is positive pitch
negate_look.set_editor_property('y', True)
negate_look.set_editor_property('x', False)
negate_look.set_editor_property('z', False)
mapping_look.set_editor_property('modifiers', [negate_look])

# ── Gamepad sticks ────────────────────────────────────────────────
imc.map_key(ia_move, unreal.Key('Gamepad_LeftStick2D'))
mapping_look_gp = imc.map_key(ia_look, unreal.Key('Gamepad_RightStick2D'))

# Save the completed IMC
unreal.EditorAssetLibrary.save_loaded_asset(imc)
```

### Modifier Reference

Modifiers transform raw input values BEFORE triggers evaluate:

| Modifier | Purpose | Key Properties |
|----------|---------|----------------|
| `InputModifierNegate` | Flip sign of axes | `x`, `y`, `z` (bool, default all True) |
| `InputModifierSwizzleAxis` | Reorder axes | `order` (EInputAxisSwizzle, e.g. YXZ) |
| `InputModifierDeadZone` | Ignore small values | `lower_threshold`, `upper_threshold`, `type` |
| `InputModifierScalar` | Multiply value | `scalar` (FVector) |
| `InputModifierSmooth` | Smooth over time | — |

WASD modifier logic explained:
- Keys produce 1D Boolean (0 or 1). MoveAction expects Axis2D (X, Y).
- W/S need SwizzleAxis(YXZ) to route the 1D value into the Y axis (forward/back).
- A/D already route into X axis naturally (1D → X component of Axis2D).
- S/A also need Negate to make the value negative (backward/left).

### Other IMC Methods

```python
imc.unmap_key(ia_move, unreal.Key('W'))        # Remove specific mapping
imc.unmap_all_keys_from_action(ia_move)         # Remove all mappings for an action
imc.unmap_all()                                  # Clear entire IMC
mappings = imc.get_mappings()                    # Get all current mappings (read-only)
```

### Inspecting IMC via UniversalBridge

For READING mapping data, UniversalBridge can serialize the entire IMC structure to JSON:
```python
bridge = unreal.UNDUniversalBridge
# Read all mappings as JSON (useful for debugging/inspection)
json_str = bridge.get_property_as_json(imc, 'DefaultKeyMappings')
print(json_str)
```

NOTE: For WRITING key mappings, always use `imc.map_key()` — not Bridge. The Bridge could write to `DefaultKeyMappings.Mappings` via `set_property_from_json()`, but `map_key()` is the native UFUNCTION that handles initialization correctly. Bridge is better suited for systems without direct UFUNCTION APIs (StateTree, ControlRig, etc.).

## Blueprint-Only Binding (No C++ Required)

When the character BP has NO C++ parent with input properties, you must wire Enhanced Input entirely in Blueprints:

### Step 1: Add IMC and IA variables to Character BP
```python
import unreal
helpers = unreal.UNDBlueprintHelpers
lib = unreal.BlueprintEditorLibrary

# Load previously created input assets
ia_move = unreal.load_asset('/Game/Input/IA_Move')
imc = unreal.load_asset('/Game/Input/IMC_Default')

# Add object reference variables pointing to the input assets
# These create UPROPERTY variables on the BP that can be set in CDO
lib.add_member_variable(bp, 'DefaultMappingContext', helpers.get_object_pin_type(imc.get_class()))
lib.add_member_variable(bp, 'MoveAction', helpers.get_object_pin_type(ia_move.get_class()))

# Compile so variables exist
unreal.BlueprintEditorLibrary.compile_blueprint(bp)

# Set CDO defaults
cdo = bp.generated_class().get_default_object()
cdo.set_editor_property('DefaultMappingContext', imc)
cdo.set_editor_property('MoveAction', ia_move)
```

### Step 2: BeginPlay graph — Add Mapping Context
In the character's BeginPlay, wire: GetController → CastToPlayerController → GetLocalPlayer → GetSubsystem(EnhancedInputLocalPlayerSubsystem) → AddMappingContext.

Use `build_graph_from_spec` for this — the key functions are:
- `GetController` (on Actor)
- `Conv_ObjectToInterface` or `CastTo` for PlayerController
- `GetLocalPlayer` (on PlayerController)
- `GetSubsystem` (on LocalPlayer subsystem class)
- `AddMappingContext` (on EnhancedInputLocalPlayerSubsystem)

### Step 3: SetupPlayerInputComponent — Bind actions
Override `SetupPlayerInputComponent`, use `GetEnhancedInputComponent` or Cast InputComponent to EnhancedInputComponent, then `BindAction`.

**IMPORTANT**: This Blueprint-only approach is more complex. If you have control over the parent C++ class, prefer adding the input binding in C++ (see below). Blueprint-only is needed when using engine base classes (Character, Pawn) directly as parent.

## C++ Binding (Stable API)

### Header — Declare input properties and callbacks
```cpp
// In YourCharacter.h
#include "InputActionValue.h"

UPROPERTY(EditDefaultsOnly, Category = "Input")
UInputAction* MoveAction;

UPROPERTY(EditDefaultsOnly, Category = "Input")
UInputAction* LookAction;

UPROPERTY(EditDefaultsOnly, Category = "Input")
UInputMappingContext* DefaultMappingContext;

void Move(const FInputActionValue& Value);
void Look(const FInputActionValue& Value);
```

### BeginPlay — Register mapping context
```cpp
// In YourCharacter.cpp
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"

void AYourCharacter::BeginPlay()
{
    Super::BeginPlay();
    if (APlayerController* PC = Cast<APlayerController>(Controller))
    {
        if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
            ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PC->GetLocalPlayer()))
        {
            Subsystem->AddMappingContext(DefaultMappingContext, 0);
        }
    }
}
```

### SetupPlayerInputComponent — Bind actions to callbacks
```cpp
void AYourCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
    if (UEnhancedInputComponent* EIC = Cast<UEnhancedInputComponent>(PlayerInputComponent))
    {
        EIC->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AYourCharacter::Move);
        EIC->BindAction(LookAction, ETriggerEvent::Triggered, this, &AYourCharacter::Look);
    }
}

void AYourCharacter::Move(const FInputActionValue& Value)
{
    FVector2D Axis = Value.Get<FVector2D>();
    AddMovementInput(GetActorForwardVector(), Axis.Y);
    AddMovementInput(GetActorRightVector(), Axis.X);
}
```

### ETriggerEvent choices for BindAction
- `ETriggerEvent::Triggered` — continuous input (movement, look). Fires every frame while active.
- `ETriggerEvent::Started` — one-shot actions (jump, interact). Fires once on activation.
- `ETriggerEvent::Completed` — fires when action ends (key released after trigger condition met).

## Build.cs Dependency

```csharp
PublicDependencyModuleNames.Add("EnhancedInput");
```

## Naming Conventions

- **IA_** prefix for Input Actions: IA_Move, IA_Jump, IA_Fire, IA_Interact, IA_Look
- **IMC_** prefix for Input Mapping Contexts: IMC_Default, IMC_Vehicle, IMC_Swimming
- Store input assets in `/Game/Input/` directory

## CRITICAL RULES

- ALWAYS add "EnhancedInput" to Build.cs PublicDependencyModuleNames
- ALWAYS add mapping context in BeginPlay via UEnhancedInputLocalPlayerSubsystem — without this, no input is processed
- ALWAYS call Super::SetupPlayerInputComponent before binding actions
- ALWAYS set ValueType on IA_ creation — Boolean default causes silent failures for movement/look
- ALWAYS create and SAVE InputActions BEFORE creating IMC and adding mappings — loading a non-existent asset returns None
- ALWAYS use `imc.map_key(action, key)` to add key mappings — the `mappings` property is DEPRECATED in UE5.7
- ALWAYS add SwizzleAxis(YXZ) modifier on W/S keys for Axis2D movement — without it, forward/back input goes to X instead of Y
- Use ETriggerEvent::Triggered for continuous input (move/look), Started for one-shot (jump)
- Include "EnhancedInputComponent.h" and "EnhancedInputSubsystems.h" in .cpp files
- Multiple IMCs can be active simultaneously — use priority parameter in AddMappingContext (higher = takes precedence)
- Use RemoveMappingContext when switching input modes (e.g., entering vehicle)
- NEVER use `imc.get_editor_property('mappings')` or `set_editor_property('mappings', ...)` — deprecated, will cause errors
