Помощь - Поиск - Пользователи - Календарь
Полная версия: Индивидуальность или универсальность?
Город Мастеров > РЕДАКТОРЫ > Neverwinter Nights Aurora Toolset
Anakondar
Сейчас в нашем мультиплеерном модуле работает следующая система:
Есть одинаковые по физическим параметрам Creature Objects (существа). Одинаковые в смысле того, что являются копиями одного ResRef, расставленными по локациям своего обитания (обычно, более одного экземпляра на локацию). Каждое из этих существ совершают псевдо периодическое движение по маршруту WayPoint-ов WP_CreatureTag_##, причём, маршрут движения у каждого экземпляра свой, чтобы они не мешали друг другу и не скучивались в одном месте. Достигнуто это следующим образом: после выставления существа в тулсете но до выставления его вэйпоинтов к его тэгу дописывается индивидуальный постфикс.

Проблемки? Ога. А именно:
Захотелось мне добавить определённому типу монстров фит, хитпоинт, скил, дубину, поменять клыки, шкуру, название, скриптсэт, аппирэнс и т.д., для чего я, отредактировав его в тулсэте, нажимаю "Мгновенное обновление", ведь руками расставлять все 2054 экземпляра лень. Естественно, индивидуальные постфиксы слетают. И опять-таки, руками туеву хучу тэгов менять лень. Что делать?

Есть решение? Да, вот оно:
В OnSpawn скрипте существ прописал кусок, который смотрит, соответствует ли тэг экземпляра его ResRef-у (если нет, значит менялся вручную). Если да, то скрипт считает, сколько таких существ в облости (area). Если больше 1, то нужно ввести индивидуальность. Делаем ему NewTag, такой что NewTag = Tag + "##", где ## каким-либо оброзом вычесленная порядковый номер существа. Если таковая замена произошла, сигнализируем об этом локальными переменными существа, для верности переносим в WP_NewTag_01 (если такого нет и если локальные переменные существа велят ему быть, а локальные переменые области позволяют ему быть - создаём этот вэйпоинт) и пускаем "по этапу". (Естественно скрипт выполняется только для тех существ, которые были в модуле изначально или создавались с моей палитры моими скриптами. Энкаунтерные существа и саммоны игнорят этот кусок.)

