xash weapon system (goldSRC (hl1))

Вступление

Как известно все начинающие кодеры под ХЛ пытаются сделать какую-нибудь пушку, добавить новое оружие.
Некоторые настолько зацикливаются на этом, что все их старания направлены именно на создание новых оружий.
Однако создавая бесчисленные клоны mp5 внимание игроков не привлечешь. Тогда начинают создавать всякие лучевые ружья с дополнительными эффектами, типа меняющихся скинов, дополнительных бодей - например глушитель для глока, какой-нибудь лучемет с экранчиком, отображающим информацию о своем состоянии.
Опытным кодерам не составит труда реализовать все это в своем оружии, новичков же подстерегает много подводных камней, например такие мелочи, как несохранение нужной анимации, боди тоже выставляется не мгновенно, а через пару секунд после загрузки, holster у оружий в оригинале тоже не работает, руки у гордона всегда оранжевые, в костюме, независимо от его наличия. Для решения всех этих проблем и была разработана xash weapon system.
Не меняя ничего в каждой пушке в отдельности, но являясь глобальной она позволит вам получить отличную базу для разработки новых оружий с новыми эффектами.
Итак приступим :)

Часть 1.Включаем Holster анимацию.

Теоретически анимация holster должна проигрываться, однако в Хл чего-то недосмотрели и в результате мы имеем то, что мы имеем. Баг стал настолько задекларированным, что его даже портировали в хл2, не пытаясь исправить.
Так давайте же исправим его!
Откройте player.h и найдите строку CBasePlayerItem *m_pLastItem;
вставьте после нее строчку CBasePlayerItem *m_pNextItem;
Теперь найдите строку void SelectItem(const char *pstr);
и вставьте после нее void QueueItem(CBasePlayerItem *pItem);
Теперь у нас есть необходимые декларации переходим в player.cpp.
Откройте его и в сейвдату добавьте вот эту строку DEFINE_FIELD( CBasePlayer, m_pNextItem, FIELD_CLASSPTR ), какой именно вы ее поместите не имеет значения. Однако для удобства ее можно поместить после строки DEFINE_FIELD( CBasePlayer, m_pLastItem, FIELD_CLASSPTR ), Пойдем дальше - найдите функцию void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) и после строки if ( m_pActiveItem )
m_pActiveItem->Holster( );
напишите m_pNextItem = NULL;
Переходим в функцию void CBasePlayer::SelectNextItem( int iItem )
найдите там строчку m_pActiveItem = pItem;
и замените ее на QueueItem(pItem);
Затем спуститсеь ниже и после окончания этой функции добавьте еще одну, собсно сам QueueItem
void CBasePlayer::QueueItem(CBasePlayerItem *pItem)
{
    if(!m_pActiveItem)// no active weapon
    {
        m_pActiveItem = pItem;
        return; // just set this item as active
    }
    else
    {
        m_pLastItem = m_pActiveItem;
        m_pActiveItem = NULL;// clear current
    }
    m_pNextItem = pItem;// add item to queue
}

Пойдем дальше - найдите функцию void CBasePlayer::ItemPreFrame() и перед строчкой
if (!m_pActiveItem)
    return;

вставьте следующий код:
if (!m_pActiveItem)
{
    if(m_pNextItem)
    {
        m_pActiveItem = m_pNextItem;
        m_pActiveItem->Deploy();
        m_pActiveItem->UpdateItemInfo();
        m_pNextItem = NULL;
    }
}

Далее находим строчку // FIX, this needs to queue them up and delay
в функции void CBasePlayer::SelectItem(const char *pstr) и вместо
m_pLastItem = m_pActiveItem;
m_pActiveItem = pItem;
находящихся немного ниже, вставляем опять таки
QueueItem(pItem);
(комметарий про фикс тоже можно убрать - теперь все будет работать как надо).
В функции void CBasePlayer::SelectLastItem(void)
Замените 5 последних строчек на это:
QueueItem(m_pLastItem);if (m_pActiveItem)
{
    m_pActiveItem->Deploy( );
    m_pActiveItem->UpdateItemInfo( );
}

Ищем функцию BOOL CBasePlayer :: SwitchWeapon( CBasePlayerItem *pWeapon )
и меняем там строчки
m_pActiveItem = pWeapon;
pWeapon->Deploy( );
на
QueueItem(pWeapon);if (m_pActiveItem)// XWider: QueueItem sets it if we have no current weapopn
{
    m_pActiveItem->Deploy( );
    m_pActiveItem->UpdateItemInfo( );
}

