[Tutorial]: Skryptowy spawn NPC wraz z funkcją smart terrain'a

Tytułem wprowadzenia…
Wszystkim pasjonatom moddowania oczywistym jest fakt, zgodnie z którym - modyfikacja pliku all.spawn polegająca na wprowadzeniu nowych NPC’ów, mutantów, pojazdów, przedmiotów czy anomalii - wymusza konieczność rozpoczynania nowej rozgrywki by spawnowany obiekt pojawił się w świecie gry. Dodając jakikolwiek obiekt - operuje się w pod-plikach alife_*.ltx ( * - nazwa lokacji) uzyskanych w wyniku procesu dekompilacji z użyciem ACDC lub edycji tychże plików xrSpawner’em (edycja bez konieczności dekompilacji). Nastręcza to wiele trudności w sytuacji gdy chcemy dodać np. postać do gry przy jednoczesnej chęci zachowania aktualnego poziomu rozwoju fabuły oraz - zachowania przez dodaną postać - stałej pozycji jako konieczności determinującej jej przetrwanie (brak przypisanego smart_terrain’a). Analizując pliki Solijanki - odkryłem sposób w jaki dodawano postacie za pomocą skryptu, skrypt ten jednak - bo mowa o amk.script - jest tak rozbudowany i posiada tyle odniesień wymuszających w istocie konieczność dodania ponad 10 plików, że postanowiłem poszukać innego sposobu. Odkryłem, że kluczowe w skryptowym dodawaniu postaci są 2 funkcje: read_stalker_params oraz write_stalker_params , które znalazłem również w pliku xrs_utils.script. Jeden tylko plik umożliwia sczytywanie wartości parametrów odpowiedzialnych za koordynaty, profil postaci, schemat działania a także za ich zapis. Dzięki wykorzystaniu w/w pliku oraz funkcji, umożliwiającej spawn obiektu o zadanych zmiennych [np. story_id, nazwa postaci] - nie jesteśmy zmuszeni rozpoczynać nowej gry, ponieważ to nie all.spawn tworzy obiekt ze struktury alife_*.ltx , lecz skrypt. Jak można będzie się przekonać na prezentacji w końcowej części poradnika - re-kompilacja pliku all.spawn, w której wprowadzono sekcję odpowiedzialną za koordynaty, umożliwia skorzystanie z pliku all.spawn (savegame wykorzystujący wersję niezmienioną spawna) - pomimo jego modyfikacji. Modyfikacja bowiem dotyczyła pliku way_*.ltx , czyli części odpowiedzialnej za punkty patrolowe, kierunki patrzenia, centra obozowisk reprezentowane przez punkt, etc. Przeprowadziłem ok. 20 testów zarówno na wersji podstawowej Shoc 1.0004 - jak również na modyfikacji zawierającej nowe mapy. Testy dotyczyły sprawdzania reakcji NPC na ostrzał, rzuty granatem, ataki mutantów, zmianę lokacji (i powrót w m-ce spawnowanych jednostek), wczytywanie zapisu gry w różnym momencie rozgrywki - za każdym razem dodane postacie utrzymywały pozycję. Metoda umożliwia tworzenie pseudo-smart terrain’a. Dlaczego pseudo? - ponieważ nie jest to obiekt o cechach spełniających kryteria parametryzacji dla smart_terrain’a - definiowanego w alife_*.ltx , lecz punkt odniesienia wykorzystywany przez dodane jednostki (spełnia funkcję smart_terrain’a), bez niego - dodane postacie rozeszłyby się na mapie, ginąc od anomalii, ataków wroga czy mutantów. Dzięki tej metodzie - możemy modyfikować każdą wersję stalkera (oraz mody), w której możliwa jest dekompilacja spawn’a, ponieważ znaczenie ma tutaj dodanie punktu odniesienia do pliku(ów) way_*.ltx. Warunkiem jej stosowania jest operowanie wyłącznie w pod-plikach way_*.ltx - all.spawn’a - mowa o sytuacji, gdy chcemy dodać postać do gry - bez jej ponownego rozpoczynania.
Po krótkim wstępie - przejdźmy do clou poradnika.

 

Tutorial dla Shoc oraz modów z możliwością dekompilacji pliku all.spawn

 

 

I. Etap przygotowawczy - zbieramy punkty terenowe.

 

Moim zamiarem jest dodanie 3 postaci do gry, które zajmować będą określoną pozycję - za przykład obiorę schemat “ogniska” czyli sytuacji w której stalkerzy skupiają się wokół centralnego punktu. Potrzebuję koordynatów dla 3 NPC’ów i omawianego punktu.