Всё ж нормально, не? Кое-какие моменты остаются:
1. А как порядковый-то номер вичислить? Дело в том, что OnSpawn запускается каждым существом отдельно, и каждое существо нумерует своих собратьев по-разному. Многие хотят быть первыми. Пытался применять различные способы: по тэгу ближайшего вэйпоинта, по LocalInt-счётчику области или модуля, но всё равно случается возникать близнецам с одинаковыми именами (тобишь, тэгами).
2. Что делать с существами, которые вместо WalkWaypoints() гуляют RendomWalk()? Как добавить индивидуальность им (а-ля "точки рандомволка существа01 должны лежать не далее, чем нДистанс от плэйсайбл01)?
3. Создал новый тип существ. Ладно, так уж и быть, расставил 99999 экземпляров через шифт по областям. А вот 999999999 (по Х штук на каждое существо) вэйпоинтов ставить лень - они все с разными тэгами к тому же. Ноль-первый создастся сам, и на него будут возвращаться после сражения все наши объекты. Но по возвращении будут стоять истуканами. Как бы скриптом сгенерить ещё парочку вэйпоинов, да так чтобы они не лежали в стене, в костре, в яме, под столом, на столе?
greye
Цитата(Anakondar @ Oct 27 2011, 11:47) *
1. А как порядковый-то номер вичислить? Дело в том, что OnSpawn запускается каждым существом отдельно, и каждое существо нумерует своих собратьев по-разному. Многие хотят быть первыми. Пытался применять различные способы: по тэгу ближайшего вэйпоинта, по LocalInt-счётчику области или модуля, но всё равно случается возникать близнецам с одинаковыми именами (тобишь, тэгами).

Можно сделать не из onSpawn, а из событий области, используя GetFirst/GetNextObjectInArea - исключит перезаписывание тега для каждого существа, + GetNearestObjectByTag для однотипных существ, чтобы не приходилось хранить их как-то ещё. Либо можно из того же onSpawn с GetNearestObjectByTag, выставляя на всех "переименованных" существ локалку, и не менять тега, если она уже выставлена.
Цитата(Anakondar @ Oct 27 2011, 11:47) *
2. Что делать с существами, которые вместо WalkWaypoints() гуляют RendomWalk()? Как добавить индивидуальность им (а-ля "точки рандомволка существа01 должны лежать не далее, чем нДистанс от плэйсайбл01)?

С ActionRandomWalk ничего не сделаешь, сколько я помню.
Цитата(Anakondar @ Oct 27 2011, 11:47) *
3. Создал новый тип существ. Ладно, так уж и быть, расставил 99999 экземпляров через шифт по областям. А вот 999999999 (по Х штук на каждое существо) вэйпоинтов ставить лень - они все с разными тэгами к тому же. Ноль-первый создастся сам, и на него будут возвращаться после сражения все наши объекты. Но по возвращении будут стоять истуканами. Как бы скриптом сгенерить ещё парочку вэйпоинов, да так чтобы они не лежали в стене, в костре, в яме, под столом, на столе?

Здесь всё достаточно грустно - чтобы сделать это честно, надо извращаться (исключительно моё мнение). Но если сделать пару допущений, то LineOfSightVector/Object + то, что CreateObject сам ищет проходимый участок в радиусе двух тайлов, должно хватить.
Anakondar
Цитата(greye @ Oct 27 2011, 12:01) *
Можно сделать не из onSpawn, а из событий области, используя GetFirst/GetNextObjectInArea - исключит перезаписывание тега для каждого существа, + GetNearestObjectByTag для однотипных существ, чтобы не приходилось хранить их как-то ещё. Либо можно из того же onSpawn с GetNearestObjectByTag, выставляя на всех "переименованных" существ локалку, и не менять тега, если она уже выставлена.


События области не подходят, так как облась не знает когда именно в ней появится новое существо. Если ставить что-то типа OnEnter - будет реагировать на каждое и опять-таки считать на каждое отдельно. Один раз при старте модуля тоже не вариант, так как существа могут появляться в любой момент волей скриптов и ДМов.

Цитата(greye @ Oct 27 2011, 12:01) *
С ActionRandomWalk ничего не сделаешь, сколько я помню.


Не, ну можно свой написать. Что-нибудь типа ActionWalkToLocation(GetRandomLocation(lOriginLoc, fDistance));
greye
Цитата(Anakondar @ Oct 27 2011, 12:28) *
Не, ну можно свой написать. Что-нибудь типа ActionWalkToLocation(GetRandomLocation(lOriginLoc, fDistance));

Можно написать генератор точек в заданной форме с заданными параметрами, но тогда не совсем понятен смысл вопроса.
Цитата(Anakondar @ Oct 27 2011, 12:28) *
События области не подходят, так как облась не знает когда именно в ней появится новое существо. Если ставить что-то типа OnEnter - будет реагировать на каждое и опять-таки считать на каждое отдельно. Один раз при старте модуля тоже не вариант, так как существа могут появляться в любой момент волей скриптов и ДМов.

Да, пожалуй. Кстати, а зачем Вы в каждом onSpawn считаете номера для всех существ того же типа, если можно выставить только для себя и изменить значение счётчика?
Anakondar
Цитата(greye @ Oct 27 2011, 12:38) *
Можно написать генератор точек в заданной форме с заданными параметрами, но тогда не совсем понятен смысл вопроса.

Суть вопроса в том, что точки получаются не всегда "правильные". Утыкание в предметы и других сущест выглядят не естественно.

Цитата(greye @ Oct 27 2011, 12:38) *
Да, пожалуй. Кстати, а зачем Вы в каждом onSpawn считаете номера для всех существ того же типа, если можно выставить только для себя и изменить значение счётчика?

Мой брат считывает значение счётчика после того, как считал его я, но до того, как я его инкрементировал. Гоблин-крысолов.

Такое редко, но бывает.

P.S.: Да, гоблины-крысоловы знают,что такое инкремент. Они умеют считать пойманных крыс и деньги за их трупы.

P.P.S.: Юмор в догонку. Разговор двух скриптеров:
- Этот твой скрипт не работает!
- Да ладно? А какой объект его запускает - убитое существо или труп этого убитого существа?
- А какая разница?
- Большая! Убитое существо и его труп - совершенно разные объекты. Они даже не знакомы!
denis0k
Цитата
Захотелось мне добавить определённому типу монстров фит, хитпоинт, скил, дубину, поменять клыки, шкуру, название, скриптсэт, аппирэнс и т.д., для чего я, отредактировав его в тулсэте, нажимаю "Мгновенное обновление", ведь руками расставлять все 2054 экземпляра лень. Естественно, индивидуальные постфиксы слетают. И опять-таки, руками туеву хучу тэгов менять лень. Что делать?
Не ставить крич вручную в тулсете. Самая простая альтернатива - поставить вместо них ещё один вейпоинт, каким-либо образом содержащий ресреф и тег (или просто номер) кричи, а при загрузке модуля пробежаться по точкам и поставить мобов из палитры. Грузиться мб будет адски (а при куче перцепшенов в толпе мобов будет ещё и тми), но зато легко реализуемо.

А ещё можно написать своё блуждание, средней степени универсальности. Чтобы не приходилось ни точки каждому мобу ставить, ни теги каждый раз обновлять.
Ilerien
Цитата
Грузиться мб будет адски (а при куче перцепшенов в толпе мобов будет ещё и тми), но зато легко реализуемо.
Почему ТМИ? Персепшены - это совершенно другие скрипты, которые запустятся _после_ спавнера. Количество операций рассчитывается для каждого скрипта отдельно.
Цитата
Захотелось мне добавить определённому типу монстров фит, хитпоинт, скил, дубину, поменять клыки, шкуру, название, скриптсэт, аппирэнс и т.д., для чего я, отредактировав его в тулсэте, нажимаю "Мгновенное обновление", ведь руками расставлять все 2054 экземпляра лень. Естественно, индивидуальные постфиксы слетают. И опять-таки, руками туеву хучу тэгов менять лень. Что делать?
Самый простой способ - сделать кастомный обновитель инстансов объектов на локах, который будет обновлять всё, кроме тегов, и натравливать его на директорию /temp0 каждый раз после правки палитры. Парсинг GFF - неоднократно решавшаяся задача. smile.gif
denis0k
Цитата
Почему ТМИ?
Потому что именно тми в перцепшене я ловил, ставя определённое число мобов в одной локации в зоне видимости друг друга smile.gif Весь прикол в том, что просто спавн толпы работает, а если мобы расставлены в тулсете - косячит. Имя скрипта в логе пишется, мне угадывать не пришлось. Собственно, разбираться я тоже не стал (были вещи куда интереснее чужих багов), просто принял за данность.
Цитата
Самый простой способ - сделать кастомный обновитель инстансов объектов на локах
Ага, для тех, кто по сорцам гфф-редактора и/или спекам гфф способен написать свою софтину, а таких в комьюнити явное меньшинство smile.gif Собственно, если ТС это не придумал сразу, то он это и не сделает, а помогать вряд ли кто-то будет. Так что остаётся старый добрый вариант с гландами и анусом smile.gif
Anakondar
Цитата(denis0k @ Oct 27 2011, 21:05) *
если ТС это не придумал сразу, то он это и не сделает

Да, как-то так оно и есть. Проще посадить пачку не сильно криворуких фанатов переписывать тэги.
denis0k
Идеальных варианта два:
1) Заранее всё спроектировать и не менять каждый раз. Настолько идеальный, что нереализуем на практике.
2) Не ставить мобов в тулсете, написав спавнер. Менее идеально, но зато легко применимо на практике, шардовики делают именно так. К слову, ставить мобов в тулсете - не самый кошерный вариант из-за того, что они впустую гоняют скрипты аи (если они конечно есть у них). Отсюда следует ешё вариант:
2а) Ставить мобов в тулсете, но при загрузке их удалять, заменяя точками, на которых они потом при игроке будут спавнится из палитры с нужными и фиксами, и тегами.