На этом с player.cpp покончено. Теоретически наши пушки уже работают как надо, однако здесь начнутся проблемы несколько иного рода. Дело в том, что SDK 2.2 и SDK 2.3 имеет в своем составе так называеме клиентские оружия, которые компилируются как на сервере, так и на клиенте. Данная система предназначена для мультиплеера и к синглу никакого отношения не имеет, однако возможны определеные пролблемы с проигрыванием анимаций Holster на таких клиентских пушках - моргания при смене оружия и.т.д.
Для того, чтобы заведомо отключить проигрывание Holster анимации на клиенте нам нужно сделать следующее:
1.Откройте weapons.cpp
2. найдите код
if ( skiplocal && ENGINE_CANSKIP( m_pPlayer->edict() ) )
return;

3.заккоментируйте его
Компилируйте обе библиотеки, если вы все сделали строго по инструкции проблем не должно возникнуть.
Теперь запускайте хл с новыми библиотеками и наслаждайтесь проигрыванием анимации holster :)
Правда вы наверное заметили некоторые огрехи в работе системы - например hornetgun, shotgun, snrak, mp5, glock не успевают тольком переключится, а по прежнему исчезают чуть ли не мгновенно. А tripmine стал огромный и занимает весь экран :)
Как ни странно, но причины такого поведения глубоко индивидуальны для разных пушек.
Например hornetgun и snrak просто не успевают переключится из за длины своей holster анимации.
У glock и shotgun функция Holster попросту отсутствует, а у mp5 ко всему еще и нету такой анимации.
Пофиксить подобное поведение для hornetgun и snrak очень легко - просто в их функции holster измените время в строчке m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5;
с 0.5 скажем на 1.3
С glock и shotgun будет чуть посложнее - нам нужно добавить в каждую пушку функцию holster, по аналогии с другими оружиями. Идем в weapons.h и добавляем void Holster( int skiplocal = 0 );
в классы глока, мп5 и шотгана. Обычно это удобно сделать под функцией Deploy.
Теперь нам нужно написать саму функцию Holster в этих пушках.
Для глока она будет выглядеть вот так (учтите, что код глока находится в папке dlls\wpn_shared)
void CGlock::Holster( int skiplocal /* = 0 */ )
{
    m_fInReload = FALSE;// cancel any reload in progress.
    
    m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5;
    SendWeaponAnim( GLOCK_HOLSTER );
}

Для дробовика вот так
void CShotgun::Holster( int skiplocal /* = 0 */ )
{
    m_fInReload = FALSE;// cancel any reload in progress.
    
    m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5;
    SendWeaponAnim( SHOTGUN_HOLSTER );
}

Вставлять их обычно удобно под функцией Deploy.
Для mp5 придется провести более серьезную работу - для начала добавьте в энумератор
новую анимацию:
enum mp5_e
{
    MP5_LONGIDLE = 0,
    MP5_IDLE1,
    MP5_LAUNCH,
    MP5_RELOAD,
    MP5_DEPLOY,
    MP5_FIRE1,
    MP5_FIRE2,
    MP5_FIRE3,
    MP5_HOLSTER,
};

и функцию Холстера
void CMP5::Holster( int skiplocal /* = 0 */ )
{
    m_fInReload = FALSE;// cancel any reload in progress.
    
    m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5;
    SendWeaponAnim( MP5_HOLSTER );
}

Однако, как вы понимаете, одним кодом тут не обойдешься - нам нужно добавить недостающую анимацию в модель v_9mmAR.mdl. Это не так сложно, как может показаться на первый взгляд.
Декомпилируйте эту модель Milkshape (или просто mdl decompiler'ом) найдите анимацию deploy, сделайте ее копию, переименовав в holster. В QC-файле добавьте новую анимацию holster, обязательно после третьей анимации shoot - т.е. самой последней. Теперь самое главное -нам нужно инвертировать данную анимацию. Это можно сделать в Milkshape, однако если увас его нету, либо вы не умеете в нем работать, можно поступить гораздо проще.
Откройте наш holster.smd и прокрутите его до строчки time 0 - это и есть первый кадр нашей анимации. Логично предположить, что заменив time 0 на time 30 мы превратим первый кадр анимации в последний, что мы собственно с вами и сделаем. Промотайте анимацию в самый конец, до последнего кадра. Переименуйте его в time 0, поднимитесь немного вверх, переименуйте предыдущий кадр в time 1 и так пока не доберетесь до самого верха. Компилируйте модель обратно при помощи studiomdl.exe или milkshape. Если вы все сделали верно, у автомата появится анимация holster. Теперь запускаем игру и наслаждаемся плавным переключением всех пушек :)
Возможно вам покажется, что некоторые пушки опускаются слишком быстро, а другие наоборот - достаются слишком медленно - время опускания можно подрегулировать индивидуально для каждой пушки, подобно тому, как мы это сделали для снарков и хорнет гана. Например для магнума я бы рекомендовал его уменьшить - поскольку анимация убирания у него слишком быстрая.
Еще у нас остался трипмайн с большой неправильно моделькой, чтобы поправить это дело, просто раскоментируйте строчку pev->body = 0; в функции
BOOL CTripmine::Deploy( )
{
    pev->body = 0;
    return DefaultDeploy( "models/v_tripmine.mdl", "models/p_tripmine.mdl", TRIPMINE_DRAW, "trip" );
}

