Добавить в корзинуПозвонить
Найти в Дзене
PlayGround.ru

Делаем мод своими руками или NWScript с jenkeee (часть 2) для Star Wars: Knights of the Old Republic

Эта статья подразумевает что у вас имеется пред настроенное окружение. В прошлой статье мы сделали первые шаги в создании своего мода для KOTOR, очень здорово, что нашелся читатель, который постарался разобраться в написанном и прошел мануал. В ходе обсуждения статьи мы пришли к выводу, что модификация не законченна и требует доработки. Выявленные проблемы при смене компаньона в слот 7: теряется опыт компаньона теряется экипировка. В этом руководстве мы рассмотрим этот частный случай и доработаем наш мод до приемлемого состояния. Перед началом работы локализуем проблемный участок нашей логики. Проблема возникает в том, что, когда мы создаём компаньона, используя пред настройки из файла availnpc7_t3.utc, там у нас фиксированы экипировка и опыт. Да, у нас есть возможность менять этот файл и подсовывать свой результат в игру, но это решение плохо для живой игры. Перед началом работы, для вашего удобства я подготовил учебный каталог, вы можете скачать его выше. В этом руководстве будет исп

Эта статья подразумевает что у вас имеется пред настроенное окружение. В прошлой статье мы сделали первые шаги в создании своего мода для KOTOR, очень здорово, что нашелся читатель, который постарался разобраться в написанном и прошел мануал. В ходе обсуждения статьи мы пришли к выводу, что модификация не законченна и требует доработки.

Выявленные проблемы при смене компаньона в слот 7:

теряется опыт компаньона

теряется экипировка.

В этом руководстве мы рассмотрим этот частный случай и доработаем наш мод до приемлемого состояния.

Перед началом работы локализуем проблемный участок нашей логики. Проблема возникает в том, что, когда мы создаём компаньона, используя пред настройки из файла availnpc7_t3.utc, там у нас фиксированы экипировка и опыт. Да, у нас есть возможность менять этот файл и подсовывать свой результат в игру, но это решение плохо для живой игры.

Перед началом работы, для вашего удобства я подготовил учебный каталог, вы можете скачать его выше. В этом руководстве будет использован такой путь размещения

D:\KOTOR_HANDMADE2

Этот путь влияет на синтаксис команд для CMD которые мы будем использовать в этом руководстве.

Первый шаг

Создадим пустышки Вимы и Т3 UTC. Нам нужны чистые персонажи, чтоб мы исключили дублирование экипировки при переключениях. Возьмём из Override Steam-клиента два файла: availnpc7_t3.utc и p_vimasunrider.utc. Это файлы, в которых хранятся профили персонажей, которые мы позже вызовем нашим скриптом. Скопируйте файлы availnpc7_t3.utc и p_vimasunrider.utc и разместите их в 04_build.

Дальше мы Python-скриптом чистим эти файлы и подготавливаем заготовки для нашего мода.

Открываем CMD и вводим туда

cd /d D:\KOTOR_HANDMADE2

copy /y 04_build\availnpc7_t3.utc 05_release\availnpc7_t3.utc

copy /y 04_build\p_vimasunrider.utc 05_release\p_vimasunrider.utc

python tools\python\clear_utc_inventory_lists.py 05_release\availnpc7_t3.utc 05_release\p_vimasunrider.utc

можете проверить результат нашей утилитой.

python tools\python\kotor_inspect.py gff 05_release\availnpc7_t3.utc --field Tag --field Experience --field Equip_ItemList --field ItemList

python tools\python\kotor_inspect.py gff 05_release\p_vimasunrider.utc --field Tag --field Experience --field Equip_ItemList --field ItemList

Ожидаемый вывод на скриншоте.

-2

Заготовки готовы!

Второй шаг

Подготовим диалоги. Мы уже это сделали в статье (часть 1), так что можем пропустить этот шаг. Для этого патча изменений в k_htmd_dialog.dlg и party_vima.dlg не требуется. Можете скопировать k_htmd_dialog.dlg и party_vima.dlg в 05_release из 01_original, а можете ничего не делать. Этот шаг контрольный для отчётности, чтоб не терять связь в последовательности редактируемых файлов — разработка под KOTOR путём реверсинга требует дисциплины на каждом шаге.

