Cycle

The Scriptorium

Smart Assembly code templates and tools for on-chain development in Eve Frontier.

Turret Targeting Modes
IntermediateChapter 4 of 420 min read

Custom Targeting Modes

Now that you understand the three built-in targeting modes, you can create your own. This chapter covers the patterns, techniques, and limitations for building custom turret targeting logic.

The Template

Every custom targeting module follows the same skeleton:

move
module my_turret::my_custom_mode;

use sui::bcs;
use world::character::Character;
use world::turret::{Self, Turret, OnlineReceipt};

/// Unique auth witness for this mode
public struct MyCustomAuth has drop {}

public fun get_target_priority_list(
    turret: &Turret,
    owner_character: &Character,
    target_candidate_list: vector<u8>,
    receipt: OnlineReceipt,
): vector<u8> {
    // 1. Verify receipt
    assert!(receipt.turret_id() == object::id(turret), 0);

    // 2. Unpack candidates
    let candidates = turret::unpack_candidate_list(target_candidate_list);

    // 3. Build return list with your custom logic
    let mut return_list = vector::empty<turret::ReturnTargetPriorityList>();

    // ... your targeting logic here ...

    // 4. Encode, destroy receipt, return
    let result = bcs::to_bytes(&return_list);
    turret::destroy_online_receipt(receipt, MyCustomAuth {});
    result
}

Pattern 1: Priority-Based Targeting

Instead of a flat +10000 boost, you can calculate priority weights dynamically:

move
public fun get_target_priority_list(
    turret: &Turret,
    owner_character: &Character,
    target_candidate_list: vector<u8>,
    receipt: OnlineReceipt,
): vector<u8> {
    assert!(receipt.turret_id() == object::id(turret), 0);

    let candidates = turret::unpack_candidate_list(target_candidate_list);
    let owner_tribe = owner_character.tribe();
    let owner_char_id = owner_character.key().item_id();

    let mut return_list = vector::empty<turret::ReturnTargetPriorityList>();
    let mut i: u64 = 0;

    while (i < candidates.length()) {
        let candidate = candidates.borrow(i);
        let is_owner = (candidate.character_id() as u64) == owner_char_id;
        let is_same_tribe = candidate.character_tribe() == owner_tribe;

        if (!is_owner && !is_same_tribe) {
            // Tiered priority system
            let mut weight = candidate.priority_weight();

            // Tier 1: Active aggressors (highest priority)
            if (candidate.is_aggressor()) {
                weight = weight + 20000;
            };

            // Tier 2: Known hostile tribes (medium priority)
            // Example: tribes 10, 15, 22 are known enemies
            let target_tribe = candidate.character_tribe();
            if (target_tribe == 10 || target_tribe == 15 || target_tribe == 22) {
                weight = weight + 5000;
            };

            return_list.push_back(turret::new_return_target_priority_list(
                candidate.item_id(), weight,
            ));
        };
        i = i + 1;
    };

    let result = bcs::to_bytes(&return_list);
    turret::destroy_online_receipt(receipt, MyCustomAuth {});
    result
}