Вариант экстенсивного решения проблемы ручной правкой через месяцок-другой сам себя задавит как колосс на глиняных ногах.
Anakondar
Проблема не в суть темы, однако, актуалочка.
Переписал всю верховую езду полностью (никаких скин или локал паременных, никаких багов типа "сесть на лошадь, которая находится в другой локации" и т.п.). Собственно, создал свой инклудинг ft_inc_horse с фанкшен сетом и в тех скриптах, где нужно, повставлял его. Естественно мне бы хотелось, чтобы сидя на лошади нельзя было полиморфиться. Смотрим заклы полиморфов:
Код
#include "x2_inc_spellhook"
а в нём
Код
#include "x3_inc_horse"
...
if (HORSEGetIsMounted(oTarget))

Естественно у меня есть своя ftGetIsMounted(). Меняю x2_inc_spellhook
Код
#include "ft_inc_horse"
...
       if (ftGetIsMounted(oTarget))


Но для того, чтобы изменение в x2_inc_spellhook возимело действие на скрипты заклов полиморфов приходится их открывать, делать нулевые изменения и пересохранять. Так какого хрена тогда нужны инклудинги, если всё равно после их изменения приходится искать среди нескольких тысяч скриптов все скрипты, в которые инклудинг включается (извините за тавтологию)? Ну или хотя бы был список всех стандартных инклудингов и список всех стандартных скриптов, куда он инклудится.

