Anakondar
Oct 27 2011, 10:47
Сейчас в нашем мультиплеерном модуле работает следующая система:
Есть одинаковые по физическим параметрам 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 (по Х штук на каждое существо) вэйпоинтов ставить лень - они все с разными тэгами к тому же. Ноль-первый создастся сам, и на него будут возвращаться после сражения все наши объекты. Но по возвращении будут стоять истуканами. Как бы скриптом сгенерить ещё парочку вэйпоинов, да так чтобы они не лежали в стене, в костре, в яме, под столом, на столе?
Цитата(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
Oct 27 2011, 11:28
Цитата(greye @ Oct 27 2011, 12:01)

Можно сделать не из onSpawn, а из событий области, используя GetFirst/GetNextObjectInArea - исключит перезаписывание тега для каждого существа, + GetNearestObjectByTag для однотипных существ, чтобы не приходилось хранить их как-то ещё. Либо можно из того же onSpawn с GetNearestObjectByTag, выставляя на всех "переименованных" существ локалку, и не менять тега, если она уже выставлена.
События области не подходят, так как облась не знает когда именно в ней появится новое существо. Если ставить что-то типа OnEnter - будет реагировать на каждое и опять-таки считать на каждое отдельно. Один раз при старте модуля тоже не вариант, так как существа могут появляться в любой момент волей скриптов и ДМов.
Цитата(greye @ Oct 27 2011, 12:01)

С ActionRandomWalk ничего не сделаешь, сколько я помню.
Не, ну можно свой написать. Что-нибудь типа ActionWalkToLocation(GetRandomLocation(lOriginLoc, fDistance));
Цитата(Anakondar @ Oct 27 2011, 12:28)

Не, ну можно свой написать. Что-нибудь типа ActionWalkToLocation(GetRandomLocation(lOriginLoc, fDistance));
Можно написать генератор точек в заданной форме с заданными параметрами, но тогда не совсем понятен смысл вопроса.
Цитата(Anakondar @ Oct 27 2011, 12:28)

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

Можно написать генератор точек в заданной форме с заданными параметрами, но тогда не совсем понятен смысл вопроса.
Суть вопроса в том, что точки получаются не всегда "правильные". Утыкание в предметы и других сущест выглядят не естественно.
Цитата(greye @ Oct 27 2011, 12:38)

Да, пожалуй. Кстати, а зачем Вы в каждом onSpawn считаете номера для всех существ того же типа, если можно выставить только для себя и изменить значение счётчика?
Мой брат считывает значение счётчика после того, как считал его я, но до того, как я его инкрементировал. Гоблин-крысолов.Такое редко, но бывает.
P.S.: Да, гоблины-крысоловы знают,что такое инкремент. Они умеют считать пойманных крыс и деньги за их трупы.
P.P.S.: Юмор в догонку. Разговор двух скриптеров:
- Этот твой скрипт не работает!
- Да ладно? А какой объект его запускает - убитое существо или труп этого убитого существа?
- А какая разница?
- Большая! Убитое существо и его труп - совершенно разные объекты. Они даже не знакомы!
denis0k
Oct 27 2011, 17:39
Цитата
Захотелось мне добавить определённому типу монстров фит, хитпоинт, скил, дубину, поменять клыки, шкуру, название, скриптсэт, аппирэнс и т.д., для чего я, отредактировав его в тулсэте, нажимаю "Мгновенное обновление", ведь руками расставлять все 2054 экземпляра лень. Естественно, индивидуальные постфиксы слетают. И опять-таки, руками туеву хучу тэгов менять лень. Что делать?
Не ставить крич вручную в тулсете. Самая простая альтернатива - поставить вместо них ещё один вейпоинт, каким-либо образом содержащий ресреф и тег (или просто номер) кричи, а при загрузке модуля пробежаться по точкам и поставить мобов из палитры. Грузиться мб будет адски (а при куче перцепшенов в толпе мобов будет ещё и тми), но зато легко реализуемо.
А ещё можно написать своё блуждание, средней степени универсальности. Чтобы не приходилось ни точки каждому мобу ставить, ни теги каждый раз обновлять.
Ilerien
Oct 27 2011, 19:24
Цитата
Грузиться мб будет адски (а при куче перцепшенов в толпе мобов будет ещё и тми), но зато легко реализуемо.
Почему ТМИ? Персепшены - это совершенно другие скрипты, которые запустятся _после_ спавнера. Количество операций рассчитывается для каждого скрипта отдельно.
Цитата
Захотелось мне добавить определённому типу монстров фит, хитпоинт, скил, дубину, поменять клыки, шкуру, название, скриптсэт, аппирэнс и т.д., для чего я, отредактировав его в тулсэте, нажимаю "Мгновенное обновление", ведь руками расставлять все 2054 экземпляра лень. Естественно, индивидуальные постфиксы слетают. И опять-таки, руками туеву хучу тэгов менять лень. Что делать?
Самый простой способ - сделать кастомный обновитель инстансов объектов на локах, который будет обновлять всё, кроме тегов, и натравливать его на директорию /temp0 каждый раз после правки палитры. Парсинг GFF - неоднократно решавшаяся задача.
denis0k
Oct 27 2011, 20:05
Цитата
Почему ТМИ?
Потому что именно тми в перцепшене я ловил, ставя определённое число мобов в одной локации в зоне видимости друг друга

Весь прикол в том, что просто спавн толпы работает, а если мобы расставлены в тулсете - косячит. Имя скрипта в логе пишется, мне угадывать не пришлось. Собственно, разбираться я тоже не стал (были вещи куда интереснее чужих багов), просто принял за данность.
Цитата
Самый простой способ - сделать кастомный обновитель инстансов объектов на локах
Ага, для тех, кто по сорцам гфф-редактора и/или спекам гфф способен написать свою софтину, а таких в комьюнити явное меньшинство

Собственно, если ТС это не придумал сразу, то он это и не сделает, а помогать вряд ли кто-то будет. Так что остаётся старый добрый вариант с гландами и анусом
Anakondar
Oct 27 2011, 20:47
Цитата(denis0k @ Oct 27 2011, 21:05)

если ТС это не придумал сразу, то он это и не сделает
Да, как-то так оно и есть. Проще посадить пачку не сильно криворуких фанатов переписывать тэги.
denis0k
Oct 28 2011, 07:37
Идеальных варианта два:
1) Заранее всё спроектировать и не менять каждый раз. Настолько идеальный, что нереализуем на практике.
2) Не ставить мобов в тулсете, написав спавнер. Менее идеально, но зато легко применимо на практике, шардовики делают именно так. К слову, ставить мобов в тулсете - не самый кошерный вариант из-за того, что они впустую гоняют скрипты аи (если они конечно есть у них). Отсюда следует ешё вариант:
2а) Ставить мобов в тулсете, но при загрузке их удалять, заменяя точками, на которых они потом при игроке будут спавнится из палитры с нужными и фиксами, и тегами.
Вариант экстенсивного решения проблемы ручной правкой через месяцок-другой сам себя задавит как колосс на глиняных ногах.
Anakondar
Oct 28 2011, 16:43
Проблема не в суть темы, однако, актуалочка.
Переписал всю верховую езду полностью (никаких скин или локал паременных, никаких багов типа "сесть на лошадь, которая находится в другой локации" и т.п.). Собственно, создал свой инклудинг 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 возимело действие на скрипты заклов полиморфов приходится их открывать, делать нулевые изменения и пересохранять. Так какого хрена тогда нужны инклудинги, если всё равно после их изменения приходится искать среди нескольких тысяч скриптов все скрипты, в которые инклудинг включается (извините за тавтологию)? Ну или хотя бы был список всех стандартных инклудингов и список всех стандартных скриптов, куда он инклудится.
З.Ы.: Я, конечно, понимаю, что менять в одном месте проще, чем в ста. Но менять в одном, а затем сто пересохранять без изменений - не сильно большой кайф. Интересно, БВ задумывались о компиляции ВСЕХ скриптов (включая стандартные), например при запуске модуля?
Цитата(Anakondar @ Oct 28 2011, 16:43)

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

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

