Skip to main content

Controllers

Where Entities describe the data, Controllers describe the logic of your quest. Controllers are, in essence, lua objects with following methods:

  1. on_init - runs once when entity is initialized
  2. on_del - runs once when entity is destroyed
  3. status - for task status, runs on every task update
  4. quest_target - for PDA marker, runs on every task update

Controllers are defined per entity and works with data in their entity; they have no access to anything else. Let's take a look at one such Controller:

igi_target_get.script
Get = {}
function Get.on_init(entity)
trace_assert(type(entity.id) == "number", "Get: entity.id is not a number", entity)
end

function Get.status(entity)
local se_obj = alife_object(entity.id)
if not se_obj then return TASK_STATUSES.FAILED end
if se_obj.parent_id == 0 then return TASK_STATUSES.COMPLETED end
return TASK_STATUSES.RUNNING
end

function Get.quest_target(obj_data)
local se_obj = assert(alife_object(obj_data.id))
if se_obj.parent_id == 65535 then
return obj_data.id
else
return se_obj.parent_id
end
end

The idea behind igi_target_get.Get is "this item should be in actor's inventory". It uses on_init to ensure id of the item exists in entity and is a number. After that, the logic is simple: status returns COMPLETED if the item is in player's inventory, FAILED if the item doesn't exist and RUNNING otherwise. quest_target is just as simple: it points to item if it's in the world, and on its parent otherwise.

Controllers have full mutable access to their entity; they can overwrite fields, add new ones or remove fields. Controllers are the main mechanism of mutability in WTF; Frames can also be mutable, but that functionality is experimental.

Helper controllers

You can add more Controllers to your entity, with a nuance that these Helpers won't in any way affect task status or PDA markers. on_init, on_del and status hooks make Helpers perfect for configuration and side effects; for example:

wtf.script
PinToSmart = {
on_init = function (entity)
local se_obj = alife_object(entity.id)
if se_obj then
se_obj.scripted_target = se_obj.smart_id
end
end,

status = function (entity)
local se_obj = alife_object(entity.id)
if se_obj then
se_obj.stay_time = game.get_game_time()
end
end,

on_del = function (entity)
local se_obj = alife_object(entity.id)
if se_obj then
se_obj.scripted_target = nil
end
end
}

wtf.PinToSmart is a simple Helper which sets scripted_target of a squad to its current smart. While it is possible to do that with macros, writing macros for every squad in every quest is prone to errors, it is easy to forget to release them at the end, and also it's just a lot of boilerplate.

on_init and on_del were a nice side effect, but the main motivation behind Helpers is its status method. There, you can add actions which will trigger sometime at runtime, like in this one:

wtf.script
Repair = function (new_condition)
return {
status = function (entity)
if entity._repair_done then return end
local item = get_object_by_id(entity.id)
if not item then return false end
if item:condition() < (new_condition / 100) then
get_object_by_id(entity.id):set_condition(new_condition)
entity._repair_done = true
end
end
}
end

This Helper is a bit more fancy, but the core functionality is clear: it will repair an item, it trigger exactly once and only when an item comes online.

Lifecycle hooks

on_init and on_del methods are called by the framework once the entity enters its respective lifecycle phase (Initialization or Finish).

  • on_init is called after init$ macros are resolved, first in-order for all the Helpers, then for the Controller
  • on_del is called in reverse order, first for Controller, then for Helpers in reverse order, and only then del$ macros are resolved

As an implementation detail, at the Initialization stage, the framework goes through entities in-order, and at the Finish stage in reverse order.