Подключение режима freeplay — S.T.A.L.K.E.R. Inside Wiki

Подключение режима freeplay

Материал из S.T.A.L.K.E.R. Inside Wiki

Перейти к: навигация, поиск

Отключение выброса на ЧАЭС-1

Создайте скрипт off_timer.script. В него напишите:

 
function delete_timer()
        local se_obj = alife():object("aes_space_restrictor_timer")  -- задаем объект
        if se_obj then
        alife():release(se_obj, true)  -- удаляем заданный объект
end
 

Теперь в каком-нибудь диалоге пишем активацию функции:

<action>off_timer.delete_timer</action>

Возврат с ЧАЭС на ранние уровни игры

Часть 1. Генерация работающего LEVEL_CHANGER

Стандартной функции alife():create(…) недостаточно для создания полноценного level_changer. Собственно методика создания сложных объектов описана в статье Один из методов спавна. Сложность заключалась лишь в порядке полей и свойствах Shape. Неоценимую помощь в этом вопросе оказала утилита ACDC (created by bardak).

Ниже я привожу код функции, которая создает и инициализирует level_changer:

 
function create_level_changer(
	p_story_id,	-- STORY_ID нового level_changer (понадобится нам позже)
	p_position,	-- вектор, координаты точки, в которой будет располагаться центр нового level_changer
	p_lvertex_id,	-- level_vertext_id  - идентифицируют уровень, на котором будет создан level_changer
	p_gvertex_id,	-- game_vertext_id   	
 
	p_gest_lv,	-- level_vertex_id   - идентифицируют уровень, на который level_changer будет перебрасывать игрока 
	p_dest_gv,	-- game_vertex_id
	p_dest_pos,	-- координаты точки, в которой на новом уровне окажется игрок
	p_dest_dir,	-- направрение взгляда игрока
	p_dest_level,	-- название уровня, например "L11_Pripyat"
	p_silent	-- следует задать 1, чтобы подавить вопрос о смене уровня (автоматический переход)
)
	local obj = alife():create("level_changer", p_position, p_lvertex_id, p_gvertex_id)
 
	level.map_add_object_spot(obj.id, "level_changer", "")
 
	local packet = net_packet()
	obj:STATE_Write(packet)
 
	-- свойства cse_alife_object
	local game_vertex_id 			= packet:r_u16()
	local cse_alife_object__unk1_f32 	= packet:r_float()
	local cse_alife_object__unk2_u32 	= packet:r_u32()
	local level_vertex_id 			= packet:r_u32()
	local object_flags 			= packet:r_u32()
	local custom_data 			= packet:r_stringZ()
	local story_id 				= packet:r_u32()
	local spawn_story_id			= packet:r_u32()
 
	-- свойства cse_shape
	local shape_count 			= packet:r_u8()
	for i=1,shape_count do
		local shape_type 		= packet:r_u8()
		if shape_type == 0 then
			-- sphere
			local center 		= packet:r_vec3()
			local radius 		= packet:r_float()
		else
			-- box
			local axis_x_x = packet:r_float()
			local axis_x_y = packet:r_float()
			local axis_x_z = packet:r_float()
			local axis_y_x = packet:r_float()
			local axis_y_y = packet:r_float()
			local axis_y_z = packet:r_float()
			local axis_z_x = packet:r_float()
			local axis_z_y = packet:r_float()
			local axis_z_z = packet:r_float()
			local offset_x = packet:r_float()
			local offset_y = packet:r_float()
			local offset_z = packet:r_float()
		end
	end
 
	-- свойства cse_alife_space_restrictor
	local restrictor_type 			= packet:r_u8()
 
	-- свойства cse_level_changer
	local dest_game_vertex_id		= packet:r_u16()  
	local dest_level_vertex_id 		= packet:r_u32()  
	local dest_position 			= packet:r_vec3()
	local dest_direction 			= packet:r_vec3()
	local dest_level_name 			= packet:r_stringZ()
	local dest_graph_point 			= packet:r_stringZ()
	local silent_mode 			= packet:r_u8()
 
 
	packet:w_begin(game_vertex_id)			-- game_vertex_id
	packet:w_float(cse_alife_object__unk1_f32)
	packet:w_u32(cse_alife_object__unk2_u32) 
	packet:w_u32(level_vertex_id)			-- level_vertex_id
	packet:w_u32( bit_not(193) )			-- object_flags = -193 = 0xFFFFFF3E
	packet:w_stringZ(custom_data)
	packet:w_u32(p_story_id)			-- story_id
	packet:w_u32(spawn_story_id)
 
	packet:w_u8(1)			-- количество фигур