Но убрать лишь функции из x3_inc_horse в тех заклинаниях, которые их вызывают, нельзя.
Зато можно поставить эту же проверку в те заклинания, которые вас
интересуют, и по ней же их отменить. По мне так это как раз решение проблемы.
Или можно бить пушкой по воробьям: экспортировать разом в модуль все стандартные скрипты, использующие инклюду, и делать билд модуля только для компиляции, чем, собственно, вы и занимались в ручном режиме.)
denis0k
Oct 28 2011, 19:28
Цитата
проблемы не решает
Полностью решает, ибо для этого придуман. Там нужно ставить специальный флаг на прерывание спелла, и тот самый x2_inc_spellhook (заранее включённый во все спеллы и абилки) не даст скастовать. В спеллхуке полиморф можно по спеллид спалить.
Anakondar
Oct 28 2011, 19:49
Цитата
Полностью решает, ибо для этого придуман. Там нужно ставить специальный флаг на прерывание спелла, и тот самый x2_inc_spellhook (заранее включённый во все спеллы и абилки) не даст скастовать.
Во-первых где там? Если в GetLocalString(GetModule(), "X2_S_UD_SPELLSCRIPT"), то я про то же и сказал, что могу поставить туда свою проверку, которая выполнится и "зарежет" каст, если необходимо. Но если моя проверка каст не зарезала, то эффект заклинания, а вместе с ним и стандартная проверка возымеют место. Но хотелось бы исключить стандартную проверку вообще.
Цитата
В спеллхуке полиморф можно по спеллид спалить.
Полиморф - один из примеров (самый простейший), где юзается стандартный лошадиный инклудинг. Моя задача не столько изменить полиморф (их всего 3 штуки), сколько вычленить использование всех функций из стандартного лошадиного инклудинга.
Цитата
Или можно бить пушкой по воробьям: экспортировать разом в модуль все стандартные скрипты, использующие инклюду, и делать билд модуля только для компиляции, чем, собственно, вы и занимались в ручном режиме.)
Опять-таки, цель всего этого дела как раз избежать нагромаждения такого большого количества изменённых стандартных скриптов (итак уже с диалоговыми проверками за 1000 кол-во перевалило). Было бы не плохо просто изменить один только спелхук или добавить один только юзер_дефенд_спел_скрипт, но это либо не помогает совсем, либо далеко от идеала.
Ilerien
Oct 28 2011, 20:57
Цитата
Если в 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
Oct 28 2011, 21:45
Да, этого локального целого я не заметил, извиняюсь.
Есть идеи по улучшению DDDS?
Flaristan
Nov 1 2011, 00:06
По поводу респавна монстров - для себя остановился на том, что лучше всего для каждой такой точки назначать своего определенного «оператора», который в свою очередь будет респить в определенных условиях в определенной точке монстров из палитры и при необходимости тут же делать нужные манипуляции над ними.
Лучше всего, если таким «оператором» будет какой-либо объект в точке респавна, тогда он будет «глазами» и «ушами» респавна и оперировать максимумом условий которые могли бы определять детали респавна.
По поводу автономной деятельности монстров – из качественных тут единственный вариант: не лениться и писать каждому типу монстров в палитре свое индивидуальное АИ.
Движение по вейпоинтам – это слишком громоздкий вариант который лучше использовать только в качестве исключений (например для АИ стражников, или если точек маршрута в АИ предусматривается всего 1 или 2). Если пичкать скрипт проверками на проходимость и прочими «наворотами» – сбоев всеравно в конце концов не избежать, просто потому, что невозможно учесть всего. Эффективнее просто использовать «рэндум валк» или сочетать ее с небольшими вставками «он персепшен».
Anakondar
Nov 1 2011, 19:42
У меня реализована несколько своеобразная система распределения монстров. Монстры спаунятся в рандомной точке из тех, что ассоциирутся с типом данного монстра (например, лисы - в лисьих норах, горные орлы - в гнёздах в горах, люди - в деревнях и городах, дьявоы - в аду). а затем добираются в место, где их пребывание необходимо (леса, поля, огороды, рудники, барикады), пешком. Как только добрались - занимаются своим делом, пока не окончат свой жизненный путь в недрах DestroyObject-а.
Flaristan
Nov 1 2011, 22:04
Тут уже затрагивали где-то рядом вопрос ненужности проработки маршрутов НПС в отсутствии игрока на локации. А в твоей системе фактически выходит, что основную часть своего жизненного цикла криттеры проводят там, где они «должны быть» - тоесть, если отбросить лишнее, получится обычный классический респавн.
Если уж так прям нужна подробная имитация жизнедеятельности, то я бы разбил такой респавн на 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
Nov 1 2011, 22:27
Цитата
переменную/таймаут на определенное значение
если int == 1 && таймаут не вышел, движение к «объекту-1»
…иначе
……если int == 1 && таймаут вышел
………если «объект-1» не находится в поле зрения (тоесть если непись где-то застрял по дороге) – «случайная ходьба» + отсроченный старт таймаута заново (чтобы непись выбрался из тупика);
Эта часть так и реализована. Только не таймаутом, а проверкой на заблокированность пути.
А по поводу движения прыжками, если нет игроков - это не нужно, так как в их отсутствие все движения паузятся автоматически.
Flaristan
Nov 1 2011, 22:41
Тут может быть еще такой фокус, что путь фактически может оказаться не заблокированным, но НПС не сможет его преодолеть командой движения, ориентируясь по прямому вектору (а «прыжки» - как раз более простая альтернатива перемещения в присутствии игрока).
Anakondar
Nov 1 2011, 23:02
Что что, а уж путь, ежели он открыт, нормально просчитывается для нпс. Рандомволка при необходимости хватает.
Ну а прыгать при игроке - вообще не кошерно.
Это текстовая версия — только основной контент. Для просмотра полной версии этой страницы, пожалуйста,
нажмите сюда.