На этом собственно и все. Мы получили стабильно работающий holster и пофиксили все баги, которые у нас могли возникнуть. Кто-то может на этом остановится, а мы перейдем ко второй части.

Часть2. Отключаем клиентские пушки и систему предиктинга

Система предикитинга вкупе с клиентскими пушками служит одной цели - в условиях плохой связи по сети предсказать действия игрока и сымитировать их. Однако, в хл эта система была добавлена, что называется, в самый последний момент, наспех и повлекла за собой массу посторонних проблем.
Во-первых уменьшилась скорость загрузки игры, во-вторых пушки переодически, при переходе с уровня на уровень перестают стрелять. В третьих увеличился траффик между клиентом и сервером. Наконец это доставляет много проблем начинающим кодерам, поскольку, если раньше код одной пушки находился в одном файле (см. SDK 2.1) то теперь он разбросан по weapons.cpp weapons.h да еще и некоторых файлах на клиенте. Разумеется чтобы проверить работу нового оружия нам нужно комиплировать клиент, иначе, вроде бы правильный код может давать совершенно непредсказуемые результаты. При игре по локальной сети или ADSL лаги практически отсутствуют, в синглплеере такого понятия нету вообще, таким образом, если вы не планируете играть в свой мод по модему, клиентские пушки лучше отключить.
Сделать это довольно просто - откройте проект в MSVC и нажмите Alt+F7. Выберите вкладку С/С++ и в строке
Preprocessor definitions удалите слово ,CLIENT_WEAPONS. Аналогичную операцию проделайте и с клиентом.
Компилируйте оба проекта при помощи Rebuild All. Не забудьте ввести в консоли команду
cl_lw 0 Проверяем что у нас получилось - монтировкой гордон стал размахивать слишком активно, питон стал выпускать свои заряды мгновенно, работа остальных оружий не изменилась. Кроме всего Хл стал грузится быстрее и после смены уровней пушки отлично стреляют.
Чтобы поправить трабл с магнумом откройте python.cpp найдите там строчки
m_flNextPrimaryAttack = 0.75;
m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 );
и замените их на
m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.75;
m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 );
Переходим в функцию idle - там нам потребуется приплюсовать UTIL_WeaponTimeBase() к четырем строкам, типа
m_flTimeWeaponIdle = (70.0/30.0);
после приплюсовки строка должна выглядеть примерно так
m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + (70.0/30.0);
на каждую idle - анимацию - своя строка.
Откройте файл mp5.cpp и найдите там строчку
m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 );
Точно также приплюсуйте к ней UTIL_WeaponTimeBase():
m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 );
Как вы помните, при cl_lw 1 у нас перестали работать еще gauss, монстровка и egon. Это надо исправить.
Откройте crowbar.cpp найдите в нем строчку PLAYBACK_EVENT_FULL
и замените там параметр FEV_NOTHOST на flags. А чуть выше добавьте этот код
int flags;
#if defined( CLIENT_WEAPONS )
flags = FEV_NOTHOST;
#else
flags = 0;
#endif

Подобную операцию проделайте так же в gauss.cpp, однако не трогайте эвенты с двумя флагами:
FEV_RELIABLE | FEV_GLOBAL или FEV_NOTHOST | FEV_RELIABLE - на они не нужны.
А вот там где имеется только флаг FEV_NOTHOST смело меняйте его на flags и вставляйте чуть выше тот же самый код как и для crowbar.cpp.
Теперь монтировка и гаусс у нас работают как с включенным cl_lw так и с отключенным.
Однако с эгоном мы ничего поделать не сможем - он активно использует передавамеые данные с сервера, для ориентации своего луча. Единственный выход заставить его работать правильно - это сделать предиктинг целиком отключаемым избавившись от дефайнов CLIENT_WEAPONS.
Чем мы с вами и займемся в третьей части :)

Часть3. Делаем динамическую систему предиктинга

Динамическая система предиктинга удобна тем, что вы можете отключить ее в любой момент и сравнить прирост
траффика, наличие багов при стрельбе при переходе на следующий уровень, а также проверить ее действенность
при игре по модему. Основную работу нам придется проделать на сервере, однако заглянем мы и на клиент.
Итак приступим :)
Откройте util.h и вставьте в самый конец файла
int IsPredicting (void) //returned predict state
Откройте util.cpp и добавьте в самое начало файла, после #include "gamerules.h"
int IsPredicting (void)
{
    //give current state
    if ( CVAR_GET_FLOAT( "cl_lw" )) return TRUE;
        else return FALSE;
}

