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:
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:
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:
base + 25000 (20000 + 5000)base + 5000base + 20000basePattern 2: Whitelist/Blacklist by Tribe
You can create an alliance system by whitelisting specific tribes:
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:
// 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:
// Package contains multiple modules, each with its own auth type:
// my_turret::defensive → DefensiveAuth
// my_turret::aggressive → AggressiveAuth
// my_turret::lockdown → LockdownAuthThe turret owner authorizes one mode at a time. To switch modes, they:
revoke_extension(turret, cap) 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 identifiercharacter_id()-- pilot identifiercharacter_tribe()-- tribe affiliationis_aggressor()-- aggression flagpriority_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:
sui client publish --gas-budget 100000000authorize_extension on your turretTurretAlertEvent emissions via the SUI event subscription APIAdvanced: Event-Driven Alerts
All turret modes should emit events for off-chain monitoring. The turret_monitor::config module provides a shared event type:
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:
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.