Sidebar

[Xash] Автоматические func_door (кодинг, туториал)

Aynekko

Маппер
07.06.2010
3 525
28
  • Золотая медаль RC
  • Бронзовая медаль 216
Привет всем. В общем, я недавно заморочился и сумел-таки сделать func_door, которые активируются игроком автоматически в заданном радиусе. К сожалению, не совсем понятно, к каким последствиям может привести данное вмешательство в код в долгосрочной перспективе, но - дверь работает, и работает исправно. Вероятно, что это полнейшее ламерство. Но - кодеров найти практически нереально, а всякие мелкие хотелки вроде этих они точно делать не будут, поэтому приходится делать самому в меру возможностей.

Итак, начнем. Кто еще не понял, делаю на XashXT моде версии 0.81 (она же официальная последняя).

Настоятельно рекомендую сделать бэкап файла doors.cpp.
Открываем файл doors.h и добавляем новый флаг рядом с другими:
#define SF_DOOR_AUTOMATED BIT( 11 )
Если у вас уже есть такой бит, то ставьте другой - это галочка для хаммера. Мы ее потом тоже сделаем.

Далее открываем файл
doors.cpp и начинаем наши кододвижения. В class CBaseDoor : public CBaseToggle добавляем две строчки:
void DoorOpenThink( void );
void DoorCloseThink( void );

Это наши новые функции. Туда же добавляем эти переменные:
int AutoDoorRadius; // радиус, на котором дверь будет открываться
float AutoTime; // время пинга/проверки дверью наличия игрока в радиусе - после этого дверь примет решение закрыться или остаться открытой.

Далее, в BEGIN_DATADESC( CBaseDoor ) добавляем новые функции, чтобы они сохранялись в сейв:
DEFINE_FUNCTION( DoorOpenThink ),
DEFINE_FUNCTION( DoorCloseThink ),


Далее, в CBaseDoor::KeyValue( KeyValueData *pkvd ) прописываются поля - это опять же нужно для хаммера. Там идут куча else if и мы добавляем туда свои условия:
else if( FStrEq( pkvd->szKeyName, "openradius" ))
{
AutoDoorRadius = Q_atof( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "autotime" ))
{
AutoTime = Q_atof( pkvd->szValue );
pkvd->fHandled = TRUE;
}


Следующее, CBaseDoor::Spawn. Находим там вот такой код:
if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) )
{
SetTouch ( NULL );
}
else // touchable button
SetTouch( DoorTouch );

Этот код меняем полностью на новый:
if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ))
{
SetTouch ( NULL );
}
// touchable button
else if (FBitSet(pev->spawnflags, SF_DOOR_AUTOMATED))
{
SetTouch( NULL );
}
else
{
SetTouch( DoorTouch );
}

Тем самым мы сделали дверь невосприимчивой к касаниям, когда установлен наш флаг. Сразу же после этого кода, все в тот же Spawn закидываем новый код:
if ( FBitSet(pev->spawnflags, SF_DOOR_AUTOMATED) )
{
SetThink(DoorOpenThink);
// когда дверь спавнится, сразу же запускается поиск живности в радиусе

if (!AutoDoorRadius)
AutoDoorRadius = 192;
// если радиус в хаммере не задан, 192 по-умолчанию

if (!AutoTime || AutoTime < 0.25)
AutoTime = 0.25;
// по-умолчанию время пинга 0.25, ниже ставить не решился - мало ли движок накроется

pev->nextthink = gpGlobals->time + AutoTime;
}


ЕСЛИ РУГАЕТСЯ НА pev->nextthink, то вместо него пишите SetNextThink. Пример:
Вместо: pev->nextthink = gpGlobals->time + AutoTime; Пишем: SetNextThink( AutoTime );

Ну а теперь самое интересное. Функции. Добавляем их, можно сразу после Spawn.
Первая функция ищет игрока, когда дверь в закрытом состоянии, вторая ищет игрока, когда дверь в открытом состоянии. Одновременно они не работают.
ОБНОВЛЕНО 25.10.2020:
любой монстр теперь может активировать дверь, а также можно заблокировать дверь мастером, после разблокировки функции будут работать.
void CBaseDoor :: DoorOpenThink ( void )
{
CBaseEntity *pList[1];
if( UTIL_MonstersInSphere( pList, 1, m_vecPosition1, AutoDoorRadius) )
{
if (!IsLockedByMaster( ))
{
DoorGoUp();
// ALERT(at_console, "Door goes up\n");
pev->nextthink = -1;
}
else
{
SetThink ( DoorOpenThink );
// ALERT(at_console, "Locked by master!\n");
pev->nextthink = gpGlobals->time + AutoTime;
}
}
else
{
// ALERT(at_console, "not here yet...\n");
SetThink ( DoorOpenThink );
pev->nextthink = gpGlobals->time + AutoTime;
}
}

