--[[ ОПИСАНИЕ ФУНКЦИЙ -- Выводит отладочное сообщение в лог, если скрипт активен (активный скрипт выбирается с помощью -- переменной debug_info.trace_script). function trace(script_name, fmt, ...) -- Переводит переменную любого типа (включая nil) в строку. Используется для отладочного вывода информации. function to_str(what) -- Создает и возвращает копию вектора function vector_copy_by_val(vec) -- Настройка параметров игровых объектов: function cfg_get_bool(char_ini, section, field, object, mandatory, default_val) function cfg_get_string(char_ini, section, field, object, mandatory, gulag_name, default_val) function cfg_get_number(char_ini, section, field, object, mandatory, default_val) -- Проверяет, находится ли stalker рядом с точкой path_point пути patrol_path function stalker_at_waypoint(stalker, patrol_path, path_point) -- Послать stalker в заданную точку patrol_path(path_point) function stalker_go_to_waypoint(stalker, patrol_path, path_point) --]] ---------------------------------------------------------------------------------------------------- -- Выводит отладочное сообщение в лог, если скрипт активен (активный скрипт выбирается с помощью -- переменной debug_info.trace_script). --function trace(script_name, fmt, ...) -- if debug_info.trace_script == script_name then -- log(string.format("[TRACE] " .. script_name .. ".script: " .. fmt, unpack(arg))) -- end --end -- Переводит переменную любого типа (включая nil) в строку. Используется для отладочного вывода информации. function to_str(what) if what == nil then return "" else return tostring(what) end end --'Копирование таблицы по значению function copy_table(dest, src) for k,v in pairs(src) do if type(v) == "table" then --' рекурсивный вызов себя же для подтаблиц dest[k] = {} copy_table(dest[k], v) else dest[k] = v end end end function print_table() local sub if subs ~= nil then sub = subs else sub = "" end for k,v in pairs(table) do if type(v) == "table" then printf(sub.."%s:", tostring(k)) print_table(v, sub.." ") elseif type(v) == "function" then printf(sub.."%s:function", tostring(k)) elseif type(v) == "userdata" then printf(sub.."%s:userdata", tostring(k)) elseif type(v) == "boolean" then if v == true then printf(sub.."%s:true", tostring(k)) else printf(sub.."%s:false", tostring(k)) end else if v ~= nil then printf(sub.."%s:%s", tostring(k),v) else printf(sub.."%s:nil", tostring(k),v) end end end end --' Возвращает расстояние между двумя точками графа с учетом разности уровней function graph_distance(vid1, vid2) local p1 = game_graph():vertex(vid1):game_point() local p2 = game_graph():vertex(vid2):game_point() --printf("GRAPH DISTANCE [%s][%s][%s] : [%s][%s][%s]", p1.x, p1.y, p1.z, p2.x, p2.y, p2.z) return game_graph():vertex(vid1):game_point():distance_to(game_graph():vertex(vid2):game_point()) end --' Сравнивает два вектора function vector_cmp(a, b) return a.x == b.x and a.y == b.y and a.z == b.z end --' Сравнивает два вектора с заданной погрешностью function vector_cmp_prec(a, b, d) return math.abs(a.x - b.x) <= d and math.abs(a.y - b.y) <= d and math.abs(a.z - b.z) <= d end -- Создает и возвращает копию вектора function vector_copy_by_val(vec) return vector():set(vec) end -- Настройка параметра типа bool у игрового объекта. -- -- char_ini - указатель на customdata (обычно object:spawn_ini()) -- section - имя секции в customdata -- field - имя поля в customdata -- object - игровой объект, от которого будет взято имя для сообщения об ошибке -- mandatory - поле обязательно должно быть задано -- -- Возвращает true или false function cfg_get_bool(char_ini, section, field, object, mandatory, default_val) if mandatory == nil then abort("section '%s': wrong arguments order in call to cfg_get_bool", section) end --printf("DEBUG: conf_get_bool: section='%s', field='%s'", section, field) if section and char_ini:section_exist(section) and char_ini:line_exist(section, field) then local ret = char_ini:r_bool(section, field) return ret end if not mandatory then if default_val then if default_val ~= false and default_val ~= true then abort("object '%s': section '%s': field '%s': default value is " .. "not boolean", object:name(), section, field) end return default_val end return false end abort("object '%s': attempt to read a non-existant boolean field '%s' in " .. "section '%s'", object:name(), field, section) end function cfg_get_string(char_ini, section, field, object, mandatory, gulag_name, default_val) if mandatory == nil or gulag_name == nil then abort("section '%s': wrong arguments order in call to cfg_get_string", section) end --printf("DEBUG: conf_get_string: section='%s', field='%s'", section, field) if section and char_ini:section_exist(section) and char_ini:line_exist(section, field) then if gulag_name and gulag_name ~= "" then return gulag_name .. "_" .. char_ini:r_string(section, field) else return char_ini:r_string(section, field) end end if not mandatory then return default_val end local err = "'Attempt to read a non-existant string field '" .. field .. "' in section '" .. section .. "'"; abort("%s", err) end function cfg_get_number(char_ini, section, field, object, mandatory, default_val) if mandatory == nil then abort("section '%s': wrong arguments order in call to cfg_get_number", section) end --printf("DEBUG: conf_get_number: section='%s', field='%s'", section, field) if section and char_ini:section_exist(section) and char_ini:line_exist(section, field) then return char_ini:r_float(section, field) end if not mandatory then return default_val end -- local err = "object name '" .. object:name() .. "': attempt to read a non-existant numeric field '" .. -- field .. "' in section '" .. section .. "'"; -- abort("%s", err) end function mob_get_string(char_ini, section, field, override, object, mandatory, default_val) if override ~= nil then return override end return cfg_get_string(char_ini, section, field, object, mandatory, "", default_val) end function mob_get_number(char_ini, section, field, override, object, mandatory, default_val) if override ~= nil then return override end return cfg_get_number(char_ini, section, field, object, mandatory, default_val) end function mob_get_bool(char_ini, section, field, override, object, mandatory, default_val) if override ~= nil then return override ~= false end return cfg_get_bool(char_ini, section, field, object, mandatory, default_val) end -- Проверяет, находится ли stalker рядом с точкой path_point пути patrol_path function stalker_at_waypoint(stalker, patrol_path, path_point) -- return stalker:level_vertex_id() == patrol_path:level_vertex_id(path_point) local stalker_pos = stalker:position() local distance = stalker_pos:distance_to_sqr(patrol_path:point(path_point)) if distance <= 0.13 then return true end return false end -- Послать stalker в заданную точку patrol_path(path_point) --[[ function stalker_go_to_waypoint(stalker, patrol_path, path_point) if stalker:animation_count() > 0 then stalker:clear_animations() end if stalker:level_vertex_id() == patrol_path:level_vertex_id(path_point) then return end stalker:set_dest_level_vertex_id(patrol_path:level_vertex_id(path_point)) stalker:set_movement_type(move.run) stalker:set_body_state(move.standing) stalker:set_sight(look.path_dir, nil, 0) stalker:set_path_type(game_object.level_path) stalker:set_mental_state(anim.danger) stalker:set_detail_path_type(move.line) end --]] function stalker_stop(stalker) stalker:set_movement_type(move.stand) end --[[ function stalker_look_at_waypoint(stalker, patrol_path, path_point) local look_pt = this.vector_copy_by_val(patrol_path:point(path_point)):sub(stalker:position()) stalker:set_sight(look.direction, look_pt, 0) end --]] --[[ function stalker_look_at_stalker(stalker, whom) local look_pt = this.vector_copy_by_val(whom:position()):sub(stalker:position()) stalker:set_sight(look.direction, look_pt, 0) end --]] --[[ function stalker_look_at_stalker_angle(stalker, whom, angle) --stalker - killer --whom - killed :) local look_pt = this.vector_copy_by_val(whom:position()):sub(stalker:position()) stalker:set_sight (look.direction, vector_rotate_y (look_pt, angle), 0) end --]] --[[ function stalker_look_firepoint_angle(stalker, whom, angle) --stalker - killer --whom - killed :) local look_pt = this.vector_copy_by_val(whom:position()):sub(stalker:position()) stalker:set_sight (look.fire_point, vector_rotate_y (look_pt, angle), 0) end --]] local function door_default_callback(door, actor) local ph_shell = door:get_physics_shell() local joint = ph_shell:get_joint_by_bone_name("door") local low_limit = 0 local hi_limit = 0 low_limit, hi_limit = joint:get_limits(low_limit, hi_limit, 0) local angle = joint:get_axis_angle(0) if angle-low_limit > hi_limit - angle then joint:set_max_force_and_velocity(100, -1.5, 0) else joint:set_max_force_and_velocity(100, 1.5, 0) end end function door_init(door) door:set_use_callback(door_default_callback) end -- Дверь door импульсом захлопнется и залочится, при юзе будет вызываться callback_fn function door_close_then_lock(door, callback_fn) local ph_shell = door:get_physics_shell() local joint = ph_shell:get_joint_by_bone_name("door") local low_limit = 0 local hi_limit = 0 low_limit, hi_limit = joint:get_limits(low_limit, hi_limit, 0) local angle = joint:get_axis_angle(0) if angle-low_limit > hi_limit - angle then joint:set_max_force_and_velocity(1000000, 0, 0) else joint:set_max_force_and_velocity(1000000, 0, 0) end end --unlock_then_open(door) -- дверь разлочится и импульсом откроется --locked(door) -- true, если дверь закрыта и залочена --При попытке поюзать незапертую дверь она просто силой открывается до максимально допустимого состояния. function parse_waypoint_data(pathname, wpflags, wpname) local rslt = {} rslt.flags = wpflags local at if string.find(wpname, "|", at, true) == nil then return rslt end local par_num local fld local val par_num = 1 for param in string.gfind(wpname, "([%w%+~_\\%=%{%}%s%!%-%,%*]+)|*") do if par_num == 1 then -- continue else if param == "" then abort("path '%s': waypoint '%s': syntax error in waypoint name", pathname, wpname) end local t_pos = string.find(param, "=", 1, true) if t_pos == nil then printf("Path_param: %s", param) abort("path '%s': waypoint '%s': syntax error in waypoint name", pathname, wpname) end fld = string.sub(param, 1,t_pos - 1) val = string.sub(param, t_pos + 1) if not fld or fld == "" then abort("path '%s': waypoint '%s': syntax error while parsing the param '%s': no field specified", pathname, wpname, param) end if not val or val == "" then val = "true" end if fld == "a" then rslt[fld] = xr_logic.parse_condlist(db.actor, "waypoint_data", "anim_state", val) else rslt[fld] = val end end par_num = par_num + 1 end return rslt end function path_parse_waypoints(pathname) if not pathname then return nil end --printf("_bp: path_parse_waypoints: pathname='%s'", pathname) local ptr = patrol(pathname) local cnt = ptr:count() local rslt = {} for pt = 0, cnt - 1 do --printf("_bp: %s", ptr:name(pt)) rslt[pt] = parse_waypoint_data(pathname, ptr:flags(pt), ptr:name(pt)) if not rslt[pt] then abort("error while parsing point %d of path '%s'", pt, pathname) end end return rslt end function path_parse_waypoints_from_arglist(pathname, num_points, ...) local arg = {...} if not pathname then return nil end local ptr = patrol(pathname) local cnt = ptr:count() if cnt ~= num_points then abort("path '%s' has %d points, but %d points were expected", pathname, cnt, num_points) end local rslt = {} local cur_arg local fl for pt = 0, cnt-1 do cur_arg = arg[pt + 1] if not cur_arg then abort("script error [1] while processing point %d of path '%s'", pt, pathname) end fl = flags32() fl:assign(cur_arg[1]) rslt[pt] = parse_waypoint_data(pathname, fl, cur_arg[2]) if not rslt[pt] then abort("script error [2] while processing point %d of path '%s'", pt, pathname) end end return rslt end function action2(obj,...) local act = entity_action() local i = 1 while true do if (arg[i] ~= nil) then act:set_action(arg[i]) else break end i = i + 1 end if (obj ~= nil) then obj:command(act,false) end return entity_action(act) end function wpn_info_get(npc) local rslt = {} local active_item = npc:active_item() local has_weapon = active_item and isWeapon(active_item) if has_weapon then rslt["id"] = active_item:id() rslt["ammo"] = active_item:get_ammo_in_magazine() end return rslt end function wpn_info_equal(wpn_info1, wpn_info2) return wpn_info1["id"] == wpn_info2["id"] and wpn_info1["ammo"] == wpn_info2["ammo"] end function get_scheme_by_section(section) local scheme = string.gsub(section, "%d", "") local at, to = string.find(scheme, "@", 1, true) if at and to then scheme = string.sub(scheme, 1, at - 1) end return scheme end -- a | b | c ==> { 1 = "a", 2 = "b", 3 = "c" } function parse_params(params) --printf("_bp: parse_params: params=%s", params) local rslt = {} local n = 1 for fld in string.gfind(params, "%s*([^|]+)%s*") do --printf("_bp: parse_params iter=%d, fld=%s", n, fld) rslt[n] = fld n = n + 1 end return rslt end function is_day() return level.get_time_hours() >= 5 and level.get_time_hours() < 22 end function electro_art_enabled () return level.get_time_hours() >= 0 and level.get_time_hours() < 5 end function no_need_to_rotate(npc, target_pos) local y = yaw(npc:direction(), utils.vector_copy_by_val(target_pos):sub(npc:position())) return y < 0.3 end function no_need_to_rotate_xz(npc, target_pos) local dir1 = npc:direction() dir1.y = 0 local dir2 = utils.vector_copy_by_val(target_pos):sub(npc:position()) dir2.y = 0 local y = yaw(dir1, dir2) return y < 0.3 end -- Перевод угла из радианов в градусы function rad2deg(r) return r * 180.0 / math.pi end -- Перевод угла из градусов в радианы function deg2rad(d) return d * math.pi / 180.0 end -- угол между двумя векторами в градусах. function angle_diff(a1, a2) local b1 = a1:normalize() local b2 = a2:normalize() local dotp = b1:dotproduct(b2) return rad2deg(math.acos(math.abs(dotp))) end -- true, если нужно поворачивать влево function angle_left(dir1, dir2) local dir_res = vector() dir_res:crossproduct(dir1, dir2) return dir_res.y <= 0 end function angle_left_xz(dir1, dir2) local dir_res = vector() dir1.y = 0 dir2.y = 0 dir_res:crossproduct(dir1, dir2) return dir_res.y <= 0 end function get_nearest_waypoint(obj, pathname, ptr, cnt) local pt_chosen = nil local min_dist = nil local dist for i = 0, cnt - 1 do dist = obj:position():distance_to(ptr:point(i)) if not min_dist or dist < min_dist then min_dist = dist pt_chosen = i end end if not pt_chosen then abort("object '%s': path '%s': get_nearest_waypoint: unable to choose a nearest waypoint (path has no waypoints?)", obj:name(), pathname) end return pt_chosen end function npc_in_zone(npc, zone) return npc ~= nil and zone ~= nil and zone:inside(npc:position()) end ------------------------- запись/загрузка CTime --------------------------- local CTime_0 if not editor() then CTime_0 = game.CTime() end -- запись CTime в пакет. если t=nil, то запишет один нулевой байт function w_CTime( p, t ) --set_save_marker(p, "save", false, "CTIME") if t == nil then p:w_u8(-1) --set_save_marker(p, "save", true, "CTIME") return end if (CTime == nil) or (t ~= CTime_0) then local Y, M, D, h, m, s, ms = 0, 0, 0, 0, 0, 0, 0 Y, M, D, h, m, s, ms = t:get( Y, M, D, h, m, s, ms ) p:w_u8 ( Y - 2000 ) p:w_u8 ( M ) p:w_u8 ( D ) p:w_u8 ( h ) p:w_u8 ( m ) p:w_u8 ( s ) p:w_u16( ms ) else p:w_u8 ( 0 ) end --set_save_marker(p, "save", true, "CTIME") end -- чтение CTime из пакета function r_CTime( p ) --set_save_marker(p, "load", false, "CTIME") local Y = p:r_u8() if Y == 255 then --set_save_marker(p, "load", true, "CTIME") return nil end if Y ~= 0 then local t = game.CTime() local M, D, h, m, s, ms = p:r_u8(), p:r_u8(), p:r_u8(), p:r_u8(), p:r_u8(), p:r_u16() t:set( Y + 2000, M, D, h, m, s, ms) --set_save_marker(p, "load", true, "CTIME") return t else --set_save_marker(p, "load", true, "CTIME") return 0 end end --------------------------------------------------------------------------- -- отослать в ближайшую разрешённую ноду по направлению к заданной. -- возвращает vertex_id, в которое отправил персонажа function send_to_nearest_accessible_vertex( npc, v_id ) if not npc:accessible( v_id ) then local vtemp = vector() --printf("vertex_position") v_id, vtemp = npc:accessible_nearest( level.vertex_position( v_id ), vtemp ) end npc:set_dest_level_vertex_id( v_id ) return v_id end -- происходит ли в данный момент смена уровня? -- нужно для того, чтобы объекты знали, какую информацию записывать при сохранении, а какую нет function level_changing() local sim = alife() if not sim then return false end local actor_gv = game_graph():vertex( sim:actor().m_game_vertex_id ) return actor_gv:level_id() ~= sim:level_id() end function parse_data_1v(npc,s) local t = {} if s then for name in string.gfind( s, "(%|*%d+%|[^%|]+)%p*" ) do -- printf("[%s]", utils.to_str(name)) local dat = { dist = nil, state = nil} local t_pos = string.find( name, "|", 1, true ) local dist = string.sub( name, 1, t_pos - 1 ) local state = string.sub( name, t_pos + 1) -- printf(" [%s]=[%s][%s]", utils.to_str(dist), utils.to_str(state), utils.to_str(sound)) dat.dist = tonumber(dist) if state then dat.state = xr_logic.parse_condlist(npc, dist, state, state) end t[tonumber(dist)] = dat end end return t end --' Вычитка свойств для спауна объектов. function parse_names(s) local t = {} for name in string.gfind( s, "([%w_%-.\\]+)%p*" ) do table.insert( t, name ) end return t end function parse_spawns(str) --' если default-ов больше, чем значений в ini, то забить недостающие последним значением из ini local t = utils.parse_names(str) local n = #t local ret_table = {} local k = 1 while k <= n do local spawn = {} spawn.section = t[k] --' Проверяем что это не последняя запись if t[k+1] ~= nil then local p = tonumber(t[k+1]) --' проверяем что вторым числом задана вероятность, а не другая секция спавну if p then --' забиваем число spawn.prob = p k = k + 2 else --' забиваем дефолт 1 spawn.prob = 1 k = k + 1 end else spawn.prob = 1 k = k + 1 end table.insert(ret_table, spawn) end return ret_table end function r_2nums( spawn_ini, section, line, def1, def2 ) if spawn_ini:line_exist( section, line ) then -- если default-ов больше, чем значений в ini, то забить недостающие последним значением из ini local t = utils.parse_names( spawn_ini:r_string( section, line ) ) local n = #t if n == 0 then return def1, def2 elseif n == 1 then return t[1], def2 else return t[1], t[2] end else return def1, def2 end end function parse_target(target) local pos = string.find(target, ",") if pos then return string.sub(target, 1, pos - 1), string.sub(target, pos + 1) else return target, nil end end function parse_data(npc,s) local t = {} if s then for name in string.gfind( s, "(%|*%d+%|[^%|]+)%p*" ) do -- printf("[%s]", utils.to_str(name)) local dat = { dist = nil, state = nil, sound = nil} local t_pos = string.find( name, "|", 1, true ) local s_pos = string.find( name, "@", 1, true ) local dist = string.sub( name, 1, t_pos - 1 ) local state local sound if s_pos then state = string.sub( name, t_pos + 1, s_pos - 1 ) sound = string.sub( name, s_pos + 1) else state = string.sub( name, t_pos + 1) end -- printf(" [%s]=[%s][%s]", utils.to_str(dist), utils.to_str(state), utils.to_str(sound)) dat.dist = tonumber(dist) if state then dat.state = xr_logic.parse_condlist(npc, dist, state, state) end if sound then dat.sound = xr_logic.parse_condlist(npc, dist, sound, sound) end table.insert(t, dat) end end return t end function parse_syn_data(npc,s) -- printf("parse_syn_data [%s]", utils.to_str(s)) local t = {} if s then for name in string.gfind( s, "(%|*[^%|]+%|*)%p*" ) do local dat = { zone = nil, state = nil, sound = nil} -- printf("[%s]", utils.to_str(name)) local t_pos = string.find( name, "@", 1, true ) local s_pos = string.find( name, "|", 1, true ) local state = string.sub( name, 1, t_pos - 1 ) local sound if s_pos then sound = string.sub( name, t_pos + 1, s_pos -1) else sound = string.sub( name, t_pos + 1) end dat.state = state dat.sound = sound table.insert(t, dat) end end return t end function is_widescreen() return (device().width/device().height>1024/768+0.01) end