The Emperor Opublikowano 2 Listopada 2013 Zgłoś Udostępnij Opublikowano 2 Listopada 2013 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=45902Uzyskawszy 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.7zTreść 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): function nowy_npc()local obj =alife():create("test",vector():set(-200.4694519043,-20.58443069458,-148.60520935059),52314,46)local params=xrs_utils.read_stalker_params(obj)params.custom="[logic]ncfg = scriptstest_npc_logic.ltx"params.sid=9000xrs_utils.write_stalker_params(params,obj)local obj =alife():create("test2",vector():set(-202.69140625,-20.110641479492,-143.33158874512),49764,57)local params=xrs_utils.read_stalker_params(obj)params.custom="[logic]ncfg = scriptstest2_npc_logic.ltx"params.sid=9001xrs_utils.write_stalker_params(params,obj)local obj =alife():create("test3",vector():set(-204.28082275391,-20.361850738525,-147.43516540527),48478,46)local params=xrs_utils.read_stalker_params(obj)params.custom="[logic]ncfg = scriptstest3_npc_logic.ltx"params.sid=9002xrs_utils.write_stalker_params(params,obj)end Opis parametrów oraz działania funkcji - na przykładzie 1 sekcji. Funkcja nowy_npc() - w moim przypadku aktywowana dialogiem, powoduje spawn 3 stalkerów (test, test2, test3) na zadane koordynaty. Dzięki wykorzystaniu skryptu xrs_utils.script poprzez wpis: local params - możliwe jest sczytanie danych NPC'a [read_stalker_params(obj)], natomiast fragment: params.custom - umożliwia wykorzystanie schematu pracy NPC'a - zdefiniowanego w pliku .ltx o ścieżce określonej wpisem: "[logic]ncfg = scriptstest_npc_logic.ltx" Ścieżka ta odnosi się do folderu scripts [gamedataconfigscripts] - zawierającego pliki .ltx (utworzymy je później). Początkującym adeptom moddingu chciałbym zwrócić uwagę na fakt, iż gra (mowa o Shoc) zawiera 2 foldery scripts: - jeden odpowiada za skrypty gry, zawierając pliki o rozszerzeniu .script [gamedatascripts], drugi - zawarty w folderze config, jest zbiorem plików .ltx [gamedataconfigscripts]. By uniknąć ewentualnej pomyłki - zwracam uwagę na ten być może oczywisty fakt. Opis plików konfigurujących schemat - patrz pkt.6 Koordynaty odpowiednio w skrypcie: X: -200.4694519043Y: -20.58443069458Z: -148.60520935059level_vertex_id: 52314game_vertex_id: 46sid=9000 - jest to numeryczny identyfikator postaci - odnoszący się do wpisu zawartego w pliku game_story_ids.ltx [gamedataconfig], patrz pkt. 10 Następnym krokiem jest zdefiniowanie parametrów postaci odpowiedzialnych za wygląd, nazwę, stan uzbrojenia, wyświetlaną ikonę, dialogi, ilość gotówki, etc. Ponieważ tworzę jednostki na Kordonie - modyfikuję plik: character_desc_escape.xml [gamedataconfiggameplay], umieszczając tam wpisy definiujące 3 stalkerów: <!-------------------------------------Początek_sekcji------------------------------------------><!-------------------------------------Test-----------------------------------------------------> <specific_character id="test" team_default = "1"> <name>Test</name> <icon>ui_npc_u_stalker_do_nauchniy</icon> <map_icon x="0" y="0"></map_icon> <bio>sim_stalker_master_bio</bio> <class>test</class> <community>stalker</community> <terrain_sect>stalker_terrain</terrain_sect> <money min="100000" max="110000" infinitive="1"></money> <rank>570</rank> <reputation>100</reputation> <visual>actorsdolgstalker_do_nauchniy</visual> <snd_config>characters_voicehuman_03stalker</snd_config> <crouch_type>0</crouch_type> <supplies> [spawn] n wpn_groza n ammo_9x39_ap n ammo_9x19_fmj n #include "gameplaycharacter_food.xml" n #include "gameplaycharacter_drugs.xml" </supplies> #include "gameplaycharacter_criticals_6.xml" #include "gameplaycharacter_dialogs.xml" </specific_character> <!-------------------------------------Test2-----------------------------------------------------> <specific_character id="test2" team_default = "1"> <name>Test2</name> <icon>ui_npc_u_stalker_do_nauchniy</icon> <map_icon x="0" y="0"></map_icon> <bio>sim_stalker_master_bio</bio> <class>test2</class> <community>stalker</community> <terrain_sect>stalker_terrain</terrain_sect> <money min="100000" max="110000" infinitive="1"></money> <rank>571</rank> <reputation>101</reputation> <visual>actorsdolgstalker_do_nauchniy</visual> <snd_config>characters_voicehuman_03stalker</snd_config> <crouch_type>0</crouch_type> <supplies> [spawn] n wpn_groza n ammo_9x39_ap n ammo_9x19_fmj n #include "gameplaycharacter_food.xml" n #include "gameplaycharacter_drugs.xml" </supplies> #include "gameplaycharacter_criticals_6.xml" #include "gameplaycharacter_dialogs.xml" </specific_character> <!-------------------------------------Test3-----------------------------------------------------> <specific_character id="test3" team_default = "1"> <name>Test3</name> <icon>ui_npc_u_stalker_do_nauchniy</icon> <map_icon x="0" y="0"></map_icon> <bio>sim_stalker_master_bio</bio> <class>test3</class> <community>stalker</community> <terrain_sect>stalker_terrain</terrain_sect> <money min="100000" max="110000" infinitive="1"></money> <rank>572</rank> <reputation>102</reputation> <visual>actorsdolgstalker_do_nauchniy</visual> <snd_config>characters_voicehuman_03stalker</snd_config> <crouch_type>0</crouch_type> <supplies> [spawn] n wpn_groza n ammo_9x39_ap n ammo_9x19_fmj n #include "gameplaycharacter_food.xml" n #include "gameplaycharacter_drugs.xml" </supplies> #include "gameplaycharacter_criticals_6.xml" #include "gameplaycharacter_dialogs.xml" </specific_character> <!-------------------------------------Koniec_sekcji-----------------------------------------------------> Parametry opisujące tworzoną postać zostały omówione w tym poradniku, w pkt. 8.2 - etapu wykonawczego tworzenia modyfikacji, toteż odsyłam pod adres w celu uzupełnienia informacji. W pliku npc_profile.xml [gamedataconfiggameplay] umieszczamy wpisy o następującej treści (dla każdej postaci): <character id="test"> <class>test</class> </character> <character id="test2"> <class>test2</class> </character> <character id="test3"> <class>test3</class> </character> Stanowią one uzupełnienie parametru "specific_character id" z pliku character_desc_escape.xml - jak również opisanego w pkt.2 i wykorzystywanego przez skrypty identyfikatora dodawanych postaci: "test"/"test2"/"test3" Do pliku spawn_sections.ltx [gamedataconfigcreatures] dodajemy wpisy, które podobnie jak opisane poprzednio - stanowią uzupełnienie identyfikatorów profilowych: "test"/"test2"/"test3" ;--początek sekcji[test]:stalker $spawn = "respawntest" character_profile = test spec_rank = master community = stalker[test2]:stalker $spawn = "respawntest2" character_profile = test2 spec_rank = master community = stalker[test3]:stalker $spawn = "respawntest3" character_profile = test3 spec_rank = master community = stalker;--koniec sekcji Pamiętajmy - by tworząc powyższe zestawienie - zachować zgodność parametrów z utworzonym wcześniej profilem postaci, omówionym w pkt. 3. tj:□ character_profile□ spec_rank - patrz link, pkt. 8.5 - w części II poradnika.□ community 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.ltxKażdy z w/w plików powinien zawierać wpis następujący: [smart_terrains]none = true[logic]active=kamp[kamp]center_point = esc_siv_centr_kamp Jest to odniesienie do punktu o nazwie "esc_siv_centr_kamp", który zostanie dodany do pod-pliku way_*.ltx stanowiącego część składową all.spawn'a. Ów punkt będą wykorzystywać jednostki oznaczone jako "test"/"test2"/"test3" (stalkerzy siedzący przy ognisku - schemat "kamp"). 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. specific_character id="esc_wolf" dodaję w miejsce występowania dialogów wpis: <actor_dialog>testowy_dialog</actor_dialog> W pliku dialogów z Kordonu tj. dialogs_escape.xml [lokalizacja: gamedataconfiggameplay] - tworzę drzewo dialogowe, dla osadzenia tekstu. <dialog id="testowy_dialog"> <phrase_list> <phrase id="0"> <text>testowy_dialog_0</text> <next>1</next> </phrase> <phrase id="1"> <text>testowy_dialog_1</text> <action>npctest.nowy_npc</action> <next>2</next> </phrase> <phrase id="2"> <text>testowy_dialog_2</text> <action>dialogs.break_dialog</action> </phrase> </phrase_list> </dialog> gdzie: □ dialog id="testowy_dialog" - jest identyfikatorem użytym w pkt.7 - wkomponowanym w sekcję dialogów Wilka. □ testowy_dialog_0, testowy_dialog_1, testowy_dialog_2 są odnośnikami do tekstu, zawartego w pliku stable_dialogs_escape.xml [lokalizacja: gamedataconfigtextpol] - adekwatnie do lokacji. □ <action>...</action> - ów tag odpowiada za aktywację funkcji. W tym konkretnym przypadku aktywowana jest funkcja "nowy_npc" zawarta w pliku npctest.script - patrz pkt. 2. Konstrukcja aktywatora to w pierwszej kolejności nazwa pliku bez rozszerzenia, w drugiej - nazwa funkcji. Obie części rozdziela kropka. □ dialogs.break_dialog - ucięcie dialogu realizowane funkcją "break_dialog" zawartą w pliku dialogs.script Uwaga: stworzone drzewo dialogowe to wersja uproszczona, mająca posłużyć za przetestowanie skryptu. Konstrukcja drzewa dialogowego winna posiadać zdefiniowany infoportions, aby wyeliminować możliwość permanentnego spawnowania tych samych jednostek - co mogłoby skutkować błędami! Link w pkt. 7 zawiera przydatne informacje nt. tworzenia infoportions. 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ę: <string id="testowy_dialog_0"> <text>Cześć Wilk, słyszałem że możesz w "mgnieniu oka" ściągnąć tu oddział Powinności?</text></string><string id="testowy_dialog_1"> <text>Dobrze słyszałeś - spójrz w kierunku płd. Chcesz się tego nauczyć? - %c[255,1,255,255]przeczytaj Tutorial ze StalkerTeam'u.</text></string><string id="testowy_dialog_2"> <text>O w mordę...</text></string> Chcąc wyróżnić fragment tekstu - wystarczy dopisać przed nim %c[255,1,255,255], co odpowiada kolorowi błękitnemu - patrz filmik. 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. 9000 = "test" 9001 = "test2" 9002 = "test3" Po zmodyfikowaniu wszystkich plików i zapisaniu zmian - przystępujemy do modyfikacji pliku all.spawn.Poniższa część przedstawiona jest również na filmiku z testem modyfikacji. 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 : [esc_siv_centr_kamp]points = p0p0:name = name00p0:position = -207.013442993164,-20.4290790557861,-169.828475952148p0:game_vertex_id = 50p0:level_vertex_id = 45902 gdzie: □ [esc_siv_centr_kamp] - to identyfikator punktu występujący w plikach opisanych w pkt. 6 Po wprowadzeniu danych - zapisuję plik i przystępuję do procesu kompilacji. Ważne jest aby nie dodawać (lub usuwać) do plików alife_*.ltx - żadnych obiektów, by nie zmieniać ich ilości. Kompilacja i sposób jej przygotowania - patrz link pkt. 11. 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: http://youtu.be/Q_ZVj6Zb2iU Prawa autorskie na podstawie regulaminu, pkt.1.6. - The Emperor, wyłączność: StalkerTeam.__________________________Autor: The Emperor dla StalkerTeam 3 Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Rekomendowane odpowiedzi