Третий шаг

Самый важный шаг - пишем nss to ncs. В этот раз я опишу его подробно, не так сжато, как в статье(часть1).

Возьмем наши заготовки из прошлой статьи и декомпилируем их.

Открываем CMD и вводим инструкции для терминала:

открыли рабочий каталог

cd /d D:\KOTOR_HANDMADE2

декомпилим vima_call_t3.ncs в vima_call_t3_old.nss

tools\nwnnsscomp\nwnnsscomp.exe -d 01_original\vima_call_t3.ncs -o 02_decompiled\vima_call_t3_old.nss

декомпилим t3_call_vima.ncs в t3_call_vima_old.nss

tools\nwnnsscomp\nwnnsscomp.exe -d 01_original\t3_call_vima.ncs -o 02_decompiled\t3_call_vima_old.nss

Почитаем полученный байткод.

-3

Ну как? Что-нибудь понятно?

Не пробуйте редактировать полученный .nss. Если вы внимательно читали статью(часть1) – там я уже говорил, это можно только читать. Прочитайте следующее и запомните!

.nss бывает авторским исходником и восстановленным текстом после декомпиляции .ncs. Расширение одинаковое, но качество разное. Авторский .nss - это нормальный NWScript-код. Декомпилированный .nss - это скорее карта того, что делает байткод: по нему удобно понять вызовы RemoveAvailableNPC, AddAvailableNPCByTemplate, SpawnAvailableNPC, но новый скрипт лучше писать заново нормальным человеческим кодом.

Зная и понимая вышеописанное, вы официально посвящаетесь в модеры KOTOR. Больше у вас нет проблем с пониманием, осталось только нафармить опыта в C-подобных языках, и вы можете реализовать всё, к чему предрасположен KOTOR Odyssey-движок.

Прочитав NSS в виде ассемблероподобного байткода, я предлагаю обратиться к исходникам: ведь они у нас есть, и не будем усложнять жизнь.

Рассмотрим кодинг-часть и напишем NSS.

Это исходник

void SpawnSlot7(location lSpot) {

SpawnAvailableNPC(7, lSpot);

}

void main() {

object oCurrent = OBJECT_SELF;

object oHost = GetFirstPC();

location lSpot = GetLocation(oCurrent);

object oTempT3 = GetObjectByTag("x_t3m4", 0);

object oRealT3 = GetObjectByTag("t3m4", 0);

RemoveAvailableNPC(7);

AddAvailableNPCByTemplate(7, "availnpc7_t3");

AssignCommand(oHost, SetGlobalFadeOut(0.0, 0.35, 0.0, 0.0, 0.0));

if (GetIsObjectValid(oTempT3)) {

AssignCommand(oHost, DelayCommand(0.40, DestroyObject(oTempT3)));

}

if (GetIsObjectValid(oRealT3)) {

AssignCommand(oHost, DelayCommand(0.40, DestroyObject(oRealT3)));

}

AssignCommand(oHost, DelayCommand(0.40, DestroyObject(oCurrent)));

AssignCommand(oHost, DelayCommand(0.45, SpawnSlot7(lSpot)));

AssignCommand(oHost, DelayCommand(0.80, SetGlobalFadeIn(0.0, 0.35, 0.0, 0.0, 0.0)));

}

Создадим 03_scripts\vima_call_t3.nss и наполним его C содержимым:

void SaveSlot7State()

{

SaveNPCState(7);

}

void TransferSlotItem(object oCreature, object oReceiver, int nSlot)

{

object oItem = GetItemInSlot(nSlot, oCreature);

if (GetIsObjectValid(oItem))

{

GiveItem(oItem, oReceiver);

}

}

void TransferAllEquipment(object oCreature, object oReceiver)

{

if (GetIsObjectValid(oCreature) && GetIsObjectValid(oReceiver))

{

TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_HEAD);

TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_BODY);

TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_HANDS);

TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_RIGHTWEAPON);

TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_LEFTWEAPON);

TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_LEFTARM);

TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_RIGHTARM);

TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_IMPLANT);

TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_BELT);

TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_CWEAPON_L);

TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_CWEAPON_R);

TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_CWEAPON_B);

TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_CARMOUR);

}

}

void SpawnSlot7WithXP(location lSpot, int nInheritedXP)

{

object oSpawned = SpawnAvailableNPC(7, lSpot);

if (GetIsObjectValid(oSpawned))

{

SetXP(oSpawned, nInheritedXP);

DelayCommand(0.1, SaveSlot7State());

}

}

void ReplaceVimaWithT3(object oHost, object oCurrent, object oTempT3, object oRealT3, location lSpot, int nInheritedXP)

{

RemoveAvailableNPC(7);

AddAvailableNPCByTemplate(7, "availnpc7_t3");

AssignCommand(oHost, SetGlobalFadeOut(0.0, 0.35, 0.0, 0.0, 0.0));

if (GetIsObjectValid(oTempT3))

{

AssignCommand(oHost, DelayCommand(0.40, DestroyObject(oTempT3)));

}

if (GetIsObjectValid(oRealT3))

{

AssignCommand(oHost, DelayCommand(0.40, DestroyObject(oRealT3)));

}

AssignCommand(oHost, DelayCommand(0.40, DestroyObject(oCurrent)));

AssignCommand(oHost, DelayCommand(0.45, SpawnSlot7WithXP(lSpot, nInheritedXP)));

AssignCommand(oHost, DelayCommand(0.80, SetGlobalFadeIn(0.0, 0.35, 0.0, 0.0, 0.0)));

}

void main()

{

object oCurrent = OBJECT_SELF;

object oHost = GetFirstPC();

int nInheritedXP = GetXP(oHost);

location lSpot = GetLocation(oCurrent);

object oTempT3 = GetObjectByTag("x_t3m4", 0);

object oRealT3 = GetObjectByTag("t3m4", 0);

TransferAllEquipment(oCurrent, oHost);

AssignCommand(oHost, DelayCommand(0.30, ReplaceVimaWithT3(oHost, oCurrent, oTempT3, oRealT3, lSpot, nInheritedXP)));

}

Объясняю, что мы изменили:

-void SpawnSlot7(location lSpot) {

-SpawnAvailableNPC(7, lSpot);

+void SaveSlot7State()

+{

+ SaveNPCState(7);

}

+

+void TransferSlotItem(object oCreature, object oReceiver, int nSlot)

+{

+ object oItem = GetItemInSlot(nSlot, oCreature);

+

+ if (GetIsObjectValid(oItem))

+ {

+ GiveItem(oItem, oReceiver);

+ }

+}

+

+void TransferAllEquipment(object oCreature, object oReceiver)

+{

+ if (GetIsObjectValid(oCreature) && GetIsObjectValid(oReceiver))

+ {

+ TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_HEAD);

+ TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_BODY);

+ TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_HANDS);

+ TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_RIGHTWEAPON);

+ TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_LEFTWEAPON);

+ TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_LEFTARM);

+ TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_RIGHTARM);

+ TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_IMPLANT);

+ TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_BELT);

+ TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_CWEAPON_L);

+ TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_CWEAPON_R);

+ TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_CWEAPON_B);

+ TransferSlotItem(oCreature, oReceiver, INVENTORY_SLOT_CARMOUR);

+ }

+}

+

+void SpawnSlot7WithXP(location lSpot, int nInheritedXP)

+{

+ object oSpawned = SpawnAvailableNPC(7, lSpot);

+

+ if (GetIsObjectValid(oSpawned))

+ {

+ SetXP(oSpawned, nInheritedXP);

+ DelayCommand(0.1, SaveSlot7State());

+ }

+}

+

+void ReplaceVimaWithT3(object oHost, object oCurrent, object oTempT3, object oRealT3, location lSpot, int nInheritedXP)