Теперь немного о том, как заменить препроцессорные дефайны на наше условие.
рассмотрим это на примере следующей функции - UTIL_WeaponTimeBase
Первоначально она выглядит вот так:
float UTIL_WeaponTimeBase( void )
{
#if defined( CLIENT_WEAPONS )
    return 0.0;
#else
    return gpGlobals->time;
#endif
}

А после замены нами дефайнов будет выглядеть вот так
float UTIL_WeaponTimeBase( void )
{
    if(IsPredicting())
    {
        return 0.0;
    }
    else
    {
        return gpGlobals->time;
    }
}

Надеюсь это понятно :)
Так например фнукция смены флагов в эвенте (которая есть в каждом оружии) после доработки будет иметь такой вид:
if(IsPredicting())
{
    flags = FEV_NOTHOST;
}
else
{
    flags = 0;
}

Данный код нужно вставить во все оружия взмен этого кода:
#if defined( CLIENT_WEAPONS )
flags = FEV_NOTHOST;
#else
flags = 0;
#endif

В weapons.cpp функция CanAttack должна иметь вот такой вид
BOOL CanAttack( float attack_time, float curtime, BOOL isPredicted )
{
    if(IsPredicting())
    {
        if ( !isPredicted )
            return ( attack_time <= curtime ) ? TRUE : FALSE;
        else    return ( attack_time <= 0.0 ) ? TRUE : FALSE;
    }
    else return ( attack_time <= curtime ) ? TRUE : FALSE;
}

а SendWeaponAnim - такой:
void CBasePlayerWeapon::SendWeaponAnim( int iAnim, int skiplocal, int body )
{
    if ( IsPredicting() )
        skiplocal = 1;
    else
        skiplocal = 0;
    
    m_pPlayer->pev->weaponanim = iAnim;
    
    //don't use this
    //if ( skiplocal && ENGINE_CANSKIP( m_pPlayer->edict() ) ) return;
    
    MESSAGE_BEGIN( MSG_ONE, SVC_WEAPONANIM, NULL, m_pPlayer->pev );
    WRITE_BYTE( iAnim ); // sequence number
    WRITE_BYTE( pev->body ); // weaponmodel bodygroup.
    MESSAGE_END();
}

В сейв дате удалите поля с FIELD_FLOAT, оставив только FILED_TIME:
TYPEDESCRIPTION    CBasePlayerWeapon::m_SaveData[] =
{
    DEFINE_FIELD( CBasePlayerWeapon, m_flNextPrimaryAttack, FIELD_TIME ),
    DEFINE_FIELD( CBasePlayerWeapon, m_flNextSecondaryAttack, FIELD_TIME ),
    DEFINE_FIELD( CBasePlayerWeapon, m_flTimeWeaponIdle, FIELD_TIME ),
    DEFINE_FIELD( CBasePlayerWeapon, m_iPrimaryAmmoType, FIELD_INTEGER ),
    DEFINE_FIELD( CBasePlayerWeapon, m_iSecondaryAmmoType, FIELD_INTEGER ),
    DEFINE_FIELD( CBasePlayerWeapon, m_iClip, FIELD_INTEGER ),
    DEFINE_FIELD( CBasePlayerWeapon, m_iDefaultAmmo, FIELD_INTEGER ),
};

В weapons.h функция UseDecrement для всех оружий, где она есть должна выглядеть вот так:
virtual BOOL UseDecrement( void )
{
    if(IsPredicting()) return TRUE;
        else return FALSE;
}

В client.cpp ничего особого в комментарии не нуждается, а вот в player.cpp, начало функции
в функциях ItemPreFrame() и ItemPostFrame измените имеющийся код:
#if defined( CLIENT_WEAPONS )
if ( m_flNextAttack > 0 )
#else
if ( gpGlobals->time < m_flNextAttack )
#endif
{
    return;
}

на такое условие:
if(IsPredicting())
{
    if(m_flNextAttack > 0) return;
}
else    
{
    if(gpGlobals->time < m_flNextAttack) return;
}

На этом с серверной частью покончено, переходим в клиент. Откройте файл hl-weapons.cpp
на клиенте (он расположен в папке hl). и в конце файла удалите
#if defined( CLIENT_WEAPONS ) и его #endif. Переходим в файл hl_baseentity.cpp
Добавьте там где-нибудь (не имеет значения где) вот эту строчку:
int IsPredicting (void){ return NULL; }//returned predict state