void CBaseDoor :: DoorCloseThink ( void )
{
CBaseEntity *pList[1];

if( !UTIL_MonstersInSphere( pList, 1, m_vecPosition1, AutoDoorRadius) )
{
DoorGoDown();
// ALERT(at_console, "Door goes down\n");
pev->nextthink = -1;
}
else
{
// ALERT(at_console, "someone is here\n");
SetThink ( DoorCloseThink );
pev->nextthink = gpGlobals->time + AutoTime;
}
}


Первая функция, как уже сказал, включается в спауне двери, а если игрок найден в радиусе - выключается, и начинает открываться дверь.
Вторая функция включается тогда, когда дверь достигла открытого состояния. Если игрок не найден в радиусе двери - запускается закрытие двери и функция выключается.
Пропишем же это. Идем в функцию CBaseDoor :: DoorHitTop. Находим там вот эти строчки:
// in flWait seconds, DoorGoDown will fire, unless wait is -1, then door stays open
SetMoveDoneTime( m_flWait );
SetMoveDone( DoorGoDown );

Меняем этот отрывок на этот:
// in flWait seconds, DoorGoDown will fire, unless wait is -1, then door stays open

if( FBitSet( pev->spawnflags, SF_DOOR_AUTOMATED ) )
{
SetTouch( NULL );
SetThink( DoorCloseThink );
pev->nextthink = gpGlobals->time + AutoTime + m_flWait;
}
else
{
SetMoveDoneTime( m_flWait );
SetMoveDone( DoorGoDown );
}

Прибавляем m_flWait - это тот самый delay before close. Если дверь медленная, то пока она откроется - игрок уже может слинять. Можно поставить секунду-две в delay и будет красиво выглядеть.
Теперь идем в CBaseDoor :: DoorHitBottom и добавляем там новое условие запуска другой функции:
if( FBitSet( pev->spawnflags, SF_DOOR_AUTOMATED ) )
{
SetThink( DoorOpenThink );
pev->nextthink = gpGlobals->time + AutoTime;
}


Осталось самая малость - нужно добавить новые параметры и флаги в fgd, для хаммера. Открываем fgd файл и находим строчку:
@SolidClass base(Door, Fire, Parent, ZHLT) = func_door : "Basic door"[]
Вместо этой строчки пишем это:
@SolidClass base(Door, Fire, Parent, ZHLT) = func_door : "Basic door"
[
openradius(integer) : "Opening radius (Auto-open flag)" : 128
autotime(string) : "Time between checking the radius for player" : 0.3
spawnflags(flags) =
[
2048: "Auto-open (experimental!)" : 0
]
]



Вот и все. Идем в хаммер и запиливаем дверь :)
ВАЖНО: для работы радиуса двери требуется ORIGIN-браш!
А теперь о принципе работы для тех, кто еще не разобрался до конца (ну и собственно само несовершенство этого кода…). После загрузки карты ВСЕ двери начинают пинговать радиус вокруг себя. И делают они это каждые 0.3 секунды. Я ставил в пустой комнате 64 двери и мне выдавало 800 фпс. Для сравнения, если поставить 8 солдат вместо 64 дверей в этой же пустой комнате, фпс будет тоже около 800. Просто пустая комната - 2000 фпс. Падение производительности считаю незначительным, поэтому решайте сами. Да и 64 двери - это много.
Следующее несовершенство - реакция двери на монстров. Ее почему-то нет.
ИСПРАВЛЕНО
Далее - дверь не работает с мастером как надо. Нужно как-то переписать код, чтобы дверь могла быть заблокирована и функции работали, только если она разблокирована.
ИСПРАВЛЕНО
И последнее - нет синхронизации для сдвоенных дверей. Открываться могут синхронно, закрываются не всегда синхронно.
ИСПРАВЛЕНО

