# How to Implement Custom Unit Actions in Unciv: A Complete Developer Guide

> Learn how to implement custom unit actions in Unciv. Follow our developer guide to define new actions, create builder functions, and register them for your mod.

- Repository: [Yair Morgenstern/Unciv](https://github.com/yairm210/Unciv)
- Tags: how-to-guide
- Published: 2026-06-18

---

**To implement custom unit actions in Unciv, define a new `UnitActionType` enum entry in [`UnitAction.kt`](https://github.com/yairm210/Unciv/blob/main/UnitAction.kt), create a builder function that returns a `Sequence<UnitAction>`, and register it in the `actionTypeToFunctions` map within [`UnitActions.kt`](https://github.com/yairm210/Unciv/blob/main/UnitActions.kt).**

Unciv is an open-source, Android-and-desktop reimplementation of Civilization V. Extending its unit action system allows modders and developers to add new buttons to the unit control panel without modifying core UI logic. The architecture follows a clear three-layer pattern separating static definitions, runtime instances, and factory assembly.

## Understanding the Unit Action Architecture

Unciv’s unit action system consists of three core components that work together to render buttons and handle clicks.

**`UnitActionType`** is an enum defined in [`core/src/com/unciv/models/UnitAction.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/models/UnitAction.kt) that acts as a static catalogue. It stores the default label, icon provider, keyboard binding, and default page placement for each action type.

**`UnitAction`** is a data class in the same file that represents a concrete instance for a specific unit. It carries the runtime title, enabled state, and the lambda that executes when the player presses the button.

**`UnitActions`** is an object in [`core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActions.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActions.kt) that serves as the factory. It queries the game state, delegates to builder functions, and returns a `Sequence<UnitAction>` that the UI consumes.

## Step-by-Step Implementation Guide

Follow these steps to add a custom action. The examples below demonstrate creating an "Inspect Unit" action that displays a popup with unit statistics.

### Define the Action Type

Add a new entry to the `UnitActionType` enum in [`core/src/com/unciv/models/UnitAction.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/models/UnitAction.kt).

```kotlin
InspectUnit(
    "Inspect Unit",
    { ImageGetter.getUnitActionPortrait("Info") },
    isSkippingToNextUnit = false,
    defaultPage = 0
)

```

The constructor parameters specify the translation key, an optional icon provider, whether the game should auto-skip to the next unit after activation, and the default page index for the paging UI.

### Create the Action Builder

Implement a builder function that returns a `Sequence<UnitAction>`. Place this in [`core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt) or a new file.

```kotlin
internal fun getInspectUnitActions(unit: MapUnit, tile: Tile) = sequence {
    val title = "Inspect ${unit.baseUnit.name}"
    yield(
        UnitAction(
            type = UnitActionType.InspectUnit,
            useFrequency = 120f,
            title = title,
            action = {
                ConfirmPopup(
                    GUI.getWorldScreen(),
                    "HP: ${unit.health}\nMoves: ${unit.currentMovement}/${unit.getMaxMovement()}",
                    "Close"
                ).open()
            }
        )
    )
}

```

The `useFrequency` float determines sorting order; higher values appear earlier in the list. The `action` lambda receives execution context when the button is clicked.

### Register the Builder

Link the builder to the enum constant in [`core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActions.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActions.kt) inside the `actionTypeToFunctions` linked map.

```kotlin
private val actionTypeToFunctions = linkedMapOf<UnitActionType, (unit: MapUnit, tile: Tile) -> Sequence<UnitAction>>(
    // ... existing mappings ...
    UnitActionType.InspectUnit to UnitActionsFromUniques::getInspectUnitActions,
    // ... end of map ...
)

```

Once registered, `UnitActions.getUnitActions()` automatically includes your custom button when enumerating available actions for a selected unit.

### Configure Page Placement (Optional)

To dynamically control which page the button appears on based on unit state, add an entry to `actionTypeToPageGetter` in [`UnitActions.kt`](https://github.com/yairm210/Unciv/blob/main/UnitActions.kt).

```kotlin
UnitActionType.InspectUnit to { unit ->
    if (unit.isFortified()) 1 else 0
}

```

This example displays the button on page 2 when the unit is fortified, and page 1 otherwise.

### Write Unit Tests

Verify the integration in [`tests/src/com/unciv/uniques/UnitUniquesTests.kt`](https://github.com/yairm210/Unciv/blob/main/tests/src/com/unciv/uniques/UnitUniquesTests.kt).

```kotlin
@Test
fun `Inspect Unit action is present`() {
    val unit = TestUtils.createUnit("Warrior", civInfo)
    val actions = UnitActions.getUnitActions(unit, UnitActionType.InspectUnit)
    assertTrue(actions.any { it.type == UnitActionType.InspectUnit })
}

```

Run `./gradlew test` to ensure the new functionality does not break existing unit action logic.

## Complete Code Example

Here is the full implementation showing all three files modified:

```kotlin
// core/src/com/unciv/models/UnitAction.kt
enum class UnitActionType(
    val label: String,
    val imageGetter: () -> Image?,
    val isSkippingToNextUnit: Boolean = false,
    val defaultPage: Int = 0
) {
    // ... existing entries ...
    
    InspectUnit(
        "Inspect Unit",
        { ImageGetter.getUnitActionPortrait("Info") },
        isSkippingToNextUnit = false,
        defaultPage = 0
    );
}

```

```kotlin
// core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt
internal fun getInspectUnitActions(unit: MapUnit, tile: Tile) = sequence {
    yield(
        UnitAction(
            type = UnitActionType.InspectUnit,
            useFrequency = 120f,
            title = "Inspect ${unit.baseUnit.name}",
            action = {
                ConfirmPopup(
                    GUI.getWorldScreen(),
                    "HP: ${unit.health}\nMoves: ${unit.currentMovement}/${unit.getMaxMovement()}",
                    "Close"
                ).open()
            }
        )
    )
}

```

```kotlin
// core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActions.kt
private val actionTypeToFunctions = linkedMapOf<UnitActionType, (unit: MapUnit, tile: Tile) -> Sequence<UnitAction>>(
    // ... existing mappings ...
    UnitActionType.InspectUnit to UnitActionsFromUniques::getInspectUnitActions
)

```

With these changes, the game displays an **"Inspect Unit"** button in the unit actions table whenever a unit is selected.

## Summary

- **Define** a static `UnitActionType` entry with label, icon, and page metadata.
- **Build** a function returning `Sequence<UnitAction>` containing the runtime logic and click handler.
- **Register** the builder in `UnitActions.actionTypeToFunctions` to enable discovery.
- **Customize** page placement via `actionTypeToPageGetter` if dynamic positioning is required.
- **Test** using the existing test suite in [`UnitUniquesTests.kt`](https://github.com/yairm210/Unciv/blob/main/UnitUniquesTests.kt) to maintain stability.

## Frequently Asked Questions

### What is the difference between UnitActionType and UnitAction?

**`UnitActionType`** is the enum constant representing the static definition of an action type, including its default label and icon. **`UnitAction`** is the runtime instance created for a specific unit, containing the actual title, enabled state, and the executable lambda that runs when the player clicks the button.

### Where should I place custom action builder functions?

Place builder functions in [`core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt) alongside existing implementations like `getParadropActions()` and `getRepairActions()`. Alternatively, create a new file in the same package and import it into [`UnitActions.kt`](https://github.com/yairm210/Unciv/blob/main/UnitActions.kt) for registration.

### How do I control which page my custom action appears on?

Set the `defaultPage` parameter in the `UnitActionType` constructor for static placement. For dynamic placement based on game state, add a lambda to `actionTypeToPageGetter` in [`UnitActions.kt`](https://github.com/yairm210/Unciv/blob/main/UnitActions.kt) that returns `0` for page one, `1` for page two, and so on.

### How do I test custom unit actions in Unciv?

Create unit tests in [`tests/src/com/unciv/uniques/UnitUniquesTests.kt`](https://github.com/yairm210/Unciv/blob/main/tests/src/com/unciv/uniques/UnitUniquesTests.kt). Instantiate a test unit using `TestUtils.createUnit()`, call `UnitActions.getUnitActions()`, and assert that your custom action type appears in the returned sequence. Run `./gradlew test` to validate against the full test suite.