Все! Можете компилить оба проекта, если все все сделали правильно, то и компиляция должна пройти без проблем. Учтите, что при смене cl_lw на 1 текущее оружие перестает стрелять и вам нужно выбрать какое-нибудь другое, чтобы глюк пропал. Впрочем, поскольку данная смена переменных нужна только для теста, с этой особенностью можно примирится.
Теперь вы сами для себя можете решить нужны ли вам клиентские пушки или нет. Размышления на данную тему выходят за рамки данного тутора.
Если вы еще не устали копипастить, то приступим к самой интересной части тутора - собственно внедрению поддержки эффектов со скинами, бодями и анимациями.

Часть 4. Xash weapon system. part1

Данная система с незначительными изменениями используется в моем моде xash (все версии),а также в SoHL:CB 1.6 final.
Откройте weapons.h и в секции public в class CBasePlayerWeapon : public CBasePlayerItem
добавьте следующие новые функции и перменные:
virtual void RestoreBody ( void );
float    m_flTimeUpdate;
int    m_iLastSkin;
int    m_iLastBody;
BOOL    b_Restored;//restore body and skin after save/load

А виртуальную функцию virtual int iItemSlot( void ) { return 0; }
замените на
virtual int iItemSlot( void )
{
    ItemInfo II;
    if(GetItemInfo(&II))
        return II.iSlot + 1;
    else
        return 0;// return 0 to MAX_ITEMS_SLOTS, used in hud
}

Это позволит нам не писать каждый раз в объявлении класса оружия номер слота.
Также нам нужно немного изменить декларацию функции DefaultDeploy - мы добавим ей еще один параметр:
BOOL DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, float fDrawTime = 0.5 );

Переходим в weapons.cpp. В TYPEDESCRIPTION CBasePlayerWeapon::m_SaveData[]
добавьте новую строчку:
DEFINE_FIELD( CBasePlayerWeapon, m_flTimeUpdate, FIELD_TIME ), Перходим в функцию void CBasePlayerWeapon::ItemPostFrame( void ) - здесь и будут сделаны наиболее значительные изменения.
Для начала переместим
// catch all
if ( ShouldWeaponIdle() )
{
    WeaponIdle();
}

с конца функции в самое ее начало. а перед этим кодом добавим вызов
RestoreBody();
В конце функции будет еще одна строка WeaponIdle(); и return;
Перед ними надо добавить ResetEmptySound(); - теперь вы можете не писать его каждый раз
в начале idle функции в самом оружии.
В функции void CBasePlayerItem::AttachToPlayer ( CBasePlayer *pPlayer )
в самый конец добавьте строчку
SetThink(NULL);
Это поможет устранить некоторые мапперские баги со взятием оружия при старте карты.
Двигаемся вниз в int CBasePlayerWeapon::AddToPlayer( CBasePlayer *pPlayer )
Найдя условие if (bResult)
return AddWeapon( );
Замените его на
if (bResult)//generic message for all weapons
{
MESSAGE_BEGIN(MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev);
WRITE_BYTE(m_iId);
MESSAGE_END(); return AddWeapon( );
}
Теперь аналогичная функция в коде самой пушки становится просто не нужна - их нужно удалить. (не забудьте также удалить их объявления в классе самих пушек).
Теперь находим функцию DefaultDeploy и целиком меняем ее на нижеследующий код:
BOOL CBasePlayerWeapon :: DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, float fDrawTime )
{
    if (g_pGameRules->IsMultiplayer() && !CanDeploy()) return FALSE;
        m_iLastSkin = -1;//reset last skin info for new weapon
    b_Restored = TRUE;//no need update if deploy
    m_pPlayer->TabulateAmmo();
    m_pPlayer->pev->viewmodel = MAKE_STRING(szViewModel);
    m_pPlayer->pev->weaponmodel = MAKE_STRING(szWeaponModel);
    strcpy( m_pPlayer->m_szAnimExtention, szAnimExt );
    SendWeaponAnim( iAnim );
    
    m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + fDrawTime;//Custom time for deploy
    m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + fDrawTime + 0.5; //Make half-second delay beetwen draw and idle animation
    
    return TRUE;
}

Данная функция теперь содержит жизненно важную информацию об обновлении сикнов и бодей,а также позволяет настроить время поднятия оружия, что тоже довольно удобно, при подгонке времени анимаций. Ну и наконец вставьте чуть ниже функцию RestoreBody - основную функцию xash weapon system.
void CBasePlayerWeapon :: RestoreBody ( void )
{
    if(!b_Restored )
    {         //calculate additional body for special effects
    MESSAGE_BEGIN( MSG_ONE, gmsgSetBody, NULL, m_pPlayer->pev );
    WRITE_BYTE( pev->body ); //weaponmodel body
    MESSAGE_END();
    
    //restore idle animation and hands position
    m_flTimeWeaponIdle = UTIL_WeaponTimeBase();
    b_Restored = TRUE;//reset after next save/load
}
//update weapon skin
if( m_iLastSkin != pev->skin)
{
MESSAGE_BEGIN( MSG_ONE, gmsgSetSkin, NULL, m_pPlayer->pev );
WRITE_BYTE( pev->skin ); //weaponmodel skin.
MESSAGE_END();
m_iLastSkin = pev->skin;
}
}