_ Uwaga _: Ilość punktów zależy od obranego schematu tj. jeśli moim zamiarem jest np. stworzenie trasy patrolowej dla 1 jednostki - składającej się z 10 waypoint’ów, muszę zebrać 11 współrzędnych (NPC + punkty trasy). Przydatna jest tutaj znajomość budowy danego schematu, ponieważ często schemat (np. snajpera) wymaga dodatkowo zdefiniowania kierunków patrzenia. Schematy pracy stalkerów to temat osobnego poradnika. Najprostszym - bo wymagającym dodania tylko jednego punktu - jest obrane za przykład “ognisko”. Wracając do naszego przykładu - zbieram 4 potrzebne punkty - korzystając z narzędzia " Smartterrain and Waypoint Tools by dez0wave" - dostępnego pod tym linkiem. (opis znajdziemy tutaj: link). Gdyby doszło do sytuacji, iż link będzie nieaktywny - omawiane narzędzie znajdziemy tutaj w postaci załącznika. Poniżej wyciąg współrzędnych z loga gry (koordynaty w wiosce nowicjuszy na Kordonie):

_! Unknown command:  patrolName=spawn_1|anim=stalker_1|pos=-200.4694519043,-20.58443069458,-148.60520935059|game=46|level=52314* Log file has been saved successfully!! Unknown command:  patrolName=spawn_2|anim=stalker_2|pos=-202.69140625,-20.110641479492,-143.33158874512|game=57|level=49764* Log file has been saved successfully!! Unknown command:  patrolName=spawn_3|anim=stalker_3|pos=-204.28082275391,-20.361850738525,-147.43516540527|game=46|level=48478* Log file has been saved successfully!! Unknown command:  patrolName=spawn_4|anim=xyz_point|pos=-207.013442993164,-20.4290790557861,-169.828475952148|game=50|level=45902_

Uzyskawszy potrzebne punkty terenowe - przechodzimy do części wykonawczej.

Alternatywne metody uzyskiwania współrzędnych opisuje ten temat, zawierający linki do narzędzi dla wszystkich wersji Stalkera, sekcja: часть 1.

LVID GVID Script SoC

 

 

II. Etap wykonawczy - tworzenie struktury moda oraz edycja plików.

 

 

Na początek skupimy się na oskryptowaniu modyfikacji.

  • Tworzymy lub kopiujemy (patrz załącznik) - plik xrs_utils.script do folderu skryptów stalkera [gamedatascripts]. Poniżej plik do pobrania lub zawartość do skopiowania (gdyby doszło do usunięcia załącznika). Jeśli mamy zamiar umieścić zawartość w pliku - pamiętajmy by nosił nazwę xrs_utils i rozszerzenie .script , ponieważ jest to nazwa wykorzystywana w funkcji spawna (opis pkt. 2 ). Istotne są tutaj funkcje: read_stalker_params oraz write_stalker_params. Plik powyższy występuje w wielu modach, w których może się różnić kilkoma sekcjami - dlatego podałem funkcje, które są b. ważne dla działania modyfikacji. Warto sprawdzić ich obecność w przypadku kompilacji z modami zawierającymi ów plik. xrs_utils.7z (3.77 KB)

Treść skryptu poniżej :