КАК СДЕЛАТЬ СИНХРОНИЗИРОВАННЫЕ ДВОЙНЫЕ ДВЕРИ?
Основное условие - у обеих дверей должен быть origin-браш. Оба origin браша должны находится в одном и том же месте. Тем самым координаты двух дверей совпадут и двери будут синхронизированы. Наличие игрока проверяется в стартовой позиции двери.

Если знаете, как улучшить, или знаете другой метод - отпишитесь, вместе сделаем нормальные четкие двери!

Результат на видео.
 
Последнее редактирование:

RAVENL!GHT

Member
20.10.2013
180
24
9
18
Интересная тема. Я думаю что класс "player_" вроде как читается только для игрока исключительно, для нпс это работать не будет. Может попробовать перерыть библиотеки #includes ? Может там чего то не хватает ? Опять же повторюсь что никогда не кодил, бывало время от времени учился писать плагины(Pawn) и было это очень давно.
 

Aynekko

Маппер
07.06.2010
3 525
28
  • Золотая медаль RC
  • Бронзовая медаль 216
Я поставил время пинга 0.25 вместо 0.3 и вроде норм, 80 дверей отлично ведут себя и ничего не лагает. Можно спокойно делать дорожку, расстилающуюся перед игроком :)
А пока, я все думаю над этими строчками:
if( ((pMonster = UTIL_FindEntityInSphere( pMonster, pev->origin, AutoDoorRadius)) != NULL) && !(FClassnameIs( pMonster->pev, "player")) )
Я до конца не понимаю, как работает эта UTIL. У меня есть подозрение, что это не дверь пингует радиус вокруг себя, а игрок. То есть он влияет на дверь, если она в его зоне досягаемости…
К примеру, вот условие, что я написал. Это условие закрытия двери. Вот во второй половине - "если НЕ игрок". Я смотрю на это и не понимаю, как оно работает вообще?
То есть "если игрок", то запуск открытия. А "если НЕ игрок", то запуск закрытия. При этом реагирует только на игрока.
Еще одна деталь, что везде в коде, где встречается эта утилита НайтиВСфере, она используется через while, а не if. Но тогда я не знаю, как написать условие "в противном случае", так как else с while не используется…тут уже мне знаний не хватает.

Подумав, добавил:

СДЕЛАЛ!!! Вот новые фукнции. Двери теперь реагируют вообще на всех монстриков. Пока хоть один есть рядом с дверью - дверь не закроется. Включая игрока.
Код обновил в первом посте

Подумав, добавил:

Результат на видео. Глючный звук из-за слишком большого количества дверей. Фпс от этого не страдает. :)
Подумав, добавил:

В общем все, тутор полностью доделан, я считаю. Протестировал двери в разных сценариях. :)
Монстры ходят по нодам и ищут друг друга как надо через эти двери, даже не тормозя. Раньше они подходили к ней, делали трассировку, открывали и шли дальше. Теперь задержек не будет.
Двойные двери всегда синхронизированы.
Работа с мастером - дверь может быть заблокирована, позже разблокирована и так далее. Функционал работает. Если дверь заблокировать мастером в открытом состоянии. То заблокируется она после закрытия.
 
Последнее редактирование:
  • Like
Reactions: RAVENL!GHT

KorteZZ

Возрождение...
18.10.2009
792
32
  • Золотая медаль 113
Очень круто! Радует, что ты не забрасываешь, а очень даже активно продолжаешь работу над своим модом, и более того, делишься своим уникальным кодом, что может помочь другим моддерам, которые не умеют кодить :D
 
  • Like
Reactions: Aynekko

Aynekko

Маппер
07.06.2010
3 525
28
  • Золотая медаль RC
  • Бронзовая медаль 216
Спасибо, как зарелизю мод вообще планирую выложить все исходники и что-то перепишу под туторы. Например, плавный зум арбалета - он тоже ламерный, но рабочий)
Мод точно доделаю, в следующем месяце тему сделаю - чтобы еще придать мотивации делать дальше.
 

Gaia

Чёрный вертолёт
Спонсор
04.08.2008
4 626
33
111
63
Красавчик :) Я так и не смог заставить работать код правильно под обычную халву, под ксашем, честно признаюсь, не тестил.
Ну и ещё одна идея для применения:
127478

Столбики поднимаются при приближении игрока, тем самым выстраивая своеобразный мостик
 

Aynekko

