Skocz do zawartości

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


Rekomendowane odpowiedzi

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.

 

post-3466-0-39481400-1383406724_thumb.pn

 

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

    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):

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

    Każ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

  • Dodatnia 3
Odnośnik do komentarza
Udostępnij na innych stronach

  • Meta zablokował(a) ten temat
Gość
Ten temat został zamknięty. Brak możliwości dodania odpowiedzi.
×
×
  • Dodaj nową pozycję...

Powiadomienie o plikach cookie

Korzystając z tej strony, zgadzasz się na nasze Warunki użytkowania.