Как вы помните мы изменили вид DefaultDeploy и теперь некоторые пушки будут возмущаться при компиляции.
В python.cpp в функции Deploy замените последнуюю строку на:
return DefaultDeploy( "models/v_357.mdl", "models/p_357.mdl",PYTHON_DRAW, "python" );
По аналогии в hl_wpn_glock -
return DefaultDeploy( "models/v_9mmhandgun.mdl", "models/p_9mmhandgun.mdl", GLOCK_DRAW, "onehanded" );
На сервере нам осталось только лишь настроить message для скинов и бодей. Откроем player.cpp
и зарегистрируем две переменные:
int gmsgSetBody = 0;
int gmsgSetSkin = 0;
а в LinkUserMessages добавим вот эти две строчки:
gmsgSetBody = REG_USER_MSG("SetBody", 1);
gmsgSetSkin = REG_USER_MSG("SetSkin", 1);
Также добавим в player.h в самый конец, под extern BOOL gInitHUD; наши переменные
extern int gmsgSetBody;
extern int gmsgSetSkin;
Чтобы они стали видны из weapons.cpp.
Переходим в клиент - здесь нам нужно принять сообщения о изменении скина и боди, разумеется,изменить их :)
Процедура добавления message вообщем-то стандартна, поэтому я не буду долго расписывать как это делается, вкратце скажу, что в hud.h после коммента // user messages надо добавить
void _cdecl MsgFunc_SetBody( const char *pszName, int iSize, void *pbuf );
void _cdecl MsgFunc_SetSkin( const char *pszName, int iSize, void *pbuf );
а в public секции класса CHUD две переменных:
int m_iSkin;//set skin for view weaponmodel
int m_iBody;//set body for view weaponmodel
Переходим в hud.cpp - добавьте это куда-нибудь к подобным же конструкциям :)
//change body for weapon models
int __MsgFunc_SetBody(const char *pszName, int iSize, void *pbuf)
{
    gHUD.MsgFunc_SetBody( pszName, iSize, pbuf );
    return 1;
}//change skin for weapon models
int __MsgFunc_SetSkin(const char *pszName, int iSize, void *pbuf)
{
    gHUD.MsgFunc_SetSkin( pszName, iSize, pbuf );
    return 1;
}

А так же не забудьте в void CHud :: Init( void ) вставить
HOOK_MESSAGE( SetBody );//change body for view weapon model
HOOK_MESSAGE( SetSkin );//change skin for view weapon model

Ну и наконец в самом hud_msg.cpp можно наконец-таки вставить собственно
наши сообщения, то есть message:
void CHud :: MsgFunc_SetBody( const char *pszName, int iSize, void *pbuf )
{
    BEGIN_READ( pbuf, iSize );
    gHUD.m_iBody = READ_BYTE();
    cl_entity_s *view = gEngfuncs.GetViewModel();
    view->curstate.body = gHUD.m_iBody;
}void CHud :: MsgFunc_SetSkin( const char *pszName, int iSize, void *pbuf )
{
    BEGIN_READ( pbuf, iSize );
    gHUD.m_iSkin = READ_BYTE();
    cl_entity_s *view = gEngfuncs.GetViewModel();
    view->curstate.skin = gHUD.m_iSkin;
}

Как будто-то бы все, впрочем нет - мы же изменили струкутру заголовка у DefaultDeploy на сервере - значит нужно это сделать и на клиенте. Откройте hl_weapons.cpp и замените функцию целиком на вот этот код:
BOOL CBasePlayerWeapon :: DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, float fDrawTime )
{
    if ( !CanDeploy() )
        return FALSE;
    
    gEngfuncs.CL_LoadModel( szViewModel, &m_pPlayer->pev->viewmodel );
    
    SendWeaponAnim( iAnim, FALSE, 0 );
    
    g_irunninggausspred = false;
    m_pPlayer->m_flNextAttack = fDrawTime + 0.5;
    m_flTimeWeaponIdle = 1.0;
    return TRUE;
}

