# API documentation for developers

## Overview

This library provides an advanced hit detection system for Luanti mods. It allows you to define named bounding boxes on entities/players and detect which boxes were hit by an attack ray or volume.

## Core Functions

### Box Definition

#### `hitboxes_lib.register_hitboxes(group_name, hitbox_definitions)`

Register named hitboxes for an entity type.

**Parameters:**
- `group_name` (string): Name of the hitboxes group name (e.g., "mobs:zombie")
- `hitbox_definitions` (table): Table of named boxes with their definitions

**Box Representation:**

Boxes can be defined in two ways:

1. **Min/Max coordinates** (recommended for axis-aligned boxes):
   - Define `x_min`, `y_min`, `z_min`, `x_max`, `y_max`, `z_max`
   - Automatically converted to 8 corner points internally
   
2. **8 vertices** (for pre-rotated or custom shapes):
   - Define `vertices` table with 8 points `{x, y, z}`
   - Points define the 6 faces of the box

**Rotation:**
- When rotation is applied (via `get_rotated_boxes`), all 8 points of the box are rotated
- This allows boxes to follow entity orientation correctly

**Hitbox definition format:**
```lua
{
    box_name = {
        -- Option 1: Define box by min/max coordinates (automatically converted to 8 points)
        x_min = number, y_min = number, z_min = number,
        x_max = number, y_max = number, z_max = number,
        
        -- Option 2: Define box by 8 vertices (points defining 6 faces)
        -- vertices = {
        --     {x, y, z},  -- point 1
        --     {x, y, z},  -- point 2
        --     ...
        --     {x, y, z},  -- point 8
        -- },
        
        -- Optional: priority for hit ordering (higher = checked first)
        priority = number,
    },
    ...
}
```

**Example:**
```lua
hitboxes_lib.register_hitboxes("mobs:zombie", {
    -- Box defined by min/max (automatically converted to 8 points)
    head = {
        x_min = -0.25, y_min = 1.4, z_min = -0.25,
        x_max = 0.25, y_max = 1.9, z_max = 0.25,
        priority = 10  -- Check head first
    },
    torso = {
        x_min = -0.35, y_min = 0.6, z_min = -0.2,
        x_max = 0.35, y_max = 1.4, z_max = 0.2,
        priority = 5
    },
    legs = {
        x_min = -0.25, y_min = 0.0, z_min = -0.2,
        x_max = 0.25, y_max = 0.6, z_max = 0.2,
        priority = 1
    },
    -- Box defined by 8 vertices (for custom shapes)
    custom_box = {
        vertices = {
            {-0.2, 0.0, -0.2},
            { 0.2, 0.0, -0.2},
            { 0.2, 0.0,  0.2},
            {-0.2, 0.0,  0.2},
            {-0.2, 0.5, -0.2},
            { 0.2, 0.5, -0.2},
            { 0.2, 0.5,  0.2},
            {-0.2, 0.5,  0.2},
        }
    }
})
```

### Hit Detection

#### `hitboxes_lib.raycast_hit(hitboxes, hit_data, max_distance)`

Detect which hitboxes are intersected by a ray attack.

**Important:** All coordinates (hitboxes, ray origin) are relative to `hit_data.ref_pos` which is treated as origin (0,0,0). This ensures consistent precision across the entire world.

**Parameters:**
- `hitboxes` (table): Hitboxes table as returned from `get_transformed_boxes()`
- `hit_data` (table): Hit information containing:
  - `ref_pos` (vector): Reference position considered as point (0,0,0) - all calculations are relative to this
  - `hit_from_relpos` (vector): Position from where hit test originates, **relative to ref_pos**
  - `hit_from_dir` (normalized vector): Direction vector of hit test (normalized)
- `max_distance` (number): Maximum ray distance

**Returns:**
- Table of hit results, ordered from first to last hit box
- Each result contains: `{name = string, distance = number, position = vector, hit_relative = vector, hit_axis = string}`
  - `name`: Name of the hit box
  - `distance`: Distance from ray origin to hit point
  - `position`: Hit position in relative coordinates (relative to `hit_data.ref_pos`)
  - `hit_relative`: Normalized hit position within the hitbox as `{x, y, z}` where each component is in range 0..1
    - `0` represents the minimum value (x_min, y_min, z_min)
    - `1` represents the maximum value (x_max, y_max, z_max)
  - `hit_axis`: Which face was hit - `"x+"`, `"x-"`, `"y+"`, `"y-"`, `"z+"`, or `"z-"` (in local coordinates)
- Returns `nil` if no hits

