Shambler team

Делаем Displacer а-ля Opposing Force


программируем в коде мода для игры Half-Life

Версия статьи — бета 0.8
Ну что же, соскучились Вы по нашим статьям? Если да, то для Вас приятная новость — сейчас руководства будут добавляться на сайт, поэтому следите ;)

Итак, я уверен 999‰ всех халферов играли в Opposing Force и пользовались оружием-телепортером. С каждым годом всё больше и больше увеличивается количество людей, желающих создать свой супер пупер мод под Халфу, и довольно многие хотят видеть в своём моде это оружие. Однако жадные пофигисты из Gearbox не спешат поделиться кодом этого орудия (ровно как и всего остального добавочного оружия, монстров и эффектов из OF), то модерам приходиться огорчаться и перекраивать свои планы...

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

Опытным программистам вставить недостающие строки не составит труда. Новичкам придётся немного помучиться, но оно и к лучшему — научатся не только нажимать Ctrl+С -> Ctrl+V (или Ctrl+Ins -> Shift+Ins), но и немного думать и причём головой, и что самое главное — своей ;)
А не спинным мозгом. Если что-то пошло не так — не опускайте руки, постепенно разберётесь.

Итак — добавляем дефайн в файл weapons.h

Добавляем файл displacer.cpp в проект.

Добавляем строчку прекеша в weapons.cpp

Регистрируем событие как на клиенте, так и на сервере (хотя кажись нужно только на клиенте — не помню) — если не помните (или не знаете), как это делать, то просмотрите руководство по созданию нового оружия.


Вот код события, за что спасибо XWiderу за предложенную функцию доступа к аттачментам (новичкам для затравки: редактор кода сразу обратит внимание на число закрывающих фигурных скобок):