– Autor: xStreamfunction readvu32u8(packet) local v={} local len=packet:r_s32() for i=1,len,1 do table.insert(v,packet:r_u8()) end return vendfunction readvu8u8(packet) local v={} local len=8 for i=1,len,1 do table.insert(v,packet:r_u8()) end return vendfunction readvu32u16(packet) local v={} local len=packet:r_s32() for i=1,len,1 do table.insert(v,packet:r_u16()) end return vendfunction writevu32u8(pk,v) local len=table.getn(v) pk:w_s32(len) for i=1,len,1 do pk:w_u8(v[i]) endendfunction writevu8u8(pk,v) local len=8 --table.getn(v) for i=1,len,1 do pk:w_u8(v[i]) endendfunction writevu32u16(pk,v) local len=table.getn(v) pk:w_s32(len) for i=1,len,1 do pk:w_u16(v[i]) endendfunction parse_object_packet(ret,stpk,updpk) ret.gvid=stpk:r_u16() ret.obf32u1=stpk:r_float() ret.obs32u2=stpk:r_s32() ret.lvid=stpk:r_s32() ret.oflags=stpk:r_s32() ret.custom=stpk:r_stringZ() ret.sid=stpk:r_s32() ret.obs32u3=stpk:r_s32() return retendfunction fill_object_packet(ret,stpk,updpk) stpk:w_u16(ret.gvid) stpk:w_float(ret.obf32u1) stpk:w_s32(ret.obs32u2) stpk:w_s32(ret.lvid) stpk:w_s32(ret.oflags) stpk:w_stringZ(ret.custom) stpk:w_s32(ret.sid) stpk:w_s32(ret.obs32u3)endfunction parse_visual_packet(ret,stpk,updpk) ret.visual=stpk:r_stringZ() ret.vsu8u1=stpk:r_u8() return retendfunction fill_visual_packet(ret,stpk,updpk) stpk:w_stringZ(ret.visual) stpk:w_u8(ret.vsu8u1)endfunction parse_dynamic_object_visual(ret,stpk,updpk) parse_object_packet(ret,stpk,updpk) parse_visual_packet(ret,stpk,updpk) return retendfunction fill_dynamic_object_visual(ret,stpk,updpk) fill_object_packet(ret,stpk,updpk) fill_visual_packet(ret,stpk,updpk)endfunction parse_creature_packet(ret,stpk,updpk) parse_dynamic_object_visual(ret,stpk,updpk) ret.team=stpk:r_u8() ret.squad=stpk:r_u8() ret.group=stpk:r_u8() ret.health=stpk:r_float() ret.crvu32u16u1=readvu32u16(stpk) ret.crvu32u16u2=readvu32u16(stpk) ret.killerid=stpk:r_u16() ret.game_death_time=readvu8u8(stpk) ret.updhealth=updpk:r_float() ret.upds32u1=updpk:r_s32() ret.updu8u2=updpk:r_u8() ret.updpos={} – čëč ďîńňŕâčňü âĺęňîđ? ëŕäíî ďîňîě ret.updpos.x=updpk:r_float() ret.updpos.y=updpk:r_float() ret.updpos.z=updpk:r_float() ret.updmodel=updpk:r_float() ret.upddir={} ret.upddir.x=updpk:r_float() ret.upddir.y=updpk:r_float() ret.upddir.z=updpk:r_float() ret.updteam=updpk:r_u8() ret.updsquad=updpk:r_u8() ret.updgroup=updpk:r_u8() return retendfunction fill_creature_packet(ret,stpk,updpk) fill_dynamic_object_visual(ret,stpk,updpk) stpk:w_u8(ret.team) stpk:w_u8(ret.squad) stpk:w_u8(ret.group) stpk:w_float(ret.health) writevu32u16(stpk,ret.crvu32u16u1) writevu32u16(stpk,ret.crvu32u16u2) stpk:w_u16(ret.killerid) writevu8u8(stpk,ret.game_death_time) updpk:w_float(ret.updhealth) updpk:w_s32(ret.upds32u1) updpk:w_u8(ret.updu8u2) updpk:w_float(ret.updpos.x) updpk:w_float(ret.updpos.y) updpk:w_float(ret.updpos.z) updpk:w_float(ret.updmodel) updpk:w_float(ret.upddir.x) updpk:w_float(ret.upddir.y) updpk:w_float(ret.upddir.z) updpk:w_u8(ret.updteam) updpk:w_u8(ret.updsquad) updpk:w_u8(ret.updgroup)endfunction parse_monster_packet(ret,stpk,updpk) parse_creature_packet(ret,stpk,updpk) ret.baseoutr=stpk:r_stringZ() ret.baseinr=stpk:r_stringZ() ret.smtrid=stpk:r_u16() ret.smtrtaskactive=stpk:r_u8() ret.updu16u1=updpk:r_u16() ret.updu16u2=updpk:r_u16() ret.upds32u3=updpk:r_s32() ret.upds32u4=updpk:r_s32() return retendfunction fill_monster_packet(ret,stpk,updpk) fill_creature_packet(ret,stpk,updpk) stpk:w_stringZ(ret.baseoutr) stpk:w_stringZ(ret.baseinr) stpk:w_u16(ret.smtrid) stpk:w_u8(ret.smtrtaskactive) updpk:w_u16(ret.updu16u1) updpk:w_u16(ret.updu16u2) updpk:w_s32(ret.upds32u3) updpk:w_s32(ret.upds32u4)endfunction parse_trader_packet(ret,stpk,updpk) ret.money=stpk:r_s32() ret.profile=stpk:r_stringZ() ret.infammo=stpk:r_s32() ret.class=stpk:r_stringZ() ret.communityid=stpk:r_s32() ret.rank=stpk:r_s32() ret.reputation=stpk:r_s32() ret.charname=stpk:r_stringZ() return retendfunction fill_trader_packet(ret,stpk,updpk) stpk:w_s32(ret.money) stpk:w_stringZ(ret.profile) stpk:w_s32(ret.infammo) stpk:w_stringZ(ret.class) stpk:w_s32(ret.communityid) stpk:w_s32(ret.rank) stpk:w_s32(ret.reputation) stpk:w_stringZ(ret.charname)endfunction parse_human_packet(ret,stpk,updpk) parse_trader_packet(ret,stpk,updpk) parse_monster_packet(ret,stpk,updpk) ret.huvu32u8u1=readvu32u8(stpk) ret.huvu32u8u2=readvu32u8(stpk) return retendfunction fill_human_packet(ret,stpk,updpk) fill_trader_packet(ret,stpk,updpk) fill_monster_packet(ret,stpk,updpk) writevu32u8(stpk,ret.huvu32u8u1) writevu32u8(stpk,ret.huvu32u8u2)endfunction parse_skeleton_packet(ret,stpk,updpk) ret.skeleton=stpk:r_stringZ() ret.skeleton_flags=stpk:r_u8() ret.source_id=stpk:r_u16() – ret.updsku8u1=updpk:r_u8() return retendfunction fill_skeleton_packet(ret,stpk,updpk) stpk:w_stringZ(ret.skeleton) stpk:w_u8(ret.skeleton_flags) stpk:w_u16(ret.source_id) – updpk:w_u8(ret.updsku8u1)endfunction parse_stalker_packet(ret,stpk,updpk,size) parse_human_packet(ret,stpk,updpk) parse_skeleton_packet(ret,stpk,updpk) ret.hellodlg=updpk:r_stringZ() ret.stunk1={} for i=stpk:r_tell(),size-1,1 do table.insert(ret.stunk1,stpk:r_u8()) end return retendfunction fill_stalker_packet(ret,stpk,updpk) fill_human_packet(ret,stpk,updpk) fill_skeleton_packet(ret,stpk,updpk) updpk:w_stringZ(ret.hellodlg) for i,v in ipairs(ret.stunk1) do stpk:w_u8(v) endendfunction parse_se_monster_packet(ret,stpk,updpk,size) parse_monster_packet(ret,stpk,updpk,size) parse_skeleton_packet(ret,stpk,updpk,size) ret.spec_obj_id=stpk:r_u16() ret.job_online=stpk:r_u8() if ret.job_online>3 then ret.state=true ret.job_online=ret.job_online-4 else ret.state=false end if ret.job_online==3 then ret.job_online_condlist=stpk:r_stringZ() end ret.was_in_smtr=stpk:r_u8() ret.stunk1={} for i=stpk:r_tell(),size-1,1 do table.insert(ret.stunk1,stpk:r_u8()) end return retendfunction fill_se_monster_packet(ret,stpk,updpk) fill_monster_packet(ret,stpk,updpk) fill_skeleton_packet(ret,stpk,updpk) stpk:w_u16(ret.spec_obj_id) local st=0 if ret.state then st=4 end stpk:w_u8(ret.job_online+st) if ret.job_online==3 then stpk:w_stringZ(ret.job_online_condlist) end stpk:w_u8(ret.was_in_smtr) for i,v in ipairs(ret.stunk1) do stpk:w_u8(v) end endfunction read_stalker_params(sobj) local stpk=net_packet() local uppk=net_packet() sobj:STATE_Write(stpk) sobj:UPDATE_Write(uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) local tbl=xrs_utils.parse_stalker_packet({},stpk,uppk,size) return tblendfunction read_monster_params(sobj) local stpk=net_packet() local uppk=net_packet() sobj:STATE_Write(stpk) sobj:UPDATE_Write(uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) local tbl=xrs_utils.parse_se_monster_packet({},stpk,uppk,size) return tblend-- ňŕáëčöŕ ďŕđŕěĺňđîâ č ńĺđâĺđíűé îáúĺęň íŕ âőîäĺfunction write_stalker_params(tbl,sobj) local stpk=net_packet() local uppk=net_packet() xrs_utils.fill_stalker_packet(tbl,stpk,uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) sobj:STATE_Read(stpk,size) sobj:UPDATE_Read(uppk)endfunction write_monster_params(tbl,sobj) local stpk=net_packet() local uppk=net_packet() xrs_utils.fill_se_monster_packet(tbl,stpk,uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) sobj:STATE_Read(stpk,size) sobj:UPDATE_Read(uppk)endfunction get_anomaly_data(sobj) local stpk=net_packet() local uppk=net_packet() sobj:STATE_Write(stpk) sobj:UPDATE_Write(uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) local t={} xrs_utils.parse_object_packet(t,stpk,uppk,size) xrs_utils.parse_shape_packet(t,stpk,uppk,size) t.restrictor_type = stpk:r_u8() t.max_power = stpk:r_float() t.owner_id = stpk:r_s32() t.enabled_time = stpk:r_s32() t.disabled_time = stpk:r_s32() t.start_time_shift = stpk:r_s32() t.offline_interactive_radius = stpk:r_float() t.artefact_spawn_count = stpk:r_u16() t.artefact_position_offset = stpk:r_s32() t.last_spawn_time_present = stpk:r_u8() if stpk:r_elapsed() ~= 0 then end return tendfunction set_anomaly_data(t,sobj) local stpk=net_packet() local uppk=net_packet() xrs_utils.fill_object_packet(t,stpk,uppk) xrs_utils.fill_shape_packet(t,stpk,uppk) stpk:w_u8(t.restrictor_type) stpk:w_float(t.max_power) stpk:w_s32(t.owner_id) stpk:w_s32(t.enabled_time) stpk:w_s32(t.disabled_time) stpk:w_s32(t.start_time_shift) stpk:w_float(t.offline_interactive_radius) stpk:w_u16(t.artefact_spawn_count) stpk:w_s32(t.artefact_position_offset) stpk:w_u8(t.last_spawn_time_present) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) sobj:STATE_Read(stpk,size) sobj:UPDATE_Read(uppk)endfunction get_lc_data(obj) local packet = net_packet() obj:STATE_Write(packet) local t={} t.game_vertex_id = packet:r_u16() t.distance = packet:r_float() t.direct_control = packet:r_s32() t.level_vertex_id = packet:r_s32() t.object_flags = packet:r_s32() t.custom_data = packet:r_stringZ() t.story_id = packet:r_s32() t.spawn_story_id = packet:r_s32() t = xrs_utils.parse_shape_packet(t,packet) t.restrictor_type = packet:r_u8() t.dest_game_vertex_id = packet:r_u16() t.dest_level_vertex_id = packet:r_s32() t.dest_position = packet:r_vec3() t.dest_direction = packet:r_vec3() t.dest_level_name = packet:r_stringZ() t.dest_graph_point = packet:r_stringZ() t.silent_mode = packet:r_u8() if packet:r_elapsed() ~= 0 then abort(“left=%d”, packet:r_elapsed()) end return tendfunction set_lc_data(t,obj) local packet = net_packet() obj:STATE_Write(packet) packet:w_begin(t.game_vertex_id) packet:w_float(t.distance) packet:w_s32(t.direct_control) packet:w_s32(t.level_vertex_id) packet:w_s32(t.object_flags) packet:w_stringZ(t.custom_data) packet:w_s32(t.story_id) packet:w_s32(t.spawn_story_id) xrs_utils.fill_shape_packet(t,packet) packet:w_u8(t.restrictor_type) packet:w_u16(t.dest_game_vertex_id) packet:w_s32(t.dest_level_vertex_id) packet:w_vec3(t.dest_position) packet:w_vec3(t.dest_direction) packet:w_stringZ(t.dest_level_name) packet:w_stringZ(t.dest_graph_point) packet:w_u8(t.silent_mode) packet:r_seek(0) obj:STATE_Read(packet, packet:w_tell())endfunction parse_object_physic_packet(ret,stpk,updpk) ret.physic_type=stpk:r_s32() ret.mass=stpk:r_float() ret.fixed_bones=stpk:r_stringZ() return retendfunction fill_object_physic_packet(ret,stpk,updpk) stpk:w_s32(ret.physic_type) stpk:w_float(ret.mass) stpk:w_stringZ(ret.fixed_bones)endfunction get_breakable_data(sobj) local stpk=net_packet() local uppk=net_packet() sobj:STATE_Write(stpk) sobj:UPDATE_Write(uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) local t={} xrs_utils.parse_object_packet(t,stpk,uppk,size) xrs_utils.parse_visual_packet(t,stpk,uppk,size) xrs_utils.parse_object_physic_packet(t,stpk,uppk,size) return tendfunction set_breakable_data(t,sobj) local stpk=net_packet() local uppk=net_packet() xrs_utils.fill_object_packet(t,stpk,uppk) xrs_utils.fill_visual_packet(t,stpk,uppk) xrs_utils.fill_object_physic_packet(t,stpk,uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) sobj:STATE_Read(stpk,size) sobj:UPDATE_Read(uppk)endfunction get_spawner_data(sobj) local stpk=net_packet() local uppk=net_packet() sobj:STATE_Write(stpk) sobj:UPDATE_Write(uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) local t={} xrs_utils.parse_object_packet(t,stpk,uppk,size) xrs_utils.parse_shape_packet(t,stpk,uppk,size) t.restrictor_type = stpk:r_u8() t.spawned_obj_count = stpk:r_u8() return tendfunction set_spawner_data(t,sobj) local stpk=net_packet() local uppk=net_packet() xrs_utils.fill_object_packet(t,stpk,uppk) xrs_utils.fill_shape_packet(t,stpk,uppk) stpk:w_u8(t.restrictor_type) stpk:w_u8(t.spawned_obj_count) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) sobj:STATE_Read(stpk,size) sobj:UPDATE_Read(uppk)endfunction parse_shape_packet(t,stpk,uppk) local shape_count = stpk:r_u8() t.shapes={} for i=1,shape_count do local shape_type = stpk:r_u8() t.shapes[i]={} t.shapes[i].shtype=shape_type if shape_type == 0 then – sphere t.shapes[i].center = stpk:r_vec3() t.shapes[i].radius = stpk:r_float() else – box t.shapes[i].v1 = stpk:r_vec3() t.shapes[i].v2 = stpk:r_vec3() t.shapes[i].v3 = stpk:r_vec3() t.shapes[i].offset = stpk:r_vec3() end endendfunction fill_shape_packet(t,stpk,updpk) stpk:w_u8(table.getn(t.shapes)) for i=1,table.getn(t.shapes) do stpk:w_u8(t.shapes[i].shtype) if t.shapes[i].shtype == 0 then stpk:w_vec3(t.shapes[i].center) stpk:w_float(t.shapes[i].radius) else stpk:w_vec3(t.shapes[i].v1) stpk:w_vec3(t.shapes[i].v2) stpk:w_vec3(t.shapes[i].v3) stpk:w_vec3(t.shapes[i].offset) end endendfunction get_restrictor_data(sobj) local stpk=net_packet() local uppk=net_packet() sobj:STATE_Write(stpk) sobj:UPDATE_Write(uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) local t={} xrs_utils.parse_object_packet(t,stpk,uppk,size) xrs_utils.parse_shape_packet(t,stpk,uppk,size) t.restrictor_type = stpk:r_u8() return tendfunction set_restrictor_data(t,sobj) local stpk=net_packet() local uppk=net_packet() xrs_utils.fill_object_packet(t,stpk,uppk) xrs_utils.fill_shape_packet(t,stpk,uppk) stpk:w_u8(t.restrictor_type) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) sobj:STATE_Read(stpk,size) sobj:UPDATE_Read(uppk)endfunction get_trader_data(sobj) local stpk=net_packet() local uppk=net_packet() sobj:STATE_Write(stpk) sobj:UPDATE_Write(uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) local t={} xrs_utils.parse_object_packet(t,stpk,uppk,size) xrs_utils.parse_visual_packet(t,stpk,uppk,size) xrs_utils.parse_trader_packet(t,stpk,uppk,size) return tendfunction set_trader_data(t,sobj) local stpk=net_packet() local uppk=net_packet() xrs_utils.fill_object_packet(t,stpk,uppk) xrs_utils.fill_visual_packet(t,stpk,uppk) xrs_utils.fill_trader_packet(t,stpk,uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) sobj:STATE_Read(stpk,size) sobj:UPDATE_Read(uppk)endfunction get_invbox_data(sobj) local stpk=net_packet() local uppk=net_packet() sobj:STATE_Write(stpk) sobj:UPDATE_Write(uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) local t={} xrs_utils.parse_object_packet(t,stpk,uppk,size) xrs_utils.parse_visual_packet(t,stpk,uppk,size) return tendfunction set_invbox_data(t,sobj) local stpk=net_packet() local uppk=net_packet() xrs_utils.fill_object_packet(t,stpk,uppk) xrs_utils.fill_visual_packet(t,stpk,uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) sobj:STATE_Read(stpk,size) sobj:UPDATE_Read(uppk)endfunction readvu8uN(packet,n) local v={} for i=1,n,1 do table.insert(v,packet:r_u8()) end return vendfunction writevu8uN(pk,v) local len=table.getn(v) for i=1,len,1 do pk:w_u8(v[i]) endendfunction parse_item_packet(ret,stpk,updpk) ret.condition=stpk:r_float() ret.updnum_items=updpk:r_u8() ret.updpos={} ret.updpos.x=updpk:r_float() ret.updpos.y=updpk:r_float() ret.updpos.z=updpk:r_float() ret.updcse_alife_item__unk1_q8v4=readvu8uN(updpk,4) ret.updcse_alife_item__unk2_q8v3=readvu8uN(updpk,3) ret.updcse_alife_item__unk3_q8v3=readvu8uN(updpk,3) return retendfunction fill_item_packet(ret,stpk,updpk) stpk:w_float(ret.condition) updpk:w_u8(ret.updnum_items) updpk:w_float(ret.updpos.x) updpk:w_float(ret.updpos.y) updpk:w_float(ret.updpos.z) writevu8uN(updpk,ret.updcse_alife_item__unk1_q8v4) writevu8uN(updpk,ret.updcse_alife_item__unk2_q8v3) writevu8uN(updpk,ret.updcse_alife_item__unk3_q8v3) return retendfunction parse_item_ammo_packet(ret,stpk,updpk) ret.ammo_left=stpk:r_u16() ret.updammo_left=updpk:r_u16() return retendfunction fill_item_ammo_packet(ret,stpk,updpk) stpk:w_u16(ret.ammo_left) updpk:w_u16(ret.updammo_left) return retendfunction get_ammo_params(sobj) local stpk=net_packet() local uppk=net_packet() sobj:STATE_Write(stpk) sobj:UPDATE_Write(uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) local t={} xrs_utils.parse_object_packet(t,stpk,uppk,size) xrs_utils.parse_visual_packet(t,stpk,uppk,size) xrs_utils.parse_item_packet(t,stpk,uppk,size) xrs_utils.parse_item_ammo_packet(t,stpk,uppk,size) return tendfunction set_ammo_data(t,sobj) local stpk=net_packet() local uppk=net_packet() xrs_utils.fill_object_packet(t,stpk,uppk) xrs_utils.fill_visual_packet(t,stpk,uppk) xrs_utils.fill_item_packet(t,stpk,uppk) xrs_utils.fill_item_ammo_packet(t,stpk,uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) sobj:STATE_Read(stpk,size) sobj:UPDATE_Read(uppk)endfunction get_destroyable_data(sobj) local stpk=net_packet() local uppk=net_packet() sobj:STATE_Write(stpk) sobj:UPDATE_Write(uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) local t={} xrs_utils.parse_object_packet(t,stpk,uppk,size) xrs_utils.parse_visual_packet(t,stpk,uppk,size) xrs_utils.parse_skeleton_packet(t,stpk,uppk,size) xrs_utils.parse_object_physic_packet(t,stpk,uppk,size) return tendfunction set_destroyable_data(t,sobj) local stpk=net_packet() local uppk=net_packet() xrs_utils.fill_object_packet(t,stpk,uppk) xrs_utils.fill_visual_packet(t,stpk,uppk) xrs_utils.fill_skeleton_packet(t,stpk,uppk) xrs_utils.fill_object_physic_packet(t,stpk,uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) sobj:STATE_Read(stpk,size) sobj:UPDATE_Read(uppk)endfunction get_weapon_data(sobj) local stpk=net_packet() local uppk=net_packet() sobj:STATE_Write(stpk) sobj:UPDATE_Write(uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) local t={} xrs_utils.parse_object_packet(t,stpk,uppk,size) xrs_utils.parse_visual_packet(t,stpk,uppk,size) xrs_utils.parse_item_packet(t,stpk,uppk,size) xrs_utils.parse_item_weapon_packet(t,stpk,uppk,size) return tendfunction set_weapon_data(t,sobj) local stpk=net_packet() local uppk=net_packet() xrs_utils.fill_object_packet(t,stpk,uppk) xrs_utils.fill_visual_packet(t,stpk,uppk) xrs_utils.fill_item_packet(t,stpk,uppk) xrs_utils.fill_item_weapon_packet(t,stpk,uppk) local size=stpk:w_tell() local size1=uppk:w_tell() stpk:r_seek(0) uppk:r_seek(0) sobj:STATE_Read(stpk,size) sobj:UPDATE_Read(uppk)endfunction parse_item_weapon_packet(ret,stpk,updpk) ret.ammo_current = stpk:r_u16() ret.ammo_elapsed = stpk:r_u16() ret.weapon_state = stpk:r_u8() ret.addon_flags = stpk:r_u8() ret.ammo_type = stpk:r_u8() ret.updcondition = updpk:r_u8() ret.updweapon_flags = updpk:r_u8() ret.updammo_elapsed = updpk:r_u16() ret.updaddon_flags = updpk:r_u8() ret.updammo_type = updpk:r_u8() ret.updweapon_state = updpk:r_u8() ret.updweapon_zoom = updpk:r_u8() ret.updcurrent_fire_mode = updpk:r_u8() return retendfunction fill_item_weapon_packet(ret,stpk,updpk) stpk:w_u16(ret.ammo_current) stpk:w_u16(ret.ammo_elapsed) stpk:w_u8(ret.weapon_state) stpk:w_u8(ret.addon_flags) stpk:w_u8(ret.ammo_type) updpk:w_u8(ret.updcondition) updpk:w_u8(ret.updweapon_flags) updpk:w_u16(ret.updammo_elapsed) updpk:w_u8(ret.updaddon_flags) updpk:w_u8(ret.updammo_type) updpk:w_u8(ret.updweapon_state) updpk:w_u8(ret.updweapon_zoom) updpk:w_u8(ret.updcurrent_fire_mode) return retend-------------------------------------function parse_ini_section_to_array(ini,section) local tmp={} if ini:section_exist(section) then local result, id, value = nil, nil, nil for a=0,ini:line_count(section)-1 do result, id, value = ini:r_line(section,a,“”,“”) if id~=nil and trim(id)~=“” and trim(id)~=nil then tmp[trim(id)]=trim(value) end end else dbglog(“xrs_utils.parse_ini_section_to_array: no such section: “..tostring(section)) end return tmpendfunction str_explode(div,str,clear) local t={} local cpt = string.find (str, div, 1, true) if cpt then repeat if clear then table.insert( t, trim(string.sub(str, 1, cpt-1)) ) else table.insert( t, string.sub(str, 1, cpt-1) ) end str = string.sub( str, cpt+string.len(div) ) cpt = string.find (str, div, 1, true) until cpt==nil end if clear then table.insert(t, trim(str)) else table.insert(t, str) end return tendfunction quotemeta(str) return (string.gsub(s, “[%^%$%(%)%%%.%[%]%%+%-%?]“, “%%%1”))end–äë˙ ďđŕâčëüíîăî ďŕđńčíăŕ çŕďđĺůĺíű ęîěěĺíňŕđčč!!!function parse_custom_data(str) local t={} if str then for section, section_data in string.gfind(str,”%s%[([^%]])%]%s([^%[%z])%s”) do section = trim(section) t[section]={} for line in string.gfind(trim(section_data), “([^n])n”) do if string.find(line,”=”)~=nil then for k, v in string.gfind(line, “([^=]-)%s*=%s*(.)") do k = trim(k) if k~=nil and k~=‘’ and v~=nil then t[section][k]=trim(v) end end else for k, v in string.gfind(line, "(.)”) do k = trim(k) if k~=nil and k~=‘’ then t[section][k]=“<<no_value>>” end end end end end end return tendfunction trim (s) return (string.gsub(s, “^%s*(.-)%s*$”, “%1”))endfunction gen_custom_data(tbl) local str=‘’ for key, value in pairs(tbl) do str = str..“n[”..key..“]n” for k, v in pairs(value) do if v~=“<<no_value>>” then str=str..k.." = “..v..“n” else str=str..k..“n” end end end return strendlocal strfunction dump_table(tbl) for k,v in pairs(tbl) do if type(v)==“table” then dbglog(”~“..tostring(k)..” => “) dump_table(v) else str=”~“..tostring(k)..” => "..tostring(v) if string.len(str)>200 then str=string.sub(str,1,200) end dbglog(str) end end get_console():execute(“flush”)end

  • Tworzymy kolejny plik skryptu - ja nadałem mu nazwę: npctest.script , nazwa pliku jest wykorzystywana do aktywacji funkcji poprzez dialog. Jeśli obierzemy inną nazwę - pamiętajmy aby wywołać funkcję poprzez wpis adekwatny do nadanej nazwy pliku. - patrz pkt. 8 niniejszego opracowania. Plik umieszczamy w folderze skryptów gry [gamedatascripts]. Jego zawartość to zbiór następujących funkcji (dla spawnu 3 NPC):

  • Ponieważ tworzę jednostki na Kordonie - modyfikuję plik: character_desc_escape.xml [gamedataconfiggameplay], umieszczając tam wpisy definiujące 3 stalkerów:

  • W pliku npc_profile.xml [gamedataconfiggameplay] umieszczamy wpisy o następującej treści (dla każdej postaci):  

  • Do pliku spawn_sections.ltx [gamedataconfigcreatures] dodajemy wpisy, które podobnie jak opisane poprzednio - stanowią uzupełnienie identyfikatorów profilowych: “test”/“test2”/“test3”

  • Tworzymy 3 pliki . ltx odpowiadające za schemat wykorzystywany przez NPC, opierający się o wykorzystanie punktu odniesienia(punkt dodamy później). Pliki umieszczamy w folderze scripts [lokalizacja:gamedataconfigscripts]. Zgodnie ze ścieżką zawartą w funkcji odpowiedzialnej za spawn NPC, opisanej w pkt. 2 - tworzymy zatem pliki:
    test_npc_logic.ltx
    test2_npc_logic.ltx
    test3_npc_logic.ltx

Każdy z w/w plików powinien zawierać wpis następujący:

  • Następnie musimy znaleźć sposób na aktywację utworzonej w pkt. 2 funkcji nowy_npc(). Ja w przykładzie wykorzystam dialog, który muszę wprowadzić do gry. Kilka uzupełniających informacji nt. dialogów i możliwości ich wykorzystania - można przeczytać pod tym linkiem do poradnika. Wykorzystam postać Wilka do realizacji tej części modyfikacji, zatem do pliku character_desc_escape.xml [lokalizacja: gamedataconfiggameplay] w sekcji Wilka tj.

  • W pliku dialogów z Kordonu tj. dialogs_escape.xml [lokalizacja: gamedataconfiggameplay] - tworzę drzewo dialogowe, dla osadzenia tekstu.   

  • Kolejny etap - to utworzenie właściwego dialogu. Do pliku odpowiadającego za dialogi z Kordonu tj. stable_dialogs_escape.xml [lokalizacja: gamedataconfigtextpol] dodaję następującą sekcję:  

  • Do pliku game_story_ids.ltx [lokalizacja: gamedataconfig] dodajemy identyfikator numeryczny zgodny ze zdefiniowanym profilem .xml oraz funkcją opisaną w pkt. 2 niniejszego rozdziału.

  • Dekompilujemy plik all.spawn - patrz ten poradnik, cz.I, dodając do powstałych plików way_** \ ***.ltx (* - nazwa mapy) - punkty odniesienia. W moim przykładzie 3 NPC’ów wykorzystuje jeden punkt w wiosce nowicjuszy, zatem dodaję sekcję odpowiadającą za punkt do pliku way_l01_escape.ltx :

  • Plik all.spawn zawierający punkt odniesienia przygotowany dla spawnowanych jednostek  - umieszczamy w folderze spawns. Jak widać na filmiku poniżej - nie potrzeba nowej gry aby jednostki pojawiły się na mapie.

 

Prezentacja wprowadzonych modyfikacji:

 

 

Prawa autorskie na podstawie regulaminu, pkt.1.6. - The Emperor , wyłączność: StalkerTeam.

__________________________

Autor: The Emperor dla StalkerTeam

3 polubienia