Condition Lists Tutorial


Courtesy of tdef#6225 Transfered to modding book by demonized#1084

Condition list or condlist is one way to write dynamic configuration files and have this structure:

somefile.ltx

[some_section]
my_condlist = {=A(a1:a2) !B +C -D ~30} X %=E(e1) +F -G%, Y

and are used like this:

somescript.script

local ini = ini_file("path\\to\\somefile.ltx")
local condlist = ini:r_string_to_condlist("some_section","my_condlist")
local result = xr_logic.pick_section_from_condlist(game_object_1, game_object_2, condlist)

the code above is the equivalent of:

if                                                                  -- {
    xr_conditions.A(game_object_1, game_object_2, {"a1","a2"}) and  -- =A(a1:a2)
    not xr_conditions.B(game_object_1, game_object_2) and           -- !B
    db.actor:has_info("C") and                                      -- +C
    not db.actor:has_info("D") and                                  -- -D    
    math.random(1,100) > 30                                         -- ~30 (not a typo, ~30 means 70% chance, ~80 means 20% and so on)
    
then                                                                -- }
    result = "X"                                                    -- X
                                                                    -- %
    xr_effects.E(game_object_1, game_object_2, {"e1"})              -- =E(e1)
    db.actor:give_info_portion("F")                                 -- +F
    db.actor:disable_info_portion("G")                              -- -G
                                                                    -- %
else                                                                -- ,
    result = "Y"                                                    -- Y
end

Condlist can have more than one condition block:

my_condlist = {-A -B} X, {-A +B} Y, Z

this is the equivalent of:

if                                                                     
    not db.actor:has_info("A") and
    not db.actor:has_info("B")    
then        
    result = "X"
elseif
    not db.actor:has_info("A") and
    db.actor:has_info("B")    
then        
    result = "Y"
else
    result = "Z"
end

All condlist elements are optional, these are all valid condlist:

cond1 =                      ;returns nil
cond2 = true                 ;returns "true"
cond3 = %=f%                 ;returns nil and executes xr_effects.f(a,b) --> remember that a and b depend on the arguments of pick_section_from_condlist 
cond4 = {-info1} , %=f1%     ;if actor doesn't have info1 then returns nil, else returns nil and executes xr_effects.f1(a,b)
cond5 = {+info1} %=f1%       ;same effect of previous one but easier to read

The return value can be ignored to use condlist as a way to run functions listed from a config file, one example are the on_complete lines in task manager files:

on_complete = %=reward_stash(true)%

this condlist has no condition (the part inside {}) and no return value, so calling

xr_logic.pick_section_from_condlist()

on this is the equivalent of just calling

xr_effects.reward_stash(x, x, {"true"})

Remember that both the return on pick_section_from_condlist and arguments passed to xr_conditions and xr_effects functions are strings and not boolean/numbers

Some config lines are condlist even if they aren't obvious, for example one line can be

something = true

but in reality it's a condlist that always returns "true", the only way to know for sure it's to search for "something" in all scripts and see if that config line is parsed as condlist anywhere.

Condlist can be also be parsed from a string

local condlist1 = ini:r_string_to_condlist("some_section","my_condlist")
local somestring = ini:r_string_ex("some_section","my_condlist")
local condlist2 = xr_logic.parse_condlist(game_object, string1, string2, somestring)

condlist1 and condlist2 are the same, the first 3 arguments of xr_logic.parse_condlist are used only to print a message if the 4th (the actual condlist string) is nil, or just use alun_utils.parse_condlist(string)

Now we examine some condlist in Anomaly, what they mean and some examples of edits we can make.

Example 1

near the end of tm_dynamic.ltx in configs/misc

[simulation_task_52]
icon = ui_inGame2_PD_Torgovets_informatsiey
storyline = false
prior = 120
sim_communities = army, bandit, csky, dolg, ecolog, freedom, killer, stalker
repeat_timeout = 14400
precondition = {=random_chance(100)} true, false

title = simulation_task_52_name
descr = simulation_task_52_text
descr_functor = general_fate_desc
job_descr = simulation_task_52_about
task_complete_descr = simulation_task_52_finish

stage_complete     = 4
target_functor     = general_fate
status_functor     = fate_task
condlist_0         = {!task_giver_alive(simulation_task_52)} fail