З.Ы.: Я, конечно, понимаю, что менять в одном месте проще, чем в ста. Но менять в одном, а затем сто пересохранять без изменений - не сильно большой кайф. Интересно, БВ задумывались о компиляции ВСЕХ скриптов (включая стандартные), например при запуске модуля?
greye
Цитата(Anakondar @ Oct 28 2011, 16:43) *
Естественно мне бы хотелось, чтобы сидя на лошади нельзя было полиморфиться. Смотрим заклы полиморфов:

Для этого есть спеллхук.
Anakondar
Может и спелхук, а не генерек, сути то не менятет - скрипт, куда инклудинг включается компилится только при изменении и сохранении самого скрипта, а не инклудинга.
greye
Цитата(Anakondar @ Oct 28 2011, 17:36) *
Может и спелхук, а не генерек, сути то не менятет - скрипт, куда инклудинг включается компилится только при изменении и сохранении самого скрипта, а не инклудинга.

Я вот об этом - линк.
Anakondar
Ну да, есть такая буква. Однако, как ни странно, проблемы не решает, ибо можно только либо добавить код в заклинание, либо заменить его полностью. Но убрать лишь функции из x3_inc_horse в тех заклинаниях, которые их вызывают, нельзя.
greye
Цитата(Anakondar @ Oct 28 2011, 18:03) *
Но убрать лишь функции из x3_inc_horse в тех заклинаниях, которые их вызывают, нельзя.

Зато можно поставить эту же проверку в те заклинания, которые вас интересуют, и по ней же их отменить. По мне так это как раз решение проблемы.

Или можно бить пушкой по воробьям: экспортировать разом в модуль все стандартные скрипты, использующие инклюду, и делать билд модуля только для компиляции, чем, собственно, вы и занимались в ручном режиме.)
denis0k
Цитата
проблемы не решает
Полностью решает, ибо для этого придуман. Там нужно ставить специальный флаг на прерывание спелла, и тот самый x2_inc_spellhook (заранее включённый во все спеллы и абилки) не даст скастовать. В спеллхуке полиморф можно по спеллид спалить.
Anakondar
Цитата
Полностью решает, ибо для этого придуман. Там нужно ставить специальный флаг на прерывание спелла, и тот самый x2_inc_spellhook (заранее включённый во все спеллы и абилки) не даст скастовать.


