Cycle

The Scriptorium

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

Turret Targeting Modes
IntermediateChapter 2 of 420 min read

Aggression Mode

The aggression mode is the most common turret behavior: only shoot targets that have attacked you or your allies. Tribe members are always protected, even if they are aggressors.

Full Source

Here is the complete aggression.move from the turret_monitor package:

move
/// Mode: AGGRESSION ONLY — shoot anyone who attacks, protect tribe members.
module turret_monitor::aggression;

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

public struct AggressionAuth has drop {}

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 len = candidates.length();
    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 num_aggressors: u64 = 0;
    let mut i: u64 = 0;

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

        if (is_aggressor) { num_aggressors = num_aggressors + 1; };

        // Shoot aggressors only. Protect tribe (even aggressive tribe members excluded).
        let excluded = is_owner || is_same_tribe || !is_aggressor;

        if (!excluded) {
            return_list.push_back(turret::new_return_target_priority_list(
                candidate.item_id(), candidate.priority_weight() + 10000,
            ));
        };
        i = i + 1;
    };

    config::emit_alert(
        object::id(turret), owner_char_id, owner_tribe,
        len, num_aggressors, b"aggression_only",
    );

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

Line-by-Line Walkthrough

Setup

move
assert!(receipt.turret_id() == object::id(turret), 0);

First safety check: verify that the OnlineReceipt belongs to this turret. This prevents receipt reuse across different turrets.

move
let candidates = turret::unpack_candidate_list(target_candidate_list);
let len = candidates.length();

Decode the BCS-encoded candidate list into a vector of TargetCandidateInfo structs.

move
let owner_tribe = owner_character.tribe();
let owner_char_id = owner_character.key().item_id();

Extract the turret owner's tribe ID and character ID. These are used for comparison against each candidate.

The Targeting Loop

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

while (i < len) {
    let candidate = candidates.borrow(i);

Initialize an empty return list, an aggressor counter (for alert events), and a loop index. SUI Move uses while loops -- there is no for loop syntax.

move
    let is_aggressor = candidate.is_aggressor();
    let is_same_tribe = candidate.character_tribe() == owner_tribe;
    let is_owner = (candidate.character_id() as u64) == owner_char_id;

For each candidate, determine three boolean properties:

  • is_aggressor: Has this target recently attacked the turret owner or allies?
  • is_same_tribe: Does this target belong to the owner's tribe?
  • is_owner: Is this the turret owner themselves? (Cannot shoot yourself.)

Note the cast (candidate.character_id() as u64) -- character_id() returns u32 but item_id() returns u64, so a cast is needed for comparison.

move
    if (is_aggressor) { num_aggressors = num_aggressors + 1; };

Count aggressors for the alert event (emitted after the loop).

The Exclusion Logic

move
    let excluded = is_owner || is_same_tribe || !is_aggressor;

This is the core targeting decision. A target is excluded (not shot) if ANY of these are true:

  • is_owner -- never shoot the turret owner
  • is_same_tribe -- never shoot tribe members (even aggressive ones)
  • !is_aggressor -- only shoot aggressors

The order of these checks does not matter logically (they are OR-ed), but reading it as "exclude the owner, exclude the tribe, exclude non-aggressors" makes the intent clear.

Adding to the Return List

move
    if (!excluded) {
        return_list.push_back(turret::new_return_target_priority_list(
            candidate.item_id(), candidate.priority_weight() + 10000,
        ));
    };

If the target passes all checks (is an aggressor, is not the owner, is not in the tribe), add it to the return list with a boosted priority weight (+ 10000). The weight boost ensures aggressors are prioritized over the game engine's default ordering.

The Alert Event

move
config::emit_alert(
    object::id(turret), owner_char_id, owner_tribe,
    len, num_aggressors, b"aggression_only",
);

After processing all candidates, emit an on-chain event if there are aggressors. This is handled by the shared config module:

move
// From turret_monitor::config
public fun emit_alert(
    turret_id: ID,
    owner_character_id: u64,
    owner_tribe_id: u32,
    num_targets: u64,
    num_aggressors: u64,
    mode: vector<u8>,
) {
    if (num_aggressors > 0) {
        event::emit(TurretAlertEvent {
            turret_id, owner_character_id, owner_tribe_id,
            num_targets, num_aggressors, mode,
        });
    };
}

Events are only emitted when aggressors are present. Off-chain services can subscribe to TurretAlertEvent to send notifications, log attacks, or trigger automated responses.

Cleanup

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

Encode the return list as BCS bytes, destroy the hot potato receipt with the AggressionAuth witness, and return the encoded result.

Behavior Summary

Given these candidates:

TargetTribeAggressor?Result
OwnerSame-Excluded (is_owner)
Tribe allySameNoExcluded (is_same_tribe)
Tribe allySameYesExcluded (is_same_tribe)
Neutral playerDifferentNoExcluded (!is_aggressor)
Hostile attackerDifferentYesTARGETED

The aggression mode is purely defensive -- it only fires at non-tribe aggressors.

The Priority Weight System

The priority_weight value determines the order in which the turret engages targets. Higher weight = engaged first.

move
candidate.priority_weight() + 10000

The base weight comes from the game engine and typically factors in distance, target size, and other game mechanics. Adding 10000 to aggressors ensures they are always high-priority targets.

If you wanted to add sub-prioritization (e.g., prioritize closer aggressors), you could use the base weight without the flat bonus, or add a variable bonus.

Key Takeaways

  • Aggression mode targets only non-tribe aggressors. Tribe members are always protected.
  • The exclusion logic is a single boolean expression: is_owner || is_same_tribe || !is_aggressor.
  • Priority weight + 10000 ensures aggressors are targeted before any default ordering.
  • TurretAlertEvent is emitted on-chain when aggressors are present, enabling off-chain notifications.
  • The hot potato receipt is destroyed with AggressionAuth {} -- the module-specific witness type.

Sign in to track your progress.