**Example:**
```lua
local ref_pos = player:get_pos()
local hit_from_relpos = {x=0, y=player:get_properties().eye_height, z=0}  -- relative to ref_pos
local move = vector.subtract(target:get_pos(), ref_pos)
local hitboxes = hitboxes_lib.get_transformed_boxes("mobs:zombie", move, target:get_rotation())

local hits = hitboxes_lib.raycast_hit(hitboxes, {
    ref_pos = ref_pos,
    hit_from_relpos = hit_from_relpos,
    hit_from_dir = player:get_look_dir()
}, 5.0)

if hits then
    for _, hit in ipairs(hits) do
        print(string.format("Hit %s at distance %.2f on face %s", 
            hit.name, hit.distance, hit.hit_axis))
        -- Access relative hit position (0..1 range within hitbox)
        print(string.format("Hit at relative pos: x=%.2f, y=%.2f, z=%.2f", 
            hit.hit_relative.x, hit.hit_relative.y, hit.hit_relative.z))
    end
end
```

#### `hitboxes_lib.sphere_hit(hitboxes, hit_data)`

Detect which hitboxes are intersected by a spherical volume swept along the hit direction.

**Important:** The sphere center is `hit_data.hit_from_relpos`, which must be **relative to `hit_data.ref_pos`**. The function performs a ray-AABB test from the sphere center along the hit direction, then verifies sphere-AABB collision at the intersection point.

**Parameters:**
- `hitboxes` (table): Hitboxes table as returned from `get_transformed_boxes()`
- `hit_data` (table): Hit information containing:
  - `ref_pos` (vector): Reference position considered as point (0,0,0)
  - `hit_from_relpos` (vector): Position from where hit test originates (sphere center), **relative to ref_pos**
  - `hit_from_dir` (normalized vector): Direction vector of hit test
  - `radius` or `sphere_radius` (number, optional): Radius of the sphere. Defaults to 1.0
  - `range` (number, optional): Maximum range. Defaults to 10

**Returns:**
- Table of hit results sorted by distance
- Each result contains: `{name = string, distance = number, position = vector, hit_relative = vector, hit_axis = string, orig = table}`
- Returns `nil` if no hits

**Example:**
```lua
local ref_pos = player:get_pos()
local hit_from_relpos = {x=0, y=0, z=0}  -- Sphere center at ref_pos (relative coordinates)
local move = vector.subtract(target:get_pos(), ref_pos)
local hitboxes = hitboxes_lib.get_transformed_boxes("mobs:zombie", move, target:get_rotation())

local hits = hitboxes_lib.sphere_hit(hitboxes, {
    ref_pos = ref_pos,
    hit_from_relpos = hit_from_relpos,
    hit_from_dir = player:get_look_dir(),
    sphere_radius = 0.5,
    range = 5.0
})
```

#### `hitboxes_lib.box_hit(hitboxes, hit_data)`

Detect which hitboxes are intersected by an oriented box volume swept along the attack direction.

**Important:** The box is automatically transformed and positioned at `hit_from_relpos` (relative to `ref_pos`) using `box_rot` rotation. The function performs collision detection along the hit direction.

**Parameters:**
- `hitboxes` (table): Hitboxes table as returned from `get_transformed_boxes()`
- `hit_data` (table): Hit information containing:
  - `ref_pos` (vector): Reference position considered as point (0,0,0)
  - `hit_from_relpos` (vector): Position from where hit test originates (box is positioned here), **relative to ref_pos**
  - `hit_from_dir` (normalized vector): Direction vector of hit test
  - `box` (table): Box definition in local coordinates, either:
    - AABB format: `{x_min, y_min, z_min, x_max, y_max, z_max}`
    - 8 vertices format: array of 8 position vectors
  - `box_rot` (rotation table or number, optional): Rotation of box. Defaults to `{x=0, y=0, z=0}`
  - `range` (number, optional): Maximum range. Defaults to 10

**Returns:**
- Table of hit results sorted by distance
- Each result contains: `{name = string, distance = number, position = vector, hit_relative = vector, hit_axis = string, orig = table, vertices = table}`
- Returns `nil` if no hits