void EV_SpinDisplacer(event_args_t *args) //Original code idea by Xwider
{
if (EV_IsLocal(args->entindex))
{
gEngfuncs.pEventAPI->EV_WeaponAnimation(DISPLACER_SPINUP, 2);
cl_entity_t *view = gEngfuncs.GetViewModel();
if (view != NULL)
{
float life = 1.14;

gEngfuncs.pEfxAPI->R_BeamEnts(view->index | 0x1000, view->index | 0x2000, args->iparam2, life, 0.8, 0.5, 0.5, 0.6, 0, 10, 2, 10, 0);
gEngfuncs.pEfxAPI->R_BeamEnts(view->index | 0x1000, view->index | 0x3000, args->iparam2, life, 0.8, 0.5, 0.5, 0.6, 0, 10, 2, 10, 0);
gEngfuncs.pEfxAPI->R_BeamEnts(view->index | 0x1000, view->index | 0x4000, args->iparam2, life, 0.8, 0.5, 0.5, 0.6, 0, 10, 2, 10, 0);
		}
	}

	if (args->bparam2 == 0) // sound mode
gEngfuncs.pEventAPI->EV_PlaySound(args->entindex, args->origin, CHAN_WEAPON, "weapons/displacer_spin.wav", 1.0, ATTN_NORM, 0, PITCH_NORM);
	else
gEngfuncs.pEventAPI->EV_PlaySound(args->entindex, args->origin, CHAN_WEAPON, "weapons/displacer_spin2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM);	
запихиваем его в файл ev_hldm.cpp. И ещё вот этот подгрузок в файл ev_hldm.h:
enum displacer_e 
{
	DISPLACER_IDLE1 = 0,
	DISPLACER_IDLE2,
	DISPLACER_SPINUP,
	DISPLACER_SPIN,
	DISPLACER_FIRE,
	DISPLACER_DRAW,
	DISPLACER_HOLSTER,
};
называйте событие, как хотите, но на сервере он называется displacer.sc и точка! (естественно можете изменить имя если хочется).

Bот код файла displacer.cpp

/***
*
*	Copyright (c) 2004 Shambler Team. All rights reserved.
*	
****/

#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "nodes.h"
#include "player.h"
#include "gamerules.h"
#include "shake.h"
#include "effects.h"

enum displacer_e 
{
	DISPLACER_IDLE1 = 0,
	DISPLACER_IDLE2,
	DISPLACER_SPINUP,
	DISPLACER_SPIN,
	DISPLACER_FIRE,
	DISPLACER_DRAW,
	DISPLACER_HOLSTER,
};

class CDisplacer : public CBasePlayerWeapon
{
public:
	int		Save( CSave &save; );
	int		Restore( CRestore &restore; );
	static	TYPEDESCRIPTION m_SaveData[];

	void Spawn( void );
	void Precache( void );
	int iItemSlot( void ) { return 4; }
	int GetItemInfo(ItemInfo *p);
	int AddToPlayer( CBasePlayer *pPlayer );

	BOOL Deploy( void );
	void Holster( int skiplocal = 0 );

	void PrimaryAttack( void );
	void SecondaryAttack (void);
	void EXPORT SpinUp( void );
	void EXPORT Teleport( void );
	void EXPORT Fire( void );
	void WeaponIdle( void );

	int m_iAttackMode; //	no need save/restore this. g-cont
private:
	unsigned short m_usDisplacer;
	int m_iBeam;
	int m_iPlace;
};

LINK_ENTITY_TO_CLASS( weapon_displacer, CDisplacer );

//===========================
//
//	TeleBall code
//
//===========================

//class CDispBall : public CGrenade //закомментирован первый вариант
class CDispBall : public CBaseEntity //Special for Ghoul [BB] :)
{
public:
	void Spawn( void );
	void Precache( void );
	void Explode( TraceResult *pTrace );
	void RemoveBall (void);
	void EXPORT ExplodeTouch( CBaseEntity *pOther );

	void EXPORT BallThink( void );
	static CDispBall *CreateDispBall( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner, CDisplacer *pLauncher );

	int m_iDispRing;
	BOOL LockRing;
};

LINK_ENTITY_TO_CLASS( dispball, CDispBall );

CDispBall *CDispBall::CreateDispBall( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner, CDisplacer *pLauncher )
{
	CDispBall *pDispBall = GetClassPtr( (CDispBall *)NULL );

	UTIL_SetOrigin( pDispBall->pev, vecOrigin );
	pDispBall->pev->angles = vecAngles;
	pDispBall->Spawn();
	pDispBall->SetTouch( CDispBall::ExplodeTouch );
	pDispBall->pev->owner = pOwner->edict();

	return pDispBall;
}

void CDispBall :: Spawn( void )
{
	Precache( );
	pev->movetype = MOVETYPE_FLY;
	pev->solid = SOLID_BBOX;

	SET_MODEL(ENT(pev), "sprites/exit1.spr"); 
	pev->scale = .5;

	UTIL_SetSize(pev, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ) );
	UTIL_SetOrigin( pev, pev->origin );
	pev->rendermode = kRenderTransAdd;
	pev->renderamt = 255;

	pev->classname = MAKE_STRING("dispball");

	SetThink( BallThink );
	SetTouch( ExplodeTouch );

	pev->angles.x -= 0;
	UTIL_MakeVectors( pev->angles );
	pev->angles.x = -(pev->angles.x + 0);
	pev->velocity = gpGlobals->v_forward * 500;
	pev->nextthink = 0.5;

	pev->dmg = 1000;
}

void CDispBall :: Precache( void )
{
	PRECACHE_MODEL("sprites/exit1.spr");
	PRECACHE_MODEL ("sprites/plasma.spr");

	PRECACHE_SOUND("weapons/displacer_teleport.wav");
	m_iDispRing = PRECACHE_MODEL ("sprites/displacer_ring.spr");
}

void CDispBall :: BallThink( void )
{
	CBeam *pBeam;
	TraceResult tr;
	Vector vecDest;
	float flDist = 1.0;

	for (int i = 0; i < 10; i++)
	{
		Vector vecDir = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) );
		vecDir = vecDir.Normalize();
		TraceResult 		tr1;
		UTIL_TraceLine( pev->origin, pev->origin + vecDir * 1024, ignore_monsters, ENT(pev), &tr1; );
		if (flDist > tr1.flFraction)
		{
			tr = tr1;
			flDist = tr.flFraction;
		}
	}

	if ( flDist == 1.0 ) return;

	pBeam = CBeam::BeamCreate("sprites/plasma.spr",200);
	pBeam->PointEntInit( tr.vecEndPos, entindex() );
	pBeam->SetStartPos( tr.vecEndPos );
	pBeam->SetEndEntity( entindex() );
	pBeam->SetColor( 90, 170, 16 );
	pBeam->SetNoise( 65 );
	pBeam->SetBrightness( 255 );
	pBeam->SetWidth( 30 );
	pBeam->SetScrollRate( 35 );
	pBeam->LiveForTime( 1 );

	pev->frame += 1; //animate teleball
	if(pev->frame > 24)
		pev->frame = fmod( pev->frame, 24 );

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

