Динамический Разброс Пуль

Материал из Наша Wiki - Наша энциклопедия Half-Life
Перейти к: навигация, поиск

Вам наверное всегда было интересно сделать динамический конус разброса как в Counter-Strike:Source ...
Ну вот и пришло время этим заняться :) Для рассмотрения пусть будет weapon_357.cpp

Взглянув на класс, мы видим такую картину :

class CWeapon357 : public CBaseHLCombatWeapon
{
	DECLARE_CLASS( CWeapon357, CBaseHLCombatWeapon );
public:

	CWeapon357( void );

	void	PrimaryAttack( void );
	void	Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator );

	DECLARE_SERVERCLASS();
	DECLARE_DATADESC();
};

Чтож, добавим сразу после Operator_HandleAnimEvent метод :

	virtual const Vector& GetBulletSpread( void );

И проимплементурем его где-то ниже в cpp'шнике чтобы было вот так:


const Vector &CWeapon357::GetBulletSpread( void )
{
	// Lolmen : Берём указатель на игрока
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); 
	if ( !pPlayer ){ return vec3_origin; } // если он нулевой ничего не делаем

	static Vector cone = vec3_origin;
	// Игрок стоит на земле
	if ( ( pPlayer->GetFlags() & FL_ONGROUND ) != 0 )
	{
		if ( ( pPlayer->GetFlags() & FL_DUCKING ) != 0 )
		{	// игрок сидит
			cone = VECTOR_CONE_1DEGREES;
		}
		else
		{
			if ( pPlayer->GetAbsVelocity().Length2D() > 120 )
			{	// игрок двигается быстро
				cone = VECTOR_CONE_5DEGREES;
			}
			else
			{	// Игрок медленно двигается или стоит
				cone = VECTOR_CONE_3DEGREES;
			}
		}
	} // Игрок в прыжке
	else
	{
		cone = VECTOR_CONE_6DEGREES;
	}
	
	return cone;
}

Это отлично подходит для медло стрелокового типа оружия, что делать со скорострельным, например автомат или тому подобное? Тогда для примера возьмём SMG1 weapon_smg1.cpp

И увидим мы следующее :


class CWeaponSMG1 : public CHLSelectFireMachineGun
{
	DECLARE_DATADESC();
public:
	DECLARE_CLASS( CWeaponSMG1, CHLSelectFireMachineGun );

	CWeaponSMG1();

	DECLARE_SERVERCLASS();
	
	void		Precache( void );
	void		AddViewKick( void );
	void		SecondaryAttack( void );

	int		GetMinBurst() { return 2; }
	int		GetMaxBurst() { return 5; }

	virtual 	void Equip( CBaseCombatCharacter *pOwner );
	bool		Reload( void );

	float		GetFireRate( void ) { return 0.075f; }	// 13.3hz
	int		CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; }
	int		WeaponRangeAttack2Condition( float flDot, float flDist );
	Activity	GetPrimaryAttackActivity( void );

	virtual const Vector& GetBulletSpread( void )
	{
		static const Vector cone = VECTOR_CONE_5DEGREES;
		return cone;
	}

	const WeaponProficiencyInfo_t *GetProficiencyValues();

	void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator )
	{
		(...) // Троеточие подразумевает код который нам видить не надо 
	}

	DECLARE_ACTTABLE();

protected:

	Vector	m_vecTossVelocity;
	float	m_flNextGrenadeCheck;
};

Кстати, GetMinBurst() и GetMaxBurst() это минимальное и максимально кол-во выстрелов для NPC, т.е если вы хотите чтобы комбин пулял как бешеный например из weapon_pistol то добавьте эти методы если их нет, либо поменяйте значение, например 10-15 вместо 2.

Вернёмся к нашим апельсинам.

добавим после

extern ConVar    sk_plr_dmg_smg1_grenade;

дефайн:

#define SMG_REACH_FULL_SPREAD_TIME 2.0f // Lolmen : время достижения максимального разброса

и после

	int		GetMaxBurst() { return 5; }

методы :

	// Lolmen : Возвращает конус разброса от мин к макс по времени
	Vector		GetDynamicSpread( const Vector &min, const Vector &max );
	void		UpdateAccuracy( void ); // обновляет точность
	void		ItemPreFrame( void ); // пре кадровая функа
	void		PrimaryAttack( void ); // видоизменим от базового класса

естественно не забудем про переменные и после переменной :

	float	m_flNextGrenadeCheck;

вставим такую вещь :

	float	m_flAccuracyPercent;

и естественно не забудем её продекларировать для SAVE/LOAD вещей.

