Post tutorial RSS Smart Terrains

A short tutorial about smart terrains, how they work, what they do, and mostly how to set them up. Have a good scripting! :)

Posted by on - Intermediate Client Side Coding

Lately some people asked us how to create and script smart terrains. To avoid responding for each single email we decided to create this little tutorial. We will use ST as Smart Terrain abbreviation. This tutorial treats about npc's ST-s but for mutants it's almost the same.

We assume you are familiar with:

- lua programming
- all.spawn editing (with acdc)
- waypoints (patrols)
- ai schemes

First you have to think about your ST, where to spawn it, how many npcs/mutants will "work" for your ST, when ST should be activated/terminated, how many states your ST will have and so on. We guess the best way would be to show you how to get it working by an example. So let's say we want to spawn ST in Cordon for 3 bandits.

1. Spawning ST:

; cse_abstract properties
section_name = smart_terrain
name = esc_bandits_smart_terrain
position = 131.02030944824,0.065616846084595,-248.9094543457
direction = 0,0,0

; cse_alife_object properties
game_vertex_id = 635
distance = 9.09999942779541
level_vertex_id = 363757
object_flags = 0x==3e
custom_data = < [smart_terrain]
type = esc_bandits_smart_terrain
cond = {-infoportion}
capacity = 3
squad = 1
groups = 5
respawn = esc_respawn_inventory_box_0002

; cse_shape properties
shapes = shape0
shape0:type = sphere
shape0:offset = 0,0,0
shape0:radius = 20.55957102775574

; cse_alife_space_restrictor properties
restrictor_type = 3

; se_smart_terrain properties

this is actually the most important stuff:

type = esc_bandits_smart_terrain
cond = {-infoportion}
capacity = 3
squad = 1
groups = 5
respawn = esc_respawn_inventory_box_0002

'''type''' is a name of our new ST (required)
'''cond''' describes conditions which have to be met (optional)
'''capacity''' ST npcs/mutants quantity i.e. how many npcs/mutants our ST can handle (required)
'''squad, groups''' no idea what they are used for, maybe they are not used at all but i might be wrong (both are optional)
'''respawn''' name of the stash (blue box) where items will be spawn when ST respawn will be called (optional)

2. Spawning npcs/mutants and assigning (binding) them to our ST: in this case for each mutant/npc we have to add to its logic:

custom_data = < [smart_terrains]
esc_bandits_smart_terrain = true

3. Adding job (logic) for each npc/mutant from our ST (for each state). Let's say our ST have two states: state 0 (describes what npcs/mutants are doing during day) and state 1 (during night). We have 3 bandits, so say:

- bandit1: walker (state 0) and kamp (state 1)
- bandit2: guard (state 0) and sleeper (state 1)
- bandit3: walker (state 0 and 1 <= he's doing the same at day and night)

There are at least 3 ways to add logic (job) for each npc/mutant, we will use the most common one, i.e. we will add logic to config\misc\gulag_escape.ltx file. It may looks like this:

;-- bandit1 (walker -> state 0, i.e. during day)
active = walker@esc_bandits_smart_terrain_bandit1

path_walk = bandit1_walk
danger = danger_condition@esc_bandits_smart_terrain
def_state_moving1 = patrol
def_state_moving2 = patrol
def_state_moving3 = patrol
meet = no_meet

;-- bandit1 (kamp -> state 1, i.e. during night)
active = kamp@esc_bandits_smart_terrain_bandit1

center_point = bandit_kamp
path_walk = bandit_kamp_task

;-- bandit2 (guard -> state 0, i.e. during day)
active = walker@esc_bandits_smart_terrain_bandit2

path_walk = bandit2_walk
path_look = bandit2_look
danger = danger_condition@esc_bandits_smart_terrain

;-- bandit2 (sleeper -> state 1, i.e. during night)
active = sleeper@esc_bandits_smart_terrain_bandit2

path_main = bandit2_sleep
wakeable = false

;-- bandit3 (guard -> state 0 and 1, i.e. during day/night)
active = walker@esc_bandits_smart_terrain_bandit3

path_walk = bandit3_walk
path_look = bandit3_look

ignore_distance_corpse = 0
ignore_distance = 0

4. Now we have to script our ST. In this case we will add our code to scripts\gulag_escape.script file. There are several things we need to do here (each of this steps is required):

- load logic (jobs) for each npc/mutant, for each state -> function load_job(...)

if type == "esc_bandits_smart_terrain" then
t = {}
;-- section is a "link" to logic defined in ltx file
t.section = "logic@esc_bandits_smart_terrain_bandit1_walker"
;-- no idea, probably describes after what time
;-- npc will use this job again (?)
t.idle = 0
;-- no idea but i guess it's optional
t.timeout = 0
;-- priority
t.prior = 100
;-- npc will use this logic if ST switched to this state
;-- in this case - state 0 (day)
t.state = {0}
;-- no idea about squad and group
t.squad = squad = groups[1]
;-- no idea what means position_threshold
t.position_threshold = 100
;-- describes whether npc in this state is online or offline
;-- online = true by default = true
;-- describes restrictors (where npc can/can't go)
t.in_rest = ""
t.out_rest = ""
;-- because of the way how jobs are assigning by
;-- smart_terrain.script you never know which job
;-- will be used by which npc; if you want to ensure
;-- that certain job is used by certain npc then
;-- you have to use predicate function; in this case
;-- we want this job to be used by expert (master) bandit
t.predicate = function(obj_info) return obj_info.rank >= 900 end
table.insert(sj, t)

t = {section = "logic@esc_bandits_smart_terrain_bandit1_kamp",
idle = 0, timeout = 0, prior = 100, state = {1},squad = squad,
group = groups[1], position_threshold = 100, online = true, in_rest = "",
out_rest = "", predicate = function(obj_info) return obj_info.rank >= 900 end}
table.insert(sj, t)

;-- bandit2 -> state 0 (day)
t = {section = "logic@esc_bandits_smart_terrain_bandit2_walker",
idle = 0, prior = 5, state = {0}, squad = squad, group = groups[1],
in_rest = "", out_rest = ""}
table.insert(sj, t)

;-- bandit2 -> state 1 (night)
t = {section = "logic@esc_bandits_smart_terrain_bandit2_sleeper",
idle = 0, prior = 5, state = {1}, squad = squad, group = groups[1],
in_rest = "", out_rest = ""}
table.insert(sj, t)

;-- bandit3 -> state 0 (day) and state 1 (night)
t = {section = "logic@esc_bandits_smart_terrain_bandit3_walker",
idle = 0, prior = 5, state = {0, 1}, squad = squad, group = groups[1],
in_rest = "", out_rest = ""}
table.insert(sj, t)

One more thing about ST states, it's up to you how many states your ST have. The important thing is to add logic for each state. For instance your ST can have those states:

0 - npcs are offline
1 - npcs are online (day)
2 - npcs are online (night)
3 - npcs are online, they decided to assault other ST
4 - npcs are online and actor attacks them

And another thing, i'm sick when i have to fill so many tables, so i usually use this function:

function fill_tbl(section, idle, prior, states, squad, group, in_rest, out_rest, online, gulag_name)
local tbl = {}

tbl.section = "logic@" .. gulag_name .. "_" .. section
tbl.idle = idle
tbl.prior = prior
tbl.state = {}

for index = 1, #states do
table.insert(tbl.state, states[index])

tbl.squad = squad = group
tbl.in_rest = in_rest
tbl.out_rest = out_rest = online
return tbl

So using above function, we can load logic like this:

if type == "esc_bandits_smart_terrain" then
local t = table.insert(sj, fill_tbl("bandit1_walker", 0, 100, {0}, squad, groups[1], "", "", true, type))
t.timeout = 0
t.position_threshold = 100
t.predicate = function(obj_info) return obj_info.rank >= 900 end
table.insert(sj, t)

t = table.insert(sj, fill_tbl("bandit1_kamp", 0, 100, {1}, squad, groups[1], "", "", true, type))
t.timeout = 0
t.position_threshold = 100
t.predicate = function(obj_info) return obj_info.rank >= 900 end
table.insert(sj, t)

table.insert(sj, fill_tbl("bandit2_walker", 0, 5, {0}, squad, groups[1], "", "", true, type))
table.insert(sj, fill_tbl("bandit2_sleeper", 0, 5, {1}, squad, groups[1], "", "", true, type))
table.insert(sj, fill_tbl("bandit3_walker", 0, 5, {0, 1}, squad, groups[1], "", "", true, type))

- automatically change job for each npc/mutant -> function load_states(...)

if type == "esc_bandits_smart_terrain" then
return function(gulag)
if not then
return gulag.state
if level.get_time_hours() >= 5 and level.get_time_hours() <= 22 then
return 0 -- switch all mutants/npc to daily job
return 1 -- switch all mutants/npc to nightly job

- ensure that our ST will be used only by bandits -> function checkStalker(...)

if gulag_type == "esc_bandits_smart_terrain" then
return npc_community == "bandit"


Check our tool for smart terrain debugging and waypoint exporting:




I'm no scripter nor modder. So to say the least, all that seems intimidating just to get a few active NPCs walking around smart terrain.

Reply Good karma Bad karma+1 vote

I don't know why, but every time someone finds out just how hard making games is, it warms my heart. :)

Reply Good karma Bad karma+4 votes

position = 131.02030944824,0.065616846084595,-248.9094543457

this makes a lot of sense to me, is this perhaps yantar near the big oak tree :) NO I DON'T UNDERSTAND A WORD YOUR SAYING THERE BUT LIKE TO :)))

Reply Good karma Bad karma+6 votes

I've read this in the past on the STALKER wiki, and it was very helpful, so thank you for taking the time to write it!

Since I've been doing a lot of spawning on empty maps for RCOM, I was thinking about updating the wiki eventually and making an article more relevant to Call of Pripyat, since there aren't many articles on it and I spent a lot of time figuring figuring out the particulars of it. I know how much time can be saved having the process explained in a tutorial versus figuring it out, and the tutorials you guys have wrote have saved me much time in the past so I hope to do the same soon!

Reply Good karma Bad karma+2 votes
dezodor Author

well if you update it, pls make an other entry, and mb rename the old as smart terrain tutorial (soc) and call yours as smart terrain tutorial (cop) :) this way wiki will have more articles, and also ppl wont get confused.

Reply Good karma+3 votes

Oh yea, I would name it appropriately, of course! :)

Don't know if you've looked at CoP's scripts much yet, but if walkers and guards have their way points named appropriately, a basic default logic is applied and one doesn't need to be manually defined. I don't know if ShoC has this (I never knew if so), but it sure is convenient for quickly creating non-exclusive alife jobs for simulation smart terrains.

Reply Good karma Bad karma+1 vote
dezodor Author

yep, it was on stk wiki since 2years, we just saw its possible to drop here tutorials too, so we will slowly port all our tuts from wiki to this site.

Reply Good karma+4 votes

Thank you, Dez0wave, for doing this. I think this will be very helpful for lots of people (including me) on their way to create nice mods and improve stalker mods quality. :)

Reply Good karma Bad karma+2 votes

Whaaat a tutorial?! nice!

Reply Good karma Bad karma+1 vote

Nice of you, it's really useful!

Reply Good karma Bad karma+2 votes
Post a comment
Sign in or join with:

Only registered members can share their thoughts. So come on! Join the community today (totally free - or sign in with your social account on the right) and join in the conversation.