void CDispBall::ExplodeTouch( CBaseEntity *pOther )
{
	TraceResult tr;
	Vector		vecSpot; // trace starts here!

	pev->enemy = pOther->edict();

	vecSpot = pev->origin - pev->velocity.Normalize() * 32;
	UTIL_TraceLine( vecSpot, vecSpot + pev->velocity.Normalize() * 64, dont_ignore_monsters, ENT(pev), &tr; );

	Explode( &tr; );
}

void CDispBall::Explode( TraceResult *pTrace )
{
	if(!LockRing) //for partially fix strange bug in HL engine (for full fix teleball MUST be moved to client side). g-cont
	{
		MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin );
		WRITE_BYTE( TE_BEAMCYLINDER );
		WRITE_COORD( pev->origin.x);
		WRITE_COORD( pev->origin.y);
		WRITE_COORD( pev->origin.z);
		WRITE_COORD( pev->origin.x);
		WRITE_COORD( pev->origin.y);
		WRITE_COORD( pev->origin.z + 800);
		WRITE_SHORT( m_iDispRing );
		WRITE_BYTE( 0 );     // startframe
		WRITE_BYTE( 10 );   // framerate
		WRITE_BYTE( 3 );     // life
		WRITE_BYTE( 20 );   // width
		WRITE_BYTE( 0 );     // noise
		WRITE_BYTE( 255 );  // red, green, blue
		WRITE_BYTE( 255 );  // red, green, blue
		WRITE_BYTE( 255 );  // red, green, blue
		WRITE_BYTE( 255 );  //brightness
		WRITE_BYTE( 0 );     // speed
		MESSAGE_END();
	}

	LockRing = TRUE;
	pev->velocity = g_vecZero;

	SetThink (RemoveBall);
	pev->nextthink = gpGlobals->time + 0.6;

}

void CDispBall::RemoveBall( void )
{
	EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/displacer_teleport.wav", 0.9, ATTN_NORM);
	pev->effects |= EF_NODRAW;

	entvars_t *pevOwner;
	if ( pev->owner )
		pevOwner = VARS( pev->owner );
	else
		pevOwner = NULL;
	pev->owner = NULL;

	UTIL_Remove( this );
	::RadiusDamage( pev->origin, pev, pevOwner, pev->dmg, 200, CLASS_NONE, DMG_ENERGYBEAM );
}

//===========================
//
//	Displacer code
//
//===========================

void CDisplacer::Spawn( )
{
	Precache( );
	m_iId = WEAPON_DISPLACER;
	SET_MODEL(ENT(pev), "models/w_displacer.mdl");

	m_iDefaultAmmo = 60;
	FallInit(); // get ready to fall down.
}

TYPEDESCRIPTION	CDisplacer::m_SaveData[] = 
{
	DEFINE_FIELD( CDisplacer, m_iPlace, FIELD_INTEGER ), //Remember last teleportation point. g-cont
};
IMPLEMENT_SAVERESTORE( CDisplacer, CBasePlayerWeapon );