on_job_descr = %=on_init_fate_task(simulation_task_52:detector_radio:broken_pda:k01_darkscape:k02_trucks_cemetery:y04_pole:l06_rostok:zaton:jupiter:pripyat)%
on_complete  = %=inc_goodwill_by_tasker_comm(simulation_task_52:50) =reward_stash(true) =reward_random_money(10500:18500) =reward_random_item(beer:vodka:vodka2:cigarettes_lucky_3:cigarettes_russian_3) -simulation_task_52_dead_spawned -simulation_task_52_item_spawned -simulation_task_52_target_found =remove_quest_item(simulation_task_52) =forget_dead_npcs(simulation_task_52) =pstor_reset(simulation_task_52)%
on_fail      = %=remove_quest_item(simulation_task_52) -simulation_task_52_dead_spawned -simulation_task_52_target_found -simulation_task_52_item_spawned =forget_dead_npcs(simulation_task_52) =pstor_reset(simulation_task_52)%

if we change precondition line to

precondition = {=is_rain} true, false

then npc will propose this quest only if you ask them while it's raining

if we change it to

precondition = false

then npc will never give this quest

true and false dont have any special meaning in the condlist on their own, in this case they matter only because the code that parses the condlist spelled out by the precondition line does different things depending on the pick_section_from_condlist return value

if we change on_complete to

on_complete = aslkdjslakd %=inc_goodwill_by_tasker_comm(simulation_task_52:50) =reward_stash(true) =reward_random_money(10500:18500) =reward_random_item(beer:vodka:vodka2:cigarettes_lucky_3:cigarettes_russian_3) -simulation_task_52_dead_spawned -simulation_task_52_item_spawned -simulation_task_52_target_found =remove_quest_item(simulation_task_52) =forget_dead_npcs(simulation_task_52) =pstor_reset(simulation_task_52)%

everything will still work as before, because the code that runs the pick_section_from_condlist on on_complete does nothing with the return value, so if it's nil, random text, true/false or whatever doesn't matter

Example 2

inside devushka.ltx in configs/scripts/escape

[walker@devushka]
path_walk = guard_2_walk
path_look = guard_2_look
on_info = {=surge_started} walker@surge
on_info2 = {=is_night} walker@sleeper_1
on_info3 = {=is_rain} walker@rain
combat_ignore_cond = {=actor_enemy =check_enemy_name(actor)} false, false
combat_ignore_keep_when_attacked = {=is_warfare} false, true
reach_distance = 10
gather_items_enabled = false
help_wounded_enabled = false
corpse_detection_enabled = false
invulnerable = {=is_warfare} false, {!actor_enemy} true, false
on_game_timer = 2400 | remark@smoking_stand

we change on_info3 to

on_info3 = {=is_rain =is_actor_enemy_to_faction(freedom)} walker@rain

now Hip will seek shelter from rain only if we are enemy to Freedom, women be like that sometimes

Custom conditions

if you want to make custom conditions either create new functions inside xr_conditions or make your own script file defining functions inside xr_conditions scope

my_conditons.script

function xr_conditions.has_more_money_than(a,b,c)
    
    -- a and b will be the game objects given as arguments to the xr_logic.pick_section_from_condlist call that evaluates this condlist
    -- most of the times a will be the actor game object and b one npc game object, but unless you're 100% sure of that (after testing or looking which pick_section_from_condlist parses it and with which arguments) 
    -- dont ever rely on those, i've seen cases where b is a server object or even nil

    local amount = c and c[1] and tonumber(c[1])
    -- c is a table where condlist arguments for the function are passed
    -- so has_more_money_than(10000) means c[1] == "10000" (a string!)
    -- so we need to convert it to a number to be able to compare it to another
    if amount then
        return db.actor:money() > amount
    end
    return false
end

now we have has_more_money_than available as conditions for condlist, now we can do

on_info = {=has_more_money_than(50000)} walker@surge

so Hip will stay outside during blowouts unless we have more than 50k rubles

you can do the same with xr_effects

my_effects.script

function xr_effects.give_tuna_to_actor(a,b,c)
    alife():create('conserva',vector(),0,0,0)
end

then change on_info2 to

on_info2 = {=is_night} walker@sleeper_1 %=give_tuna_to_actor%

and when Hip goes to sleep you'll get a tuna can