Также нам придется добавить функцию-заглушку для RestoreBody(); в hl_baseentity.cpp
Она выглядит примерно следующим образом:
void CBasePlayerWeapon :: RestoreBody() {}
Ну вот и все, можно компилировать. Теперь несколько слов о том как использовать эти возможности. Скины можно менять просто написав pev->skin = номер скина; в коде пушки - больше ничего делать не надо :)
Аналогично меняются и тела: напишите pev->body = номер боди; в любой части кода пушки Боди меняются независимо от анимации поэтому вам не нужно каждый раз вызывать SendWeaponAnim, что разумеется очень удобно. Также после save\load теперь восстанавливается нужная idle анимация - это можно увидеть на арбалете, если расстрелять весь боезапас и сохранится\загрузится - в оригинале арбалет опять начнет проигрывать полную анимацию, чего не случится с нашим кодом :)
Теоретически ракетница тоже должна играть правильную анимацию, если у нее нет ракет, однако по глупой ошибке valve проигрывание idle анимации помещено в условие
if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType])
{
Как вы понимаете, если патронов нету то выбор анимации уже не произойдет и мы будем наблюдать
баг после сохранения\загрузки. Исправить это довольно легко - достаточно просто заккоментировать
это условие вместе с ветвлением else.
Перменная m_flTimeUpdate очень пригодится вам для создания всяких паралельных эффектов, вроде
анимации экранчика скинами. Вот пример анимации экранчика ракетницы из xash.
void CRpg::UpdateScreen ( void )
{
    if ( m_flTimeUpdate > UTIL_WeaponTimeBase() ) return;
        
    if (m_pSpot)
    {
        if ( m_iChargeLevel)
        {
            if( pev->skin >= 4 ) pev->skin = 1;
                pev->skin++;
            EMIT_SOUND(ENT(m_pPlayer->pev),CHAN_ITEM, "weapons/beep.wav", 1, ATTN_NORM);
        }
        else pev->skin = 5;
    }
    else pev->skin = 0;
        
    m_flTimeUpdate = UTIL_WeaponTimeBase() + 0.3;
}

Данная функция вызывается из void CRpg::WeaponIdle( void ). Перменная m_iChargeLevel хранит статус ракеты - заряжена\в полете. Экранчик выключается вместе с лазерным прицелом, поэтому сделана проверка на наличие m_pSpot. Теоретически все тоже самое можно сделать через SetThink и pev->nextthink, но это очень глючный и ненадежный способ - во всяком случае, когда я сам пытался сделать экран таким способом мне понадобилась еще куча переменных, для того, чтобы блокировать многократный вызов SetThink, к тому же после перехода на другой уровень или после сохранения\загрузки экранчик напрочь переставал работать.
Надеюсь этих аргументов достаточно, чтобы убедить вас не юзать SetThink в своих оружиях.
Ну и напоследок, если вы еще не забыли - теперь вы можете настраивать время поднятия для
каждой пушки отдельно. Можно например сделать разные времена для сингла и мультиплеера.
Учтите, что время не влияет на скорость проигрывания анимации, а лишь определяет время включения анимации idle. Ну и наконец мне хотелось бы рассказать вам о последней фишке xash weapon system - зависимость модели рук от наличия костюма.

Часть 5. Xash weapon system. part2

Все наверное знают, что в оригинале руки у гордона "всегда в костюме". По сюжету игры это оправдано - все равно игрок сначала получает костюм, а уж только потом находит оружие. А вот для модмейкеров это может быть действительно полезно. Предствим себе такую ситуацию - в начале игры у игрока есть оружие, потом он находит костюм, а потом снова его теряет. И все это время руки игрока остаются в оранжевом костюме. "мелочь" - скажете вы, но из таких вот мелочей как раз таки и складывается общее впечатление об игре. Сам код очень простой, вся сложность как вы догадались заключается в создании моделей. Впрочем вы можете взять их из SoHL:CB 1.6 в полном наборе ;) Однако ближе к делу. Код довольно прост и находится целиком на сервере в weapons.h и weapons.cpp
Начнем как всегда с декларации этого дела в weapons.h
в class CBasePlayerWeapon : public CBasePlayerItem добавьте новые перменные
int m_iBody;
BOOL PlayerHasSuit;
А где-нибудь к остальным дефайнам - вот эти:
#define SUIT m_pPlayer->pev->weapons & 1<<weapon_suit ="">
#define NUM_HANDS 2 //number of hands: barney and gordon
Тут следует оговорится - дело в том что возня с bodygroup занятие довольно муторное и поэтому я решил сделать следующим образом - переменная pev->body у нас занята только под руки и кодер ее не изменяет. Вместо этого он использует переменную m_iBody точно также как и pev->body. Не забудьте сделать соответствующие поправки в коде своих пушек. дефайн SUIT у нас равен FLASE, если игрок не имеет костюма и равен TRUE если игрок его имеет.
Теоретически число рук не ограниченно - главное прдумать проверки при которых они будут менятся.
Открываем weapons.cpp и добавляем в сейв дату наши переменные
DEFINE_FIELD( CBasePlayerWeapon, PlayerHasSuit, FIELD_BOOLEAN ),
DEFINE_FIELD( CBasePlayerWeapon, m_iBody, FIELD_INTEGER ),
теперь перемещаемся в void CBasePlayerWeapon::ItemPostFrame( void )
и после строчки m_fFireOnEmpty = FALSE; добавляем код опускания\поднимания рук:
</weapon_suit>
//play sequence holster/deploy if player find or lose suit
if( ((SUIT) && !PlayerHasSuit) || (!(SUIT) && PlayerHasSuit))
{
    m_pPlayer->m_pActiveItem->Holster( );
    m_pPlayer->QueueItem(this);
    if (m_pPlayer->m_pActiveItem)
    {
        m_pPlayer->m_pActiveItem->Deploy();
        m_pPlayer->m_pActiveItem->UpdateItemInfo( );
    }
}