This creates three priority tiers:

  • Aggressors from hostile tribes: base + 25000 (20000 + 5000)
  • Non-hostile targets from hostile tribes: base + 5000
  • Aggressors from neutral tribes: base + 20000
  • Other non-tribe targets: base
  • Pattern 2: Whitelist/Blacklist by Tribe

    You can create an alliance system by whitelisting specific tribes:

    move
    fun is_allied_tribe(tribe_id: u32): bool {
        // Hardcoded alliance list
        tribe_id == 3 || tribe_id == 7 || tribe_id == 12
    }
    
    // In your targeting loop:
    let is_ally = is_same_tribe || is_allied_tribe(candidate.character_tribe());
    let excluded = is_owner || is_ally;

    This protects members of allied tribes just like your own tribe. The limitation is that the alliance list is hardcoded at compile time -- you need to publish a new package version to change it.

    Pattern 3: Aggressor-Only with No Tribe Protection

    This is the "No Protect" mode from the turret_monitor package. It shoots all aggressors including tribe members:

    move
    // Shoot ALL aggressors. Only exclude the owner.
    let excluded = is_owner || !is_aggressor;

    Useful in scenarios where you cannot trust all tribe members, or when tribal affiliation should not grant immunity.

    Pattern 4: Conditional Mode Switching

    Since you cannot have dynamic configuration objects (the game controls the PTB), you might want to publish multiple modes and let the turret owner switch between them by re-authorizing:

    move
    // Package contains multiple modules, each with its own auth type:
    // my_turret::defensive     → DefensiveAuth
    // my_turret::aggressive    → AggressiveAuth
    // my_turret::lockdown      → LockdownAuth

    The turret owner authorizes one mode at a time. To switch modes, they:

  • Revoke the current extension: revoke_extension(turret, cap)
  • Authorize the new one: authorize_extension(turret, cap)
  • This is the primary mechanism for runtime mode switching.

    Limitations

    No Dynamic Configuration Object

    Unlike the tribe storage extension, turret targeting functions cannot take extra object parameters. The game engine calls get_target_priority_list with exactly four arguments -- you cannot add a fifth parameter for a config object.

    This means:

    • You cannot read from a shared config object at runtime.
    • Alliance lists, priority weights, and other settings must be hardcoded.
    • To change configuration, you must publish a new package version.

    No External Calls

    The targeting function runs in a PTB controlled by the game engine. You cannot make calls to external modules or read from other shared objects within the targeting function itself.

    Gas Constraints

    The game engine imposes a gas budget on targeting calls. Complex logic (many iterations, heavy computation) might exceed this budget. Keep your targeting loop simple -- O(n) over the candidate list is fine, but avoid nested loops or expensive operations.

    Candidate Data Limitations

    You can only act on the data provided in TargetCandidateInfo:

    • item_id() -- target identifier
    • character_id() -- pilot identifier
    • character_tribe() -- tribe affiliation
    • is_aggressor() -- aggression flag
    • priority_weight() -- base priority

    You cannot query the target's ship class, HP, distance, cargo, or other game state from within the targeting function.

    Testing Custom Modes

    Since you cannot unit-test turret functions in isolation (they depend on game-provided types), testing typically happens on testnet:

  • Deploy your package: sui client publish --gas-budget 100000000
  • Authorize on a turret: Use a PTB to call authorize_extension on your turret
  • Fly a test character into range: Use a second account to approach the turret
  • Check events: Monitor TurretAlertEvent emissions via the SUI event subscription API
  • Verify targeting: Observe whether the test character is targeted correctly
  • Advanced: Event-Driven Alerts

    All turret modes should emit events for off-chain monitoring. The turret_monitor::config module provides a shared event type:

    move
    public struct TurretAlertEvent has copy, drop {
        turret_id: ID,
        owner_character_id: u64,
        owner_tribe_id: u32,
        num_targets: u64,
        num_aggressors: u64,
        mode: vector<u8>,
    }

    If you are building your own package (not extending turret_monitor), define your own event type:

    move
    use sui::event;
    
    public struct MyTurretEvent has copy, drop {
        turret_id: ID,
        targets_engaged: u64,
        mode: vector<u8>,
    }
    
    // Emit after processing
    event::emit(MyTurretEvent {
        turret_id: object::id(turret),
        targets_engaged: return_list.length(),
        mode: b"my_custom_mode",
    });

    Key Takeaways

    • All custom targeting modules follow the same template: verify receipt, unpack candidates, filter and prioritize, encode results, destroy receipt.
    • Priority weight manipulation creates tiered targeting (aggressors vs. hostiles vs. neutrals).
    • Tribe whitelisting enables alliance systems, but alliance lists must be hardcoded.
    • The game engine controls the PTB, so you cannot pass config objects or make external calls during targeting.
    • Mode switching is done by revoking one extension and authorizing another on the turret.
    • Always emit events for off-chain monitoring and alerting.

    Sign in to track your progress.