Маппер
07.06.2010
3 525
28
  • Золотая медаль RC
  • Бронзовая медаль 216
Красавчик :) Я так и не смог заставить работать код правильно под обычную халву, под ксашем, честно признаюсь, не тестил.
Столбики поднимаются при приближении игрока, тем самым выстраивая своеобразный мостик
А что именно не получилось? Может я в туторе накосячил. Надо же поправить!

О мостике я тоже думал, обязательно сделаю себе в мод что-то подобное) а пока - вот сделал по твоему скриншоту :)
Тут я немного считерил: дело в том, что нпс не ходят по дверям. Поэтому я поставил невидимый func_wall. Но тестил и без него. Игрок отлично ходит по дверям и не багается. Здесь нужно было бы радиус задать чуть побольше и может быть скорость. Тут 140 "дверей") в целях оптимизации можно порезать мост на поперечные линии, тогда получится 10-20 дверей, а тут каждый блок дверь. Смотреться конечно не так эффектно будет.
Подумав, добавил:

Вот еще бонусом. Задал новый флаг :) суть флага в том, что в изначальной позиции дверь невидимая, в конечной - видимая. Плюс задается прозрачность.
Только в ксаше нужно починить флаг SF_DOOR_SILENT. Он там задан как бит 31, а такого бита в хаммере нет. Там их всего штук 20 флагов что ли.
Дело в том, что даже если не задавать двери никакие звуки, то звук все равно начинает лагать, так как звуки в двери все равно вызываются, даже если их нет (спасибо валв).
А этот флаг отключает воспроизведение звуков дверью насовсем, чтобы не занимала канал. Без этого флага при беге по этому мосту фпс ощутимо проседает, даже если у моста нет звука. Нужно флаг делать...
 
Последнее редактирование:

Gaia

Чёрный вертолёт
Спонсор
04.08.2008
4 626
33
111
63
Может я в туторе накосячил
Нет, тут всё в порядке. В хл, вернее в тех сорцах что мне удалось нарыть под 19ю студию, CBaseToggle не умеет в pev->nextthink. SetThink задаётся, и выполняет то что дали ровно один раз, в момент спавна. Дальше я разбираться не полез, потому как изрядно подзабыл что там и как, да и не до этого совсем.

починить флаг SF_DOOR_SILENT
Попробуй так:

doors.h
Код:
#define SF_DOOR_SILENT                768
hl.fgd
@BaseClass base(Appearflags, Targetname, RenderFields, Global, Angles) = Door
Код:
    [
        1 : "Starts Open" : 0
        4 : "Don't link" : 0
        8: "Passable" : 0
        32: "Toggle" : 0
        256:"Use Only" : 0
        512: "Monsters Can't" : 0
        768: "Silent Move" : 0
    ]
 

Aynekko

Маппер
07.06.2010
3 525
28
  • Золотая медаль RC
  • Бронзовая медаль 216
По-моему pev->nextthink = gpGlobals->time + 0.1 это то же самое, что SetThink( 0.1 ). Попробуй, вдруг сработает?
А флаг со звуком я уже починил. Поменял BIT(31) на BIT(13) (он же 8192 в хаммере). Но все равно спасибо :)
 

Gaia

Чёрный вертолёт
Спонсор
04.08.2008
4 626
33
111
63
Нед. SetThink задает функцию, а pev->nextthink задает время, в которое эта функциябудет вызвана
 

Aynekko

Маппер
07.06.2010
3 525
28
  • Золотая медаль RC
  • Бронзовая медаль 216
Нед. SetThink задает функцию, а pev->nextthink задает время, в которое эта функциябудет вызвана
Тьфу блин, я имел в виду SetNextThink! Сейчас проверил - в ксаше работает. Переписал в спауне "pev->nextthink = gpGlobals->time + AutoTime;" как "SetNextThink(AutoTime)". Все оки :)
 

Aynekko

Маппер
07.06.2010
3 525
28
  • Золотая медаль RC
  • Бронзовая медаль 216
В паренте почему-то двери не хотят работать. Одна дверь прикреплена к другой - одна открывается, а другая нет. Соответственно нельзя сделать например дверь, которая бы въезжала в стену, а потом вбок (в автоматическом варианте имею в виду). У меня не получилось.
 

Новые сообщения

Донат - Хостинг

Итого
200.00 $
Цель
600.00 $

Доноры Красавчики

Пользователи онлайн