int CDisplacer::AddToPlayer( CBasePlayer *pPlayer )
{
	if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) )
	{
		MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev );
		WRITE_BYTE( m_iId );
		MESSAGE_END();
		return TRUE;
	}
	return FALSE;
}

BOOL CDisplacer::Deploy( void )
{
	return DefaultDeploy( "models/v_displacer.mdl", "models/p_displacer.mdl", DISPLACER_DRAW, "gauss" );
}

void CDisplacer::Holster( int skiplocal )
{
	m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.5;
	SendWeaponAnim( DISPLACER_HOLSTER );
}

int CDisplacer::GetItemInfo(ItemInfo *p)
{
	p->pszName = STRING(pev->classname);
	p->pszAmmo1 = "uranium";
	p->iMaxAmmo1 = URANIUM_MAX_CARRY;
	p->pszAmmo2 = NULL;
	p->iMaxAmmo2 = -1;
	p->iMaxClip = WEAPON_NOCLIP;
	p->iSlot = 3;
	p->iPosition = 4;
	p->iId = m_iId = WEAPON_DISPLACER;
	p->iFlags = 0;
	p->iWeight = DISPLACER_WEIGHT;

	return 1;
}

void CDisplacer::Precache( void )
{
	PRECACHE_MODEL("models/w_displacer.mdl");
	PRECACHE_MODEL("models/v_displacer.mdl");
	PRECACHE_MODEL("models/p_displacer.mdl");

	PRECACHE_SOUND("weapons/displacer_fire.wav");
	PRECACHE_SOUND ("buttons/button10.wav");
	PRECACHE_SOUND ("weapons/displacer_self.wav");

	UTIL_PrecacheOther( "dispball" );

	m_iBeam = PRECACHE_MODEL("sprites/plasma.spr");
	m_usDisplacer = PRECACHE_EVENT( 1, "events/displacer.sc" );
}

void CDisplacer::SpinUp ( void )
{
	int flags;
	flags = 0;

	if (!m_iAttackMode) SetThink (CDisplacer::Fire);
	else SetThink (CDisplacer::Teleport);

	//spinup event
	PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usDisplacer, 0.0, (float *)&g;_vecZero, (float *)&g;_vecZero, 0.0, 0.0, 0, m_iBeam, 0, m_iAttackMode);
	
	pev->nextthink = gpGlobals->time + 1.1;
	m_flTimeWeaponIdle = gpGlobals->time + 1.15;
}

void CDisplacer::PrimaryAttack( void )
{
	if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] < 20) //Check for ammo
	{
		EMIT_SOUND( edict(), CHAN_BODY, "buttons/button10.wav", 1, ATTN_NORM );
		m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0;
		return;
	}

	m_iAttackMode = FALSE; //We set attack mode in first mode

	SetThink (CDisplacer::SpinUp);

	m_flNextPrimaryAttack = gpGlobals->time + 2.0;
	m_flTimeWeaponIdle = gpGlobals->time + 1.5;
	pev->nextthink = gpGlobals->time + 0.1;
}

void CDisplacer::SecondaryAttack ( void )
{
	if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] < 60)
	{
		EMIT_SOUND( edict(), CHAN_BODY, "buttons/button10.wav", 1, ATTN_NORM );
		m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0;
		return;
	}

	if (m_iAttackMode) return;
	m_iAttackMode = TRUE; //We set attack mode in second mode

	SetThink (CDisplacer::SpinUp);

	m_flNextSecondaryAttack = gpGlobals->time + 2.0;
	m_flTimeWeaponIdle = gpGlobals->time + 1.5;
	pev->nextthink = gpGlobals->time + 0.1;
}

