Привет всем. В общем, я недавно заморочился и сумел-таки сделать 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 браша должны находится в одном и том же месте. Тем самым координаты двух дверей совпадут и двери будут синхронизированы. Наличие игрока проверяется в стартовой позиции двери.
Если знаете, как улучшить, или знаете другой метод - отпишитесь, вместе сделаем нормальные четкие двери!
Результат на видео.
Итак, начнем. Кто еще не понял, делаю на 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 браша должны находится в одном и том же месте. Тем самым координаты двух дверей совпадут и двери будут синхронизированы. Наличие игрока проверяется в стартовой позиции двери.
Если знаете, как улучшить, или знаете другой метод - отпишитесь, вместе сделаем нормальные четкие двери!
Результат на видео.
Последнее редактирование: