Сповіщення
Очистити все

SoC. Спів точок переходу між рівнями


Ранг:
Майстер
Роль:
Гість
Записи:
752
Приєднався:
7 місяців тому
 

У цій статті спробую передати практичний досвід скриптового створення точок переходу між рівнями. Файл all.spawn не буде змінюватися, тому такий механізм більш зручний для суміщення модів і не вимагає початку нової гри.

Як приклад спробуємо реалізувати режим freeplay – продовження гри після знищення О-Свідомості. Для цього потрібно створити як мінімум дві точки — повернення на ЧАЕС-1 із внутрішнього дворика (після знищення О-Свідомості) та перехід із ЧАЕС-1 до Прип'яті. До речі, якщо повернутися на ЧАЕС-1, то там продовжуватиме діяти таймер "викиду", який врешті-решт включить "deadzone". Відключити лічильник мені поки не вдалося, зате можна легко відключити сам "викид" і досхочу побігати за рівнем.

 

Частина 1. Генерація працюючого LEVEL_CHANGER

Стандартної функції alife():create(…) недостатньо створення повноцінного level_changer.

Нижче я наводжу код функції, яка створює та ініціалізує level_changer (пишеться у створений нами файл _freeplay_sa.script ):

 
function create_level_changer ( 
	p_story_id,	 -- STORY_ID нового level_changer (потрібно нам пізніше) 
	p_position,	 -- вектор, координати точки, в якій буде розташовуватися центр нового level_changer 
	p_lvertex_id,	 
	--	 level_vertext_id - ідентифікують рівень, на якому буде створено level_chan   	
 
	p_dest_lv,	 -- level_vertex_id - ідентифікують рівень, на який level_changer буде перекидати гравця 
	p_dest_gv,	 -- game_vertex_id 
	p_dest_pos,	 -- координати точки, в якій на новому рівні виявиться гравець p_dest_dir 
	,	 
	--	 напрям погляду гравця p_dest_le 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: ) 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 ( ) 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 :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 ( ) 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_le : 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 ) ) -- 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 ( 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_dest_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, завжди 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
 

Її ви просто скопіюєте у наш файл. Змінювати нічого не треба.

Частина 2. Створення точок переходу

Тепер слід написати функції створення потрібних точок переходу та підключити їх до гри. Самі функції прості (теж у файл _freeplay_sa.script )

 
function refuze_o_sozn ( ) 
	if  ( no has_alife_info ( " freeplay_activated2 " ) )  then 
		create_level_changer ( 21410 , vector ( ) : 2280 , _ _ _ _ _ _ _
			 _ _
			 _ _
			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 _ _ 

 
function create_chaes2pripyat_exit ( )
	 -- створюється перехід з ЧАЕС до Прип'яти 
	if  ( not has_alife_info ( 	" exit_chaes2pripyat_created  " ) )  then 
		create_level_changer ( 31410 , vector ( ) : set (  917.35 ) . 866 , 2401  ,
			 73868 ,
			 2117 ,
			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
 

Функція refuze_o_sozn створює «тихий» перехід на рівень ЧАЕС-1 та звичайний – на початку рівня ЧАЕС-1 для повернення до Прип'яті, після чого перекидає гравця у новий перехід. Усі переходи захищаються унікальними info_portions, щоб уникнути їхнього повторного створення, адже гравець може захотіти «закінчити» гру кілька разів.

Тепер підключення. По-перше, треба додати нові info-portions. Я вирішив не змінювати оригінальні файли гри, а зробив для них (та й для інших теж) окремий файл

config\gameplay\_info_sa.xml

наступного виду:

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

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

[info_portions] 
;список xml файлів, що містять info_portions
files = _info_sa, info_portions, ....................

До речі, саме так я рекомендую додавати нові діалоги та нових персонажів. Це спростить процеси поєднання модів та аддонів.

Наступний крок – підключення наших скриптів до гри. Для цього відкрийте файл

config\ui\ui_movies.xml

Трохи нижче « mov_desire_5 » знаходиться тег ролика для закінчення «Приєднання до Свідомості». Його ми чіпати не будемо - Мічений стане медузою. А ось після нього – тег для ролика «Відмова від О-Свідомості»: « mov_refuse_osoznanie ». Функцію завершення в ньому замінимо наступним чином:

<function_on_stop>_freeplay_sa.refuze_o_sozn</function_on_stop>

Можна запускати і, якщо є збереження перед монолітом та/або свідомістю, тестувати. Перші переходи спрацьовують «тихо» - запит зміну рівня не видається (у параметрі p_silent задана 1). До переходу в Прип'ять можна встигнути добігти, доки не спрацював викид... Але це якось неправильно, викид треба зупинити (він же стався, поки ми були всередині станції). "По-чесному" (скриптом, без модифікації all.spawn ) таймер викиду відключити не вийде, зате викид можна придушити невеликим "хаком". Знайдіть файл xr_logic.script, а в ньому - функцію switch_to_section . Її потрібно модифікувати так:

 
-- Перемикання на вказану секцію, якщо встановлено. 
-- Якщо section == nil, залишається працювати стара секція. 
function switch_to_section ( npc, st, section ) 
  if section == nil  or section == ""  then 
    return  false 
  end
 
  -- 15.03.2008 by SA: 
  -- відключає "смертельні зони" на ЧАЕС після запуску режиму FREEPLAY 
  -- таймер не відключається, але сам "викид" не відбувається 
  if  ( section == "sr_aes_deadzone" )  then 
	if  ( has_alife_info ( "freeplay_activated " ) )  then 
		return  false 
	end 
  end
 
  ... далі без змін ...
end
 

Даний спосіб залишає таймер, що висить на нулях, і всі ефекти початку викиду, але сам « викид » відключається.

І останнє: перехід ЧАЕС1-Прип'ять слід зазначити на карті. І тому існує цілком «легальний» механізм. Знаходимо файл level_tasks.script і наприкінці функції add_lchanger_location дописуємо наступне:

-- aes 
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.

Відключення викиду на ЧАЕС-2

Відключення викиду на ЧАЕС-2 проводиться найпростішою модифікацією двох файлів - sr_aes_deadzone та xr_effects . Модифікуйте функцію в sr_aes_deadzone так:

 
function action_postprocess:update ( delta ) 
if  ( not has_alife_info ( " freeplay_activated2 " ) )  then 
	if xr_logic.try_switch_to_another_section ( self.object, self.st, db.actor )  then 
		return 
	end
 
	self.actor_inside = self.object:inside ( db.actor:position ( ) )
 
	--if self.actor_inside == true then 
	   --printf ("[%d]ACTOR IN DEAD ZONE !!!", time_global()) 
	--else 
	   --printf ("[%d]ACTOR NOT IN DEAD ZONE ! !!", time_global ())    
	--end 
	self:update_hit ( delta ) 
end 
end
 

Викид вимкнено. Землетрус працює окремо від викиду. Модифікуйте функцію xr_effects так:

 
-- постпроцес
 
	і вплив удару в морду function aes_earthshake ( npc ) 
if  ( not has_alife_info ( " freeplay_activated2 " ) )  then 
	local snd_obj = xr_sound . , 0 , vector ( ) , 1.0 ) 
	level.add_cam_effector ( "camera_effects \\ earthquake.anm" , 1974 , false , "" ) --set_postprocess ("scripts\\earthshake.ltx") end end
    


 

Цей спосіб не без недоліків – якщо ви хочете створювати у своєму моді викиди за допомогою цих скриптів і вони будуть у фріплеї – працювати вони не будуть.


   
Цитата