--		packet:w_u8(0)			-- тип фигуры: сфера
--		packet:w_vec3(vector():set(0, 0, 0))  -- sphere_center
--		packet:w_float(3.0)
	packet:w_u8(1)			-- тип фигуры: box
	packet:w_float(2)	   -- axis_x_x
	packet:w_float(0)	   -- axis_x_y
	packet:w_float(0)	   -- axis_x_z
	packet:w_float(0)	   -- axis_y_x
	packet:w_float(4)	   -- axis_y_y
	packet:w_float(0)	   -- axis_y_z
	packet:w_float(0)	   -- axis_z_x
	packet:w_float(0)	   -- axis_z_y
	packet:w_float(4)	   -- axis_z_z
	packet:w_float(0)	   -- offset_x
	packet:w_float(0)	   -- offset_y
	packet:w_float(0)          -- offset_z
 
	packet:w_u8(3)	 	   -- restrictor_type
 
	packet:w_u16(p_gest_gv)			-- destination game_vertex_id
	packet:w_u32(p_dest_lv)			-- destination level_vertex_id
	packet:w_vec3(p_dest_pos)		-- destination position
	packet:w_vec3(p_dest_dir)		-- destination direction (направление взгляда)
	packet:w_stringZ(p_dest_level)		-- destination level name
	packet:w_stringZ("start_actor_99")	-- some string, always const
	packet:w_u8(p_silent)			-- 1 for silent level changing
 
	packet:r_seek(0)
	obj:STATE_Read(packet, packet:w_tell())
 
--	news_manager.send_tip(db.actor, "LC creation finished", nil, nil, 30000)
end
 

Для shape типа "box" загрузка координат методом packet:r_matrix() окончилась неудачей. Я подозреваю, что не был прочитан вектор "offset", но точной уверенности нет, поэтому пока остановился на покомпонентной выборке и сохранении координат.

Часть 2. Создание точек перехода

Теперь следует написать функции создания нужных точек перехода и подключить их к игре. Сами функции просты:

function exit_monolit()
if (not has_alife_info("freeplay_activated1")) then
create_level_changer(11410, vector():set(-13.26, 47.71, 46.57), 200, 2417,
2384,
162109,
vector():set( 375.615, 0.224, 27.737 ),
vector():set( 0.0, 0.0 , 0.0 ),
"L12_Stancia",
1)
 
db.actor:give_info_portion("freeplay_activated1")
end
 
-- создается переход из ЧАЭС в Припять
create_chaes2pripyat_exit()
 
-- актер перебрасывается в level_changer, возвращающий его на ЧАЭС, ко входу в бункер
db.actor:set_actor_position( vector():set(-13.26, 47.71, 46.57) )
end
 
function refuze_o_sozn()
if (not has_alife_info("freeplay_activated2")) then
create_level_changer(21410, vector():set(946.872, 6.0, 167.66), 240852, 2637,
2280,
472710,
vector():set( 1062.15, -0.0982, -3.512 ),
vector():set( 0.0 , 0.0 , -1.0 ),
"L12_Stancia",
1)
 
db.actor:give_info_portion("freeplay_activated2")
end
 
-- создается переход из ЧАЭС в Припять
create_chaes2pripyat_exit()
 
-- актер перебрасывается в level_changer, возвращающий его к правым воротам ЧАЭС
db.actor:set_actor_position( vector():set(946.872, 6.0, 167.66) )
end
 
function create_chaes2pripyat_exit()
-- создается переход из ЧАЭС в Припять
if (not has_alife_info( "exit_chaes2pripyat_created" )) then
create_level_changer(31410, vector():set( 917.35, 0.419, -316.35 ), 403866, 2401,
2117,
73868,
vector():set( 31.3, 3.0, 240.0 ),
vector():set( 0.0, 0.0, -1.0 ),
"L11_Pripyat",
0)
 
db.actor:give_info_portion("exit_chaes2pripyat_created")
end
end

Функцию exit_monolit я создал исключительно для тестирования, но решил оставить и тут. Вдруг кто-то захочет реализовать более сложный возврат: Меченого грузят в "грузовик смерти" и он снова приходит в себя на кордоне...

Функция exit_monolit создает «тихий» переход на уровень ЧАЭС-1 и обычный - в начале уровня ЧАЭС-1 для возврата в Припять, после чего перебрасывает актера прямо внутрь созданного перехода. Функция refuze_o_sozn делает тоже самое, только игрок появляется перед воротами в правом верхем углу карты (мне кажется, что так более логично). Все телепорты защищаются уникальными info_portions, дабы избежать их повторного создания, ведь игрок может захотеть «закончить» игру несколько раз.