void CDisplacer::Fire (void)
{
	SendWeaponAnim( DISPLACER_FIRE );
	m_pPlayer->SetAnimation( PLAYER_ATTACK1 );

	Vector vecSrc = m_pPlayer->GetGunPosition( ) + gpGlobals->v_forward * 16 + gpGlobals->v_right * 2 + gpGlobals->v_up * -5;
	m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= 20;

	EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/displacer_fire.wav", 0.9, ATTN_NORM );
	CDispBall *pDispBall = CDispBall::CreateDispBall( vecSrc, m_pPlayer->pev->v_angle, m_pPlayer, this );
}


void CDisplacer:: Teleport ( void )
{	
	m_iPlace = !m_iPlace;

	CBaseEntity *pSpot = NULL;
	m_iAttackMode = FALSE; //reset firemode

	if ( m_iPlace )	//Xen and earth target
		pSpot = UTIL_FindEntityByClassname( pSpot, "info_xen" );
	else
		pSpot = UTIL_FindEntityByClassname( pSpot, "info_earth" );

	if (pSpot)
	{
		UTIL_ScreenFade( m_pPlayer, Vector(0, 200, 0), 0.5, 0.5, 255, FFADE_IN );

		m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= 60;
		Vector tmp = pSpot->pev->origin;
		tmp.z -= m_pPlayer->pev->mins.z;
		tmp.z++;
		UTIL_SetOrigin( m_pPlayer->pev, tmp );

		m_pPlayer->pev->angles = pSpot->pev->angles;
		m_pPlayer->pev->velocity = m_pPlayer->pev->basevelocity = g_vecZero;

		EMIT_SOUND( edict(), CHAN_BODY, "weapons/displacer_self.wav", 1, ATTN_NORM );
		SendWeaponAnim( DISPLACER_FIRE );
	}
	else
	{
		SendWeaponAnim( DISPLACER_SPINUP );
		EMIT_SOUND( edict(), CHAN_BODY, "buttons/button10.wav", 1, ATTN_NORM );	
		m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.3;
	}

 	m_flNextSecondaryAttack = gpGlobals->time + 2.0;
	m_flTimeWeaponIdle = gpGlobals->time + 1.0;
}

void CDisplacer::WeaponIdle( void )
{
	int iAnim;

	m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES );

	if ( m_flTimeWeaponIdle > gpGlobals->time )
		return;

	float flRand = RANDOM_FLOAT(0,1);

	if ( flRand <= 0.5 )
	{
		iAnim = DISPLACER_IDLE1;
		m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT(10,15);
	}
	else 
	{
		iAnim = DISPLACER_IDLE2;
		m_flTimeWeaponIdle = gpGlobals->time + 3;
	}

	SendWeaponAnim( iAnim );
}

K вящей радости любителей телепортнуться можно ещё добавить две строчки в файл triggers.cpp, дабы Half-Life не ругалась, а вела себя тихо и примерно:

LINK_ENTITY_TO_CLASS( info_xen,   CPointEntity );  //Two teleporartion points
LINK_ENTITY_TO_CLASS( info_earth, CPointEntity );

Xотя какая нафиг разница куда вы впишете эти две строчки?
Да хоть в h_export.cpp, но только чур в самый конец, если вам от этого легче.

Внимание! Жалобы, типа "у меня не работает" не принимаються. Только предложения.

Hа этом всё, спасибо за внимание :)

З.Ы. К коду очевидно, что понадобятся следующие ресурсы:
models/w_displacer.mdl
models/v_displacer.mdl
models/p_displacer.mdl

weapons/displacer_fire.wav
weapons/displacer_self.wav
weapons/displacer_spin.wav
weapons/displacer_spin2.wav
weapons/displacer_teleport.wav

sprites/displacer_ring.spr

и events/displacer.sc

Скачать эти ресурсы можете в архиве displ_res.7z [252 Кб].

А эти три уже и так есть в Valve\pak0.pak :
buttons/button10.wav
sprites/exit1.spr
sprites/plasma.spr

g-cont и компания
2005 Shambler Team
  Counter.CO.KZ -     !
Hosted by uCoz