**Example:**
```lua
local ref_pos = player:get_pos()
local hit_from_relpos = {x=0, y=0, z=0}  -- Box positioned at ref_pos (relative coordinates)
local move = vector.subtract(target:get_pos(), ref_pos)
local hitboxes = hitboxes_lib.get_transformed_boxes("mobs:zombie", move, target:get_rotation())

-- Define box in local coordinates
local weapon_box = hitboxes_lib.collisionbox_to_box({-0.2, -0.2, -0.2, 0.2, 0.2, 0.2})

local hits = hitboxes_lib.box_hit(hitboxes, {
    ref_pos = ref_pos,
    hit_from_relpos = hit_from_relpos,
    hit_from_dir = player:get_look_dir(),
    box = weapon_box,
    box_rot = player:get_rotation(),
    range = 5.0
})
```
```

### Entity Integration

#### `hitboxes_lib.get_hitgroup_name(object)`

Get the registered hitgroup name for an object.

**Parameters:**
- `object` (ObjectRef): The object to query

**Returns:**
- `string`: The hitgroup name if hitboxes are registered for this object
- `nil`: If no hitboxes are registered

**Behavior:**
1. For lua entities: checks `luaent.hitgroup_name` or `luaent.name`
2. For players: returns `"player:default"` if registered
3. Only returns a name if hitboxes are actually registered for that group

**Example:**
```lua
local hitgroup = hitboxes_lib.get_hitgroup_name(target_obj)
if hitgroup then
    print("Target has hitboxes: " .. hitgroup)
else
    print("Target has no hitboxes")
end
```

#### `hitboxes_lib.detect_hits(hit_data)`

Detect which hitboxes are hit using pre-computed hit_data. This is a pure detection function that returns hit information without processing or calling callbacks.

**Parameters:**
- `hit_data` (table): Complete hit data structure containing:
  - `ref_pos` (vector, **required**): Reference position - all coordinates are relative to this point
  - `hit_from_relpos` (vector, **required**): Position from where hit test originates (e.g., player eye position, projectile position), **relative to ref_pos**
  - `hit_from_dir` (vector, **required**): Direction of hit test (normalized vector)
  - `hitgroup_name` (string, **required**): Name of registered hitbox group to test against
  - `hitbox_relpos` (vector, **required**): Position of hitboxes entity, **relative to ref_pos**
  - `hitbox_rot` (rotation table or yaw, **required**): Rotation of hitboxes for transformation
  - `mode` (string, optional): Detection mode - `"raycast"` (default), `"sphere"`, or `"box"`
  - `range` (number, optional): Maximum detection range (default: 4.0)
  
  **For sphere mode:**
  - `sphere_radius` (number, optional): Radius of sphere (default: 1.0). The sphere center is `hit_from_relpos` (relative coordinates).
  
  **For box mode:**
  - `box` (table, **required**): Box definition (AABB or 8 vertices) in local coordinates
  - `box_rot` (rotation table or yaw, optional): Rotation of box (default: {0,0,0}). The box is automatically transformed and positioned at `hit_from_relpos`.

**Returns:**
- `table`: Array of hit results sorted by distance, or `nil` if no hits
- Each hit contains: `{name = string, distance = number, position = vector, hit_relative = vector, hit_axis = string, orig = table}`

**Behavior:**
1. Validates all required fields in hit_data
2. Returns `nil` if hitgroup_name is not registered
3. Calculates relative positions for defender hitboxes
4. Performs hit detection based on specified mode
5. Returns sorted list of hits (closest first)
6. Does NOT process hits or modify damage - only detects and returns hit information

**Example (raycast mode):**
```lua
local ref_pos = player:get_pos()
local hits = hitboxes_lib.detect_hits({
    ref_pos = ref_pos,
    hit_from_relpos = {x=0, y=player:get_properties().eye_height, z=0},  -- relative to ref_pos
    hit_from_dir = player:get_look_dir(),
    hitgroup_name = "mobs:zombie",
    hitbox_relpos = vector.subtract(target:get_pos(), ref_pos),  -- relative to ref_pos
    hitbox_rot = target:get_rotation(),
    mode = "raycast",
    range = 5.0
})
if hits then
    for i, hit in ipairs(hits) do
        print(string.format("Hit %d: %s at distance %.2f", i, hit.name, hit.distance))
    end
end
```

**Example (sphere mode):**
```lua
local ref_pos = player:get_pos()
local hits = hitboxes_lib.detect_hits({
    ref_pos = ref_pos,
    hit_from_relpos = {x=0, y=0, z=0},  -- Sphere center at ref_pos (relative coordinates)
    hit_from_dir = player:get_look_dir(),
    hitgroup_name = "mobs:zombie",
    hitbox_relpos = vector.subtract(target:get_pos(), ref_pos),  -- relative to ref_pos
    hitbox_rot = target:get_rotation(),
    mode = "sphere",
    sphere_radius = 2.0,
    range = 8.0
})
```

**Example (box mode):**
```lua
-- Define box in local coordinates (e.g., sword swing in front of player)
local weapon_box = {x_min = 0.5, y_min = -0.2, z_min = -0.3, 
                    x_max = 1.5, y_max = 0.2, z_max = 0.3}

