У цій статті спробую передати практичний досвід скриптового створення точок переходу між рівнями. Файл 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
Цей спосіб не без недоліків – якщо ви хочете створювати у своєму моді викиди за допомогою цих скриптів і вони будуть у фріплеї – працювати вони не будуть.