+{

+ RemoveAvailableNPC(7);

+ AddAvailableNPCByTemplate(7, "availnpc7_t3");

+

+ AssignCommand(oHost, SetGlobalFadeOut(0.0, 0.35, 0.0, 0.0, 0.0));

+

+ if (GetIsObjectValid(oTempT3))

+ {

+ AssignCommand(oHost, DelayCommand(0.40, DestroyObject(oTempT3)));

+ }

+

+ if (GetIsObjectValid(oRealT3))

+ {

+ AssignCommand(oHost, DelayCommand(0.40, DestroyObject(oRealT3)));

+ }

+

+ AssignCommand(oHost, DelayCommand(0.40, DestroyObject(oCurrent)));

+ AssignCommand(oHost, DelayCommand(0.45, SpawnSlot7WithXP(lSpot, nInheritedXP)));

+ AssignCommand(oHost, DelayCommand(0.80, SetGlobalFadeIn(0.0, 0.35, 0.0, 0.0, 0.0)));

+}

+

void main() {

object oCurrent = OBJECT_SELF;

object oHost = GetFirstPC();

+int nInheritedXP = GetXP(oHost);

+

location lSpot = GetLocation(oCurrent);

+

object oTempT3 = GetObjectByTag("x_t3m4", 0);

object oRealT3 = GetObjectByTag("t3m4", 0);

-RemoveAvailableNPC(7);

-AddAvailableNPCByTemplate(7, "availnpc7_t3");

-AssignCommand(oHost, SetGlobalFadeOut(0.0, 0.35, 0.0, 0.0, 0.0));

-if (GetIsObjectValid(oTempT3)) {

-AssignCommand(oHost, DelayCommand(0.40, DestroyObject(oTempT3)));

-}

-if (GetIsObjectValid(oRealT3)) {

-AssignCommand(oHost, DelayCommand(0.40, DestroyObject(oRealT3)));

-}

-AssignCommand(oHost, DelayCommand(0.40, DestroyObject(oCurrent)));

-AssignCommand(oHost, DelayCommand(0.45, SpawnSlot7(lSpot)));

-AssignCommand(oHost, DelayCommand(0.80, SetGlobalFadeIn(0.0, 0.35, 0.0, 0.0, 0.0)));

+

+TransferAllEquipment(oCurrent, oHost);

+

+AssignCommand(oHost, DelayCommand(0.30, ReplaceVimaWithT3(oHost, oCurrent, oTempT3, oRealT3, lSpot, nInheritedXP)));

}

`SpawnSlot7(lSpot)` заменили на: SpawnSlot7WithXP(lSpot, nInheritedXP);

Потому что теперь после спавна надо получить объект нового Т3 и вызвать:

SetXP(oSpawned, nInheritedXP);

В начало main() добавили:

int nInheritedXP = GetXP(oHost);

Перед заменой добавили:

TransferAllEquipment(oCurrent, oHost);

А старую большую цепочку замены вынесли из main() в отдельную функцию:

ReplaceVimaWithT3(...);

Это сделано не ради красоты, а из-за задержки:

DelayCommand(0.30, ReplaceVimaWithT3(...));

То есть Сначала переносим предметы через GiveItem, даем игре короткий момент обработать передачу, и только потом уничтожаем старую Виму и спавним Т3.

Старый скрипт уже показал функции переключения NPC. А функции для опыта и предметов были найдены в nwscript.nss: GetXP, SetXP, GetItemInSlot, GiveItem, SaveNPCState. После этого новая логика стала простой: до замены забираем данные и вещи у старого состояния, после спавна применяем данные к новому объекту.

Теперь скомпилируем скрипт в игровые ресурсы:

cd /d D:\KOTOR_HANDMADE2

tools\nwnnsscomp\nwnnsscomp.exe -c 03_scripts\vima_call_t3.nss -o 05_release\vima_call_t3.ncs

Статья получилась достаточно большой. И в ней я рассказал достаточно сложный материал.

Фаил t3_call_vima.nss как создать я описывать не буду, в статье написано достаточно чтоб желающий разобраться смог сделать это сам. Готовые файлы прикреплены к модификации и доступны для загрузки тут.

-4

Важная техническая деталь: на каждой планете существуют разные копии Ebon Hawk. Все изменения, которые мы делали выше, автор делал для копии Ebon Hawk, которая находится на Слехероне и является точкой входа на планету.

Комментарии приветствуются.