Во-первых где там? Если в GetLocalString(GetModule(), "X2_S_UD_SPELLSCRIPT"), то я про то же и сказал, что могу поставить туда свою проверку, которая выполнится и "зарежет" каст, если необходимо. Но если моя проверка каст не зарезала, то эффект заклинания, а вместе с ним и стандартная проверка возымеют место. Но хотелось бы исключить стандартную проверку вообще.

Цитата
В спеллхуке полиморф можно по спеллид спалить.

Полиморф - один из примеров (самый простейший), где юзается стандартный лошадиный инклудинг. Моя задача не столько изменить полиморф (их всего 3 штуки), сколько вычленить использование всех функций из стандартного лошадиного инклудинга.

Цитата
Или можно бить пушкой по воробьям: экспортировать разом в модуль все стандартные скрипты, использующие инклюду, и делать билд модуля только для компиляции, чем, собственно, вы и занимались в ручном режиме.)
Опять-таки, цель всего этого дела как раз избежать нагромаждения такого большого количества изменённых стандартных скриптов (итак уже с диалоговыми проверками за 1000 кол-во перевалило). Было бы не плохо просто изменить один только спелхук или добавить один только юзер_дефенд_спел_скрипт, но это либо не помогает совсем, либо далеко от идеала.
Ilerien
Цитата
Если в GetLocalString(GetModule(), "X2_S_UD_SPELLSCRIPT"), то я про то же и сказал, что могу поставить туда свою проверку, которая выполнится и "зарежет" каст, если необходимо. Но если моя проверка каст не зарезала, то эффект заклинания, а вместе с ним и стандартная проверка возымеют место. Но хотелось бы исключить стандартную проверку вообще.
В чём проблема? Если хочется только убрать дефолтную лошадиную проверку полиморфа, то делается это просто, если посмотреть функцию X2PreSpellCastCode() в x2_inc_spellhook.
Neverwinter Script
int X2PreSpellCastCode()
{
  object oTarget = GetSpellTargetObject();
  int nContinue;

  //---------------------------------------------------------------------------
  // This small addition will check to see if the target is mounted and the
  // spell is therefor one that should not be permitted.
  //---------------------------------------------------------------------------
  if (!GetLocalInt(GetModule(),"X3_NO_SHAPESHIFT_SPELL_CHECK"))
  { // do check for abort due to being mounted check
      if (HorseGetIsMounted(oTarget)&&X3ShapeShiftSpell(oTarget))
      { // shape shifting not allowed while mounted
          if(GetIsPC(oTarget))
          {
              FloatingTextStrRefOnCreature(111982,oTarget,FALSE);
          }
          return FALSE;
      } // shape shifting not allowed while mounted
  } // do check for abort due to being mounted check


  //здесь - всякий флуд вроде проверок на концентрацию, секвенсоры и UMD

  return nContinue;
}
Достаточно выставить в 1 локалку X3_NO_SHAPESHIFT_SPELL_CHECK, и лошадиная проверка выполняться не будет.
Anakondar
Да, этого локального целого я не заметил, извиняюсь.

Есть идеи по улучшению DDDS?
Flaristan
По поводу респавна монстров - для себя остановился на том, что лучше всего для каждой такой точки назначать своего определенного «оператора», который в свою очередь будет респить в определенных условиях в определенной точке монстров из палитры и при необходимости тут же делать нужные манипуляции над ними.
Лучше всего, если таким «оператором» будет какой-либо объект в точке респавна, тогда он будет «глазами» и «ушами» респавна и оперировать максимумом условий которые могли бы определять детали респавна.