local ref_pos = player:get_pos()
local hits = hitboxes_lib.detect_hits({
    ref_pos = ref_pos,
    hit_from_relpos = {x=0, y=0, z=0},  -- Box positioned at ref_pos (relative coordinates)
    hit_from_dir = player:get_look_dir(),
    hitgroup_name = "mobs:zombie",
    hitbox_relpos = vector.subtract(target:get_pos(), ref_pos),  -- relative to ref_pos
    hitbox_rot = target:get_rotation(),
    mode = "box",
    box = weapon_box,  -- Box will be positioned at hit_from_relpos
    box_rot = player:get_rotation(),  -- Box will be rotated
    range = 3.0
})
```

#### `hitboxes_lib.detect_hits_from_objects(defender_obj, attacker_obj, direction)`

Convenience wrapper for `detect_hits()` that extracts data from ObjectRefs. Provided for backward compatibility and ease of use.

**Parameters:**
- `defender_obj` (ObjectRef): The object being attacked
- `attacker_obj` (ObjectRef): The object performing the attack
- `direction` (vector, optional): Attack direction. If not provided, calculated from positions

**Returns:**
- Same as `detect_hits()`: Array of hit results sorted by distance, or `nil` if no hits

**Behavior:**
1. Queries hitgroup for defender using `get_hitgroup_name()`
2. Returns `nil` if defender has no registered hitboxes
3. Extracts positions, rotations, and other data from objects
4. Automatically detects weapon type from attacker's wielded item or entity definition
5. Builds hit_data structure and calls `detect_hits()`

**Example:**
```lua
local hits = hitboxes_lib.detect_hits_from_objects(target, player, direction)
if hits then
    for i, hit in ipairs(hits) do
        print(string.format("Hit %d: %s at distance %.2f", i, hit.name, hit.distance))
    end
end
```

**Use Case:**
This is useful for integrating hitbox detection into existing mods without modifying their code directly. You can call this function after entities are registered by other mods.

### Utility Functions

#### `hitboxes_lib.collisionbox_to_box(collisionbox)`

Convert Luanti's collisionbox format to this mod's hitbox format.

**Purpose:**
Luanti entities use collisionbox in array format `{x1, y1, z1, x2, y2, z2}`, but this mod uses named table format `{x_min = number, y_min = number, ...}`. This function converts between the two formats and automatically determines which values are min and which are max, regardless of input order.

**Parameters:**
- `collisionbox` (array): Collisionbox in Luanti format - array of 6 numbers `{x1, y1, z1, x2, y2, z2}`
  - Values can be in any order (min/max is determined automatically)

**Returns:**
- Table in hitbox format: `{x_min = number, y_min = number, z_min = number, x_max = number, y_max = number, z_max = number}`
- Guarantees x_min ≤ x_max, y_min ≤ y_max, z_min ≤ z_max

**Example:**
```lua
-- Get entity's collisionbox (values may not be in min/max order)
local entity = minetest.registered_entities["mobs:zombie"]
local collision = entity.collisionbox  -- e.g., {-0.3, 0.0, -0.3, 0.3, 1.8, 0.3}

-- Convert to hitbox format (automatically determines min/max)
local box = hitboxes_lib.collisionbox_to_box(collision)
-- Result: {x_min=-0.3, y_min=0.0, z_min=-0.3, x_max=0.3, y_max=1.8, z_max=0.3}

-- Works even if values are "backwards"
local weird_box = hitboxes_lib.collisionbox_to_box({0.5, 1.0, 0.3, -0.5, 0.0, -0.3})
-- Result: {x_min=-0.5, y_min=0.0, z_min=-0.3, x_max=0.5, y_max=1.0, z_max=0.3}

-- Now you can use it to register hitboxes
hitboxes_lib.register_hitboxes("mobs:zombie", {
    full_body = {
        box = box,
        priority = 5
    }
})