Теперь, если игрок подберет костюм, руки с оружием опустятся, а затем вновь поднимутся, но разумеется уже с другой субмоделью. В void CBasePlayerWeapon::SendWeaponAnim( int iAnim )
после строки m_pPlayer->pev->weaponanim = iAnim; Вставьте строчку подсчета бодигрупп:
//calculate additional body for special effects
pev->body = (pev->body % NUM_HANDS) + NUM_HANDS * m_iBody;
Перейдем в функцию DefaultDeploy, добавьте собственно код смены боди в самое начало:
if ( SUIT ) pev->body = PlayerHasSuit = TRUE;
else pev->body = PlayerHasSuit = FALSE;
Ну и в функции RestoreBody перед message с pev->body тоже нужно вставить строчку подсчета бодигрупп:
if(!b_Restored )
{ //calculate additional body for special effects
pev->body = (pev->body % NUM_HANDS) + NUM_HANDS * m_iBody;

Вот собственно и все. Теперь поговорим о разных неприятностях, которые нас ожидают при введении этой системы - ну во первых как вы уже догадались - стандартные модели вам не подойдут.
Во-вторых могут возникнуть проблемы с tripmine который не использует w_модель - впрочем в SoHL:CB 1.6 вас ожидает и эта модель, так что проблем не должно возникнуть.
И наконец самое главное - при выстрелах боди будут менятся на другие - это происходит потому, что анимация встрела проигрывается на клиенте, который не знает что творится с бодями на сервере, особенно при cl_lw 0. Выходов из ситуации я вижу два - первый - это перенести анимацию выстрела обратно на сервер.
Способ второй - передать текущий pev->body (Именно pev->body!) на клиент через тот же эвент и поставить его в анимации. Например разберем случай, как это сделать для глока. Вот его эвент PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), fUseAutoAim ? m_usFireGlock1 : m_usFireGlock2, 0.0,
(float *)&g_vecZero, (float *)&g_vecZero, vecDir.x, vecDir.y, 0, 0, ( m_iClip == 0 ) ? 1 : 0, 0 );

Добавим в первый интегер наш pev->body - именно в интегер, поскольку общее число бодей больше двух.
PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), fUseAutoAim ? m_usFireGlock1 : m_usFireGlock2, 0.0,
(float *)&g_vecZero, (float *)&g_vecZero, vecDir.x, vecDir.y, pev->body, 0, ( m_iClip == 0 ) ? 1 : 0, 0 );

А на клиенте, в файле ev_hldm.cpp найдем эвент глока и в функции проигрывания анимации:
gEngfuncs.pEventAPI->EV_WeaponAnimation( empty ? GLOCK_SHOOT_EMPTY : GLOCK_SHOOT, 2 );
Заменим число 2 на наш переданный параметр:
gEngfuncs.pEventAPI->EV_WeaponAnimation( empty ? GLOCK_SHOOT_EMPTY : GLOCK_SHOOT, args->iparam1 );
Правда у вас может возникнуть проблема с egon - там в эвенте нету свободных параметров - как вариант, вы можете сделать проигрывание анимации на сервере :)
Короче говоря нерешаемых проблем как таковых тут нету - все решает аккуратность и терпение.
Данная система начинает работать без внесения изменений в файлы оружия, что очень удобно. Если какая-либо пушка не имеет бодей вобще, это никак не скажется на работе системы и остальных пушек.
Наслаждайтесь =)

Заключение

Похоже в данной области я опять первопроходец - туторов по глобальным системам оружия я как-то не встречал.
будем надеяться что каждый сможет почерпнуть что-то полезное для своего мода. Тутор специально разбит на части, которые независят друг от друга (ну разумеется кроме последней части, которая без плавного опускания рук будет не слишком красиво выглядеть). Хотелось бы выразить благодарность BUzerу за исправление и оптимизацию некоторых моментов. А также Xwiderу за код первой части.
На этом, пожалуй, все.P.S from VM: т.к это очень большой тутор, могут иметь место ошибки с моей стороны...
Источник: HLFX.Ru Forum

Автор: Дядя Миша

CMT (CS Mapping Tutorials) - © 2006-2011+. Created by VM
[ Script Execution time: 0.0047 ]