Теперь подключение. Во-первых надо добавить новые info-portions. Я решил не изменять оригинальные файлы игры, а сделал для них (ну и для других тоже) отдельный файл config\gameplay\_info_sa.xml

следующего вида:

<?xml version="1.0" encoding="windows-1251" ?>
 
<game_information_portions>
 
<info_portion id="freeplay_activated1"></info_portion>
<info_portion id="freeplay_activated2"></info_portion>
<info_portion id="exit_chaes2pripyat_created"></info_portion>
 
</game_information_portions>

Теперь в этот файл можно будет добавлять новые info_portion, которые вы будете использовать в своих сюжетах. Подключается этот файл в system.ltx в секции «info_portions»:

[info_portions]
;список xml файлов, содержащих info_portions
files = _info_sa, info_portions, ....................

Кстати, именно так я рекомендую добавлять новые диалоги и новых персонажей. Это упростит процессы совмещения модов и аддонов.


Следующий шаг - подключение наших скриптов к игре. Для этого откройте файл config\ui\ui_movies.xml

найдите элементы «mov_desire_» (их пять, по количеству ложных концовок игры - все варианты общения с Монолитом). Внутри каждого элемента есть дочерние элементы «function_on_stop», которые задают функцию, запускающуюся сразу после ролика. Стандартное содержимое:

<function_on_stop>xr_effects.game_credits</function_on_stop>

Функция xr_effects.game_credits запускает финальные титры. Именно ее и нужно заменить на вызов нашей функции _freeplay_sa.exit_monolit. Вот что должно получиться:

<function_on_stop>_freeplay_sa.exit_monolit</function_on_stop>

Чуть ниже «mov_desire_5» находится тэг ролика для концовки «Присоединение к О-Сознанию». Его мы трогать не будем - Меченый станет медузой. А вот после него - тэг для ролика «Отказ от О-Сознания»: «mov_refuse_osoznanie». Функцию завершения в нем заменим следующим образом:

<function_on_stop>_freeplay_sa.refuze_o_sozn</function_on_stop>

И последнее: переход ЧАЭС1-Припять необходимо отметить на карте. Для этого существует вполне «легальный» механизм. Находим файл level_tasks.script и в конце функции add_lchanger_location дописываем следующее:

-- aes (++16.03.2008 by SA):
obj = sim:story_object(31410)
if obj then
level.map_add_object_spot(obj.id, "level_changer", "To Pripyat")
end

Здесь 31410 - story_id нашего level_changer, который создается нашей функцией create_chaes2pripyat_exit.

На этом пока все (мелкие огрехи типа направления взгляда игрока после смены уровня исправлю позже). Прошу тестировать и дополнять.

С уважением, sarthur.

Телепорты

В каталоге gamedata\scripts\ Создадим файл bind_mteleport.script с логикой работы нашего телепорта. -- ************************************************ -- ** Imp ** -- ** Биндер самодельных телепортов ** -- ** Поддерживает работу самопальных телепортов ** -- ************************************************

local teleport_binders ={} -- Список телепортов

function abs_comp(a,b) -- Служебная функция вычисления разности if( a < b) then return (b - a) else return (a - b) end end

function teleportate(x,y,z) -- Функция телепортации local a = vector() -- Задаем координаты a.x = x a.y = y a.z = z

-- Сама телепортация db.actor:set_actor_position(a)

-- Звуковое сопровождение local snd_obj = xr_sound.get_safe_sound_object(affects\tinnitus3a) snd_obj:play_no_feedback(db.actor, sound_object.s2d, 0, vector(), 1.0)

end


function actor_update(delta) local i,v,acter_poz,s

-- Получим позицию актера (что-бы каждый раз не запрашивать) acter_poz = db.actor:position()

-- Проверяем наши телепорты for i, v in pairs(teleport_binders) do s = v.parametrs

local obj = level.object_by_id( i ) if obj ~= nil then -- Наш телепорт в онлайне проверяем дальше if s.teleporte ~= nil and s.teleporte ~= false then -- Телепорт запущен if ( time_global() <= s.time ) then -- Если время отведенное на показ спецэфектов -- прошло, производим телепортацию teleportate(s.poz_x,s.poz_y,s.poz_z) if s.rotate ~= nil then db.actor:set_actor_direction(s.rotate) end s.teleporte = false end return end