По поводу автономной деятельности монстров – из качественных тут единственный вариант: не лениться и писать каждому типу монстров в палитре свое индивидуальное АИ.
Движение по вейпоинтам – это слишком громоздкий вариант который лучше использовать только в качестве исключений (например для АИ стражников, или если точек маршрута в АИ предусматривается всего 1 или 2). Если пичкать скрипт проверками на проходимость и прочими «наворотами» – сбоев всеравно в конце концов не избежать, просто потому, что невозможно учесть всего. Эффективнее просто использовать «рэндум валк» или сочетать ее с небольшими вставками «он персепшен».
Anakondar
У меня реализована несколько своеобразная система распределения монстров. Монстры спаунятся в рандомной точке из тех, что ассоциирутся с типом данного монстра (например, лисы - в лисьих норах, горные орлы - в гнёздах в горах, люди - в деревнях и городах, дьявоы - в аду). а затем добираются в место, где их пребывание необходимо (леса, поля, огороды, рудники, барикады), пешком. Как только добрались - занимаются своим делом, пока не окончат свой жизненный путь в недрах DestroyObject-а.
Flaristan
Тут уже затрагивали где-то рядом вопрос ненужности проработки маршрутов НПС в отсутствии игрока на локации. А в твоей системе фактически выходит, что основную часть своего жизненного цикла криттеры проводят там, где они «должны быть» - тоесть, если отбросить лишнее, получится обычный классический респавн.
Если уж так прям нужна подробная имитация жизнедеятельности, то я бы разбил такой респавн на 2 части:
1) если игрок присутствует на локации во время предположительного спавна, где логически должен «проживать» криттер, то спавнить его в «берлоге» и отмечать путь до мест, где он «должен быть» вейпоинтами (желательно вдоль дороги, если гуманоид, или вдоль проработанной тропинки, если монстр) + заглушка, форсирующая «джампами» его перемещение между вейпоинтами, в случае если он не укладывается в «график» и находится вне боя (вплоть до удаления криттера).
2) если криттер не дошел/не успел дойти вовремя на «нужное место» или если во время таймаута отведенного на его путь до «нужного места» в локации небыло игроков, респить его непосредственно там где он должен оказаться.

Так же можно научить неписей «находить» дорогу до нужного места, если вариант с «прыгающими» не устраивает. Вместо вейпоинтов можно использовать ориентиры по тагам объектов на локации (например отдельно стоящие деревья или камни и т.д. на пути к нужному месту) – движение непися прописать отдельным скриптом разбитым на фазы и поместить в «онхеартбит»:
- [первый блок]
выполнить единожды, задать переменную int = 1, задать переменную/таймаут на определенное значение
если int == 1 && таймаут не вышел, движение к «объекту-1»
…иначе
……если int == 1 && таймаут вышел
………если «объект-1» не находится в поле зрения (тоесть если непись где-то застрял по дороге) – «случайная ходьба» + отсроченный старт таймаута заново (чтобы непись выбрался из тупика);
………иначе задать переменную int = 2, задать переменную/таймаут заново
- [второй блок]
если int == 2 && таймаут не вышел, движение к «объекту-2»
…иначе
……если int == 2 && таймаут вышел
………если «объект-2» не находится в поле зрения – «случайная ходьба» + отсроченный старт таймаута заново;
………иначе задать переменную int = 3, задать переменную/таймаут заново
- [третий блок]… e.t.c.
Anakondar
Цитата
переменную/таймаут на определенное значение
если int == 1 && таймаут не вышел, движение к «объекту-1»
…иначе
……если int == 1 && таймаут вышел
………если «объект-1» не находится в поле зрения (тоесть если непись где-то застрял по дороге) – «случайная ходьба» + отсроченный старт таймаута заново (чтобы непись выбрался из тупика);


Эта часть так и реализована. Только не таймаутом, а проверкой на заблокированность пути.

А по поводу движения прыжками, если нет игроков - это не нужно, так как в их отсутствие все движения паузятся автоматически.
Flaristan
Тут может быть еще такой фокус, что путь фактически может оказаться не заблокированным, но НПС не сможет его преодолеть командой движения, ориентируясь по прямому вектору (а «прыжки» - как раз более простая альтернатива перемещения в присутствии игрока). smile.gif
Anakondar
Что что, а уж путь, ежели он открыт, нормально просчитывается для нпс. Рандомволка при необходимости хватает.

Ну а прыгать при игроке - вообще не кошерно.
Это текстовая версия — только основной контент. Для просмотра полной версии этой страницы, пожалуйста, нажмите сюда.
Invision Power Board © 2001-2019 Invision Power Services, Inc.