найдём :

	DEFINE_FIELD( m_flNextGrenadeCheck, FIELD_TIME ),

и после него впишем :

	DEFINE_FIELD( m_flAccuracyPercent,		FIELD_FLOAT ), // идёт как переменная с плавающей точкой

так. Теперь идём ниже (скроллим наш код) и начинаем после :


void CWeaponSMG1::AddViewKick( void )
{
	... // опущено
}

Писать такой вот аццкий код :


void CWeaponSMG1::ItemPreFrame( void )
{
	UpdateAccuracy();
	BaseClass::ItemPreFrame();
}
void CWeaponSMG1::UpdateAccuracy( void )
{
	// Lolmen : указатель на игрока
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); 
	if ( !pPlayer ){ return; } // Если игрока нету то ничего не делаем

	// Проверка на разъедание накопленной неточности
	if ( !( pPlayer->m_nButtons & IN_ATTACK ) )
	{
		m_flAccuracyPercent -= clamp( gpGlobals->frametime, 0.1f, 0.2f );
		m_flAccuracyPercent = clamp( m_flAccuracyPercent, 0.0f, SMG_REACH_FULL_SPREAD_TIME );
	}
}
void CWeaponSMG1::PrimaryAttack( void )
{
	BaseClass::PrimaryAttack();

	// добавляем с каждой аттакой по процентной доле времени между выстрелами
	m_flAccuracyPercent += GetFireRate();
}
Vector CWeaponSMG1::GetDynamicSpread( const Vector &min, const Vector &max )
{
	float ramp = RemapValClamped( m_flAccuracyPercent, 0.0f, SMG_REACH_FULL_SPREAD_TIME, 0.0f, 1.0f );
	Vector ret; VectorLerp( min, max, ramp, ret );
	return ret;
}

Теперь найдём такой кусок кода :


bool CWeaponSMG1::Reload( void )
{
	bool fRet;
	float fCacheTime = m_flNextSecondaryAttack;

	fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD );
	if ( fRet )
	{
		// Undo whatever the reload process has done to our secondary
		// attack timer. We allow you to interrupt reloading to fire
		// a grenade.
		m_flNextSecondaryAttack = GetOwner()->m_flNextAttack = fCacheTime;

		WeaponSound( RELOAD );
	}

	return fRet;
}

И видо изменим :


bool CWeaponSMG1::Reload( void )
{
	bool fRet;
	float fCacheTime = m_flNextSecondaryAttack;

	fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD );
	if ( fRet )
	{
		// Undo whatever the reload process has done to our secondary
		// attack timer. We allow you to interrupt reloading to fire
		// a grenade.
		m_flNextSecondaryAttack = GetOwner()->m_flNextAttack = fCacheTime;
		// Lolmen : обнуляем щётчик
		m_flAccuracyPercent = 0.0f;
		WeaponSound( RELOAD );
	}

	return fRet;
}

Теперь находим кусок в начале :


	virtual const Vector& GetBulletSpread( void )
	{
		static const Vector cone = VECTOR_CONE_5DEGREES;
		return cone;
	}

И начинаем знакомую по началу тутора процедуру :


	virtual const Vector& GetBulletSpread( void )
	{
		// Lolmen : Берём указатель на игрока
		CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); 
		if ( !pPlayer ){ return vec3_origin; } // если он нулевой ничего не делаем

		static Vector cone = VECTOR_CONE_PRECALCULATED;
		// Игрок стоит на земле
		if ( ( pPlayer->GetFlags() & FL_ONGROUND ) != 0 )
		{
			if ( ( pPlayer->GetFlags() & FL_DUCKING ) != 0 )
			{	// игрок сидит
				cone = GetDynamicSpread( VECTOR_CONE_1DEGREES, VECTOR_CONE_3DEGREES );
			}
			else
			{
				if ( pPlayer->GetAbsVelocity().Length2D() > 120 )
				{	// игрок двигается быстро
					cone = GetDynamicSpread( VECTOR_CONE_5DEGREES, VECTOR_CONE_8DEGREES );
				}
				else
				{	// Игрок медленно двигается или стоит
					cone = GetDynamicSpread( VECTOR_CONE_3DEGREES, VECTOR_CONE_5DEGREES );
				}
			}
		} // Игрок в прыжке
		else
		{
			cone = GetDynamicSpread( VECTOR_CONE_7DEGREES, VECTOR_CONE_10DEGREES );
		}

		return cone;
	}

Теперь, поэкспереминтировав с силой значений можно получить требуемый результат. главное что у GetDynamicSpread первый аргумент должен быть меньше второго но больше ноля...

Если возникнут проблемы, обращаемся в форум.

Автор: Lolmen