-- Пороверим не забрел-ли актер в наш телепорт if (abs_comp(s.x, acter_poz.x)< v.parametrs.radius and abs_comp(s.z, acter_poz.z)< v.parametrs.radius and abs_comp(s.y, acter_poz.y)< v.parametrs.z_radius) then -- Актер в зоне действия телепорта, запустим телепорт s["teleporte"] = true s["time"] = time_global() + 500

-- Запускаем спецэфекты телепортации level.add_pp_effector ("teleport.ppe", 2006, false) end end end end

function bind( obj ) obj:bind_object( restrictor_teleport( obj ) ) end


class "restrictor_teleport" ( object_binder )

function restrictor_teleport:__init(obj, char_ini) super(obj) end

function restrictor_teleport:net_spawn(data) local char_ini = system_ini()

-- Если это телепорт то занесем его в специальный список телепортов if self.teleport == true then teleport_binders[self.object:id()] = self

-- Заполним таблицу параметров self["parametrs"] = {} if char_ini:line_exist(self.section, "radius") then self.parametrs["radius"] = tonumber(char_ini:r_string(self.section, "radius")) else self.parametrs["radius"] = 2 -- Дефолтный радиус по xy end if char_ini:line_exist(self.section, "z_radius") then self.parametrs["z_radius"] = tonumber(char_ini:r_string(self.section, "z_radius")) else self.parametrs["z_radius"] = self.parametrs["radius"] -- если радиус высоты не задан то задаем равным радиусу xy end

-- Запомним позицию что-бы каждый раз не считать local s_obj = alife():object(self.object:id()) self.parametrs["x"] = tonumber(s_obj.position.x); self.parametrs["y"] = tonumber(s_obj.position.y); self.parametrs["z"] = tonumber(s_obj.position.z);

-- Запомним координаты куда телепортимся self.parametrs["poz_x"] = tonumber(char_ini:r_string(self.section, "poz_x")) self.parametrs["poz_y"] = tonumber(char_ini:r_string(self.section, "poz_y")) self.parametrs["poz_z"] = tonumber(char_ini:r_string(self.section, "poz_z"))

if char_ini:line_exist(self.section, "rotate") then self.parametrs["rotate"] = tonumber(char_ini:r_string(self.section, "rotate")) end end return true end

function restrictor_teleport:net_destroy() -- Удаляем наш телепорт teleport_binders[self.object:id()] = nil self.parametrs = nil object_binder.net_destroy(self) end

function restrictor_teleport:reload(section) local char_ini = system_ini()

self.section = section -- Если это телепорт то if char_ini ~= nil and char_ini:line_exist(self.section, "teleport") then self["teleport"] = true end end


Для постоянного обновления нужно прицепить функцию actor_update() к биндеру актера, для чего в файле bind_stalker.script найдем функцию:

function actor_binder:update(delta)

В ней найдем вызов обновления рестрикторов bind_restrictor.actor_update(delta) под которым вставим строку с вызовом нашей функции обновления: bind_mteleport.actor_update(delta)

Все с программной частью закончили, теперь задаем данные телепорта.

В каталоге gamedata\config\misc открываем файл zone_teleport.ltx и в конце файла добавляем следующие строки описывающие конкретный телепорт: [m_teleport_1]:zone_teleport teleport = standart script_binding = bind_mteleport.bind

Параметры нашего телепорта

radius = 2

Высота захвата телепорта

z_radius = 2

Куда телепортируемся (телепортация всегда идет в пределах карты)

poz_x = 22.78 poz_y = 20.35 poz_z = 659.24

Угол зрения при появлении. Если параметра нет то не меняется.

rotate = 1.5

Параметры нашего телепорта: radius - на самом деле не радиус, а половина длинны стороны нашего квадрата (в начале я хотел сделать его кругом, но посчитал, что лучше не тратить процессорное врямя по пусту). Центром квадрата является точка респавна телепорта. z_radius - высота нашего телепорта. poz_x, poz_y, poz_z - координаты точки телепортации. rotate - Угол поворота после телепортации от оси X (я не разбирался в каких единицах задается, но 1.5 примерно равно 90 градусов). Если параметр удалить то будет оставатья угол под которым актер вошел в телепорт. Использование

Теперь с помощью create создадим наш телепорт: Пример: local obj local a = vector() a.x = -244.55 a.y = -19.46 a.z = -125.42 obj = alife():create("m_teleport_1",a,12829,8,65535)

С вами был vllzl

Другие места
LANGUAGE