-- Or use it directly with transform_attack_box
local weapon_box = hitboxes_lib.collisionbox_to_box({0.5, -0.2, -0.3, 1.5, 0.2, 0.3})
local attack_box_rel = hitboxes_lib.transform_attack_box(
    weapon_box,
    player:get_pos(),
    player:get_rotation(),
    player:get_pos()
)
```

#### `hitboxes_lib.transform_attack_box(attacker_box, attacker_pos, attacker_rot, ref_pos)`

Transform an attack box from attacker's local coordinates to relative coordinates for use with `box_hit`.

**Purpose:**
When using `box_hit`, the attack box must be in relative coordinates (relative to `ref_pos`). This function takes an attacker's local hitbox and transforms it by:
1. Rotating it by the attacker's rotation (yaw)
2. Translating it by the attacker's position relative to the reference position

**Parameters:**
- `attacker_box` (table): Attack box in attacker's local coordinates, either:
  - AABB format: `{x_min = number, y_min = number, z_min = number, x_max = number, y_max = number, z_max = number}`
  - 8 vertices format: array of 8 position vectors `{{x, y, z}, ...}`
- `attacker_pos` (vector): Attacker's absolute position in world coordinates `{x, y, z}`
- `attacker_rot` (vector or number): Rotation vector `{x, y, z}` (only y/yaw is used) or a single number for yaw in radians
- `ref_pos` (vector): Reference position (typically same as `hit_data.pos`) `{x, y, z}`

**Returns:**
- AABB table in relative coordinates: `{x_min, y_min, z_min, x_max, y_max, z_max}`
- Ready to be used with `box_hit` function

**Example:**
```lua
-- Define attacker's weapon hitbox in local coordinates (e.g., sword swing)
local weapon_box = {
    x_min = 0.5, y_min = -0.2, z_min = -0.3,
    x_max = 1.5, y_max = 0.2, z_max = 0.3
}

-- Transform to relative coordinates
local attacker_pos = player:get_pos()
local attacker_rot = player:get_rotation()
local ref_pos = attacker_pos  -- Use attacker pos as reference

local attack_box_relative = hitboxes_lib.transform_attack_box(
    weapon_box,
    attacker_pos,
    attacker_rot,
    ref_pos
)

-- Get target hitboxes
local target_pos = target:get_pos()
local move = vector.subtract(target_pos, ref_pos)
local hitboxes = hitboxes_lib.get_transformed_boxes("mobs:zombie", move, target:get_rotation())

-- Perform box hit detection
local hits = hitboxes_lib.box_hit(hitboxes, {
    ref_pos = ref_pos,
    hit_from_pos = attacker_pos,
    hit_from_dir = player:get_look_dir(),
    box = attack_box_local,
    box_rot = attacker_rot,
    range = 5.0
})
```

#### `hitboxes_lib.get_transformed_boxes(group_name, pos, rot)`

Get all hitboxes for a group, transformed (rotated and positioned) to world coordinates.

**Parameters:**
- `group_name` (string): Name of the hitbox group
- `pos` (vector): Position vector {x, y, z}
- `rot` (vector or number): Rotation vector {x, y, z} (only y/yaw is used) or a single number for yaw in radians

**Returns:**
- Table of transformed boxes: `{box_name = {vertices = {...}, aabb = {...}, priority = number}, ...}`
- Each box contains 8 rotated and positioned points in world coordinates
- Returns empty table `{}` if group has no registered hitboxes

**Note:** Rotation is applied to all 8 corner points of each box around the Y axis (yaw), then translated to the given position.

**Example:**
```lua
-- Using rotation vector
local hitboxes = hitboxes_lib.get_transformed_boxes("mobs:zombie", 
    target:get_pos(), 
    target:get_rotation())

-- Using yaw directly
local hitboxes = hitboxes_lib.get_transformed_boxes("mobs:zombie",
    {x=10, y=5, z=10},
    math.pi/2)  -- 90 degrees
```

#### `hitboxes_lib.visualize_object_hitboxes(group_name, object, duration, attach)`

Show hitbox wireframes for debugging on a specific object.

**Parameters:**
- `group_name` (string): Name of the hitbox group to visualize
- `object` (ObjectRef): The entity or player to visualize hitboxes on
- `duration` (number, optional): How long to show boxes in seconds (default: 5)
- `attach` (boolean, optional): If `true` (default), hitboxes are attached to the object and follow it. If `false`, hitboxes are created at the current position as a static snapshot.

**Modes:**
- **Attached mode** (`attach=true`): Visualization entities are attached to the object using local coordinates (multiplied by 10 for Luanti's attachment system). They follow the object's movement and rotation automatically.
- **Static mode** (`attach=false`): Visualization entities are created at the current world position and rotation. They remain fixed in space even if the object moves.

**Example:**
```lua
-- Visualize zombie hitboxes attached to entity (follows movement)
hitboxes_lib.visualize_object_hitboxes("mobs:zombie", zombie_obj, 10, true)

-- Visualize player hitboxes as static snapshot (doesn't follow)
hitboxes_lib.visualize_object_hitboxes("player:default", player, 5, false)

-- Default behavior (attached)
hitboxes_lib.visualize_object_hitboxes("mobs:zombie", zombie_obj, 10)
```
