ЗЕРКАЛА В HALF-LIFE

Для того, чтобы создать зеркало, нужно иметь соответствующим образом ориентированную поверхность. Для этого создается браш func_mirror. В его свойствах нужно указать такие параметры, как ориентацию и радиус, в котором объекты отражаются, относительно центра браша. Этот код поместите в файл bmodels.cpp:

#include "weapons.h"

#define SF_MIRROR_STARTOFF 0x01
#define SF_MIRROR_PASSABLE 0x02

class CFuncMirror : public CBaseEntity
{
public:
void Spawn( void );
void Precache( void );
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
void Think( void );
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];
private:
int m_iInitialRenderMode;
BOOL bSent;
};

TYPEDESCRIPTION CFuncMirror::m_SaveData[] =
{
DEFINE_FIELD( CFuncMirror, m_iInitialRenderMode, FIELD_INTEGER ),
};
IMPLEMENT_SAVERESTORE( CFuncMirror, CBaseEntity );
LINK_ENTITY_TO_CLASS( func_mirror, CFuncMirror );

void CFuncMirror :: Spawn( void )
{
pev->angles = g_vecZero;
pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything

if (pev->spawnflags & SF_MIRROR_PASSABLE)
pev->solid = SOLID_NOT;
else
pev->solid = SOLID_BSP;

if (pev->spawnflags & SF_MIRROR_STARTOFF)
pev->impulse = 0;
else
pev->impulse = 1;

Precache();

SET_MODEL( ENT(pev), STRING(pev->model) );

m_iInitialRenderMode = pev->rendermode;

if (pev->frags==0 && (pev->armorvalue < pev->size.y || pev->armorvalue < pev->size.z))
ALERT(at_console,"WARNING: Mirror radius (%f) is less than dimension (%f x %f)!\n",pev->armorvalue,pev->size.y,pev->size.z);
if (pev->frags==1 && (pev->armorvalue < pev->size.x || pev->armorvalue < pev->size.z))
ALERT(at_console,"WARNING: Mirror radius (%f) is less than dimension (%f x %f)!\n",pev->armorvalue,pev->size.x,pev->size.z);
if (pev->frags==2 && (pev->armorvalue < pev->size.x || pev->armorvalue < pev->size.y))
ALERT(at_console,"WARNING: Mirror radius (%f) is less than dimension (%f x %f)!\n",pev->armorvalue,pev->size.x,pev->size.y);

pev->nextthink = pev->ltime + 1.0;
}

void CFuncMirror :: Precache( void )
{
bSent = FALSE;
}

void CFuncMirror :: Think( void )
{
if (bSent )
{
pev->nextthink = pev->ltime + 0.5;
return;
}

if (pev->impulse)
{
if (pev->rendermode != m_iInitialRenderMode)
pev->rendermode = m_iInitialRenderMode;

if (UTIL_PlayerByIndex(1))
{
PLAYBACK_EVENT_FULL(FEV_RELIABLE|FEV_GLOBAL,
edict(),
m_usMirror,
0.0,
(float *)&Center(),
(float *)&g_vecZero,
0.0,
0.0,
pev->armorvalue, //distance
pev->frags, //type (0-X,1-Y,2-Z)
1, //enabled
0);

bSent = TRUE;
}

}
else
{
if (pev->rendermode != kRenderNormal)
{
pev->rendermode = kRenderNormal;

PLAYBACK_EVENT_FULL(FEV_RELIABLE|FEV_GLOBAL,
edict(),
m_usMirror,
0.0,
(float *)&Center(),
(float *)&g_vecZero,
0.0,
0.0,
pev->armorvalue, //distance
pev->frags, //type (0-X,1-Y,2-Z)
0, //enabled
0);
}

bSent = TRUE;
}
pev->nextthink = pev->ltime + 0.5;
}

void CFuncMirror :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
if ( ShouldToggle( useType, (int)(pev->frame)) )
{
pev->frame = 1 - pev->frame;
pev->impulse = 1 - pev->impulse;
bSent = FALSE;
}
}

Для правильного отражения игрока в режиме first person, создаем вспомогательную энтитю - player_marker. Нас интересует только ее модель (player_marker.mdl), так что в принципе можно использовать cycler или monster_furniture. Модель тоже может быть любой, она не рисуется, главное - ее название. Не забудьте поместить ее в папку 'valve/models'. Эти энтити нужно поместить по одной за каждым зеркалом.

class CPlayerMarker : public CBaseEntity
{
public:
void Spawn( void );
void Precache( void );
};

LINK_ENTITY_TO_CLASS( player_marker, CPlayerMarker );

void CPlayerMarker :: Spawn( void )
{
Precache();
SET_MODEL( ENT(pev), "models/player_marker.mdl" );
}

void CPlayerMarker::Precache( void )
{
PRECACHE_MODEL( "models/player_marker.mdl" );
}

Так как зеркало посылается на клиент через событие, то нужно сделать еще следующие действия:

1) В файле weapons.h :
extern DLL_GLOBAL unsigned short m_usMirror;
2) В файле weapons.cpp :
DLL_GLOBAL unsigned short m_usMirror;
3) Там же, в функции W_Precache:
m_usMirror = PRECACHE_EVENT(1,"events/mirror.sc");
4) Создать файл "mirror.sc" в папке "events"

Теперь перейдем к клиентской части. Вначале нам нужно подцепить событие - установка зеркала. Это делается стандартным методом добавления нового события:

1) В файле hl/hl_events.cpp:
В секции экспорта:
void EV_Mirror( struct event_args_s *args );
В функции Game_HookEvents:
gEngfuncs.pfnHookEvent( "events/mirror.sc", EV_Mirror );
2) В файле hl/ev_hldm.cpp:
В секции экспорта:
void EV_Mirror( struct event_args_s *args );
В конец файла:
void EV_Mirror( event_args_t *args )
{
vec3_t org;
bool bNew = true;

VectorCopy(args->origin,org);
float dist = (float)args->iparam1;
int type = args->iparam2;
int bEnabled = args->bparam1;

//we have mirror
if (gHUD.numMirrors)
{
for (int ic=0;ic<32;ic++)
{
if (gHUD.Mirrors[ic].origin[0] == org[0] &&
gHUD.Mirrors[ic].origin[1] == org[1] &&
gHUD.Mirrors[ic].origin[2] == org[2])
{
if (bEnabled && !gHUD.Mirrors[ic].enabled )
gHUD.numMirrors++;
else if (!bEnabled && gHUD.Mirrors[ic].enabled )
gHUD.numMirrors--;
gHUD.Mirrors[ic].enabled = bEnabled;
bNew = false;
break;
}
}
}

if (bNew)
{
if (gHUD.numMirrors >= 32)
ConsolePrint("ERROR: Can't register mirror, maximum 32 allowed!\n");
else
{
VectorCopy(org,gHUD.Mirrors[gHUD.numMirrors].origin);
gHUD.Mirrors[gHUD.numMirrors].type = type;
gHUD.Mirrors[gHUD.numMirrors].enabled = bEnabled;
gHUD.Mirrors[gHUD.numMirrors].radius = dist;
gHUD.numMirrors++;
}
}
}

Сами зеркала мы храним в экземпляре класса CHud. Откройте файл hud.h и перейдите к его объявлению. Перед ним вставим описание структуры:

typedef struct cl_mirror_s
{
vec3_t origin;
int enabled;
float radius;
int type;
} cl_mirror_t;

Теперь в самом объявлении, в разделе public, добавляем:

struct cl_mirror_s Mirrors[32]; //Limit - 32 mirrors!
int numMirrors;

Теперь нам нужно учесть, что при старте нового уровня или рестарте этого зеркала создаются заново. Вставим строку
numMirrors = 0;
в телах следующих функций
CHud :: VidInit
CHud :: MsgFunc_ResetHUD

Теперь все готово, чтобы делать собственно отражение. Откройте файл StudioModelRenderer.h и в конце объявления класса допишите:
int mirror_id;
bool b_PlayerMarkerParsed;
int m_nCachedFrameCount;

Откройте файл StudioModelRenderer.cpp, найдите функцию StudioDrawModel, и в самом конце ее (перед return) добавьте код:

if (gHUD.numMirrors>0 && !(m_pCurrentEntity->model->name[7]=='v' && m_pCurrentEntity->model->name[8]=='_'))
{
int renderfx = m_pCurrentEntity->curstate.renderfx;

for (int ic=0;ic < gHUD.numMirrors;ic++)
{
//Parsing mirror

if (!gHUD.Mirrors[ic].enabled)
{
continue;
}

vec3_t delta;
float dist;
VectorSubtract(gHUD.Mirrors[ic].origin,m_pCurrentEntity->origin,delta);
dist = Length(delta);

if (gHUD.Mirrors[ic].radius < dist)
{
continue;
}

m_pCurrentEntity->curstate.renderfx = 100;

mirror_id = ic;


gEngfuncs.pTriAPI->CullFace( TRI_NONE );

if (flags & STUDIO_RENDER)
{
// see if the bounding box lets us trivially reject, also sets
if (!IEngineStudio.StudioCheckBBox ())
return 0;

(*m_pModelsDrawn)++;
(*m_pStudioModelCount)++; // render data cache cookie

if (m_pStudioHeader->numbodyparts == 0)
return 1;
}

if (m_pCurrentEntity->curstate.movetype == MOVETYPE_FOLLOW)
{
StudioMergeBones( m_pRenderModel );
}
else
{
StudioSetupBones( );
}
StudioSaveBones( );

if (flags & STUDIO_EVENTS)
{
StudioCalcAttachments( );
IEngineStudio.StudioClientEvents( );
// copy attachments into global entity array
if ( m_pCurrentEntity->index > 0 )
{
cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index );

memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 );
}
}

if (flags & STUDIO_RENDER)
{
lighting.plightvec = dir;
IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting );

IEngineStudio.StudioEntityLight( &lighting );

// model and frame independant
IEngineStudio.StudioSetupLighting (&lighting);

// get remap colors
m_nTopColor = m_pCurrentEntity->curstate.colormap & 0xFF;
m_nBottomColor = (m_pCurrentEntity->curstate.colormap & 0xFF00) >> 8;

IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor );

StudioRenderModel( );
}

gEngfuncs.pTriAPI->CullFace( TRI_FRONT );

m_pCurrentEntity->curstate.renderfx = renderfx;

}

}

Этот код отвечает за отражение всех моделей, кроме игрока. Теперь перейдите в начало этой же функции. Добавьте после строки:

IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale );

Этот код:

if (m_nCachedFrameCount != m_nFrameCount)
{
b_PlayerMarkerParsed = false;
m_nCachedFrameCount = m_nFrameCount;
}

if (!strcmp(m_pCurrentEntity->model->name,"models/player_marker.mdl"))
{
if (!b_PlayerMarkerParsed)
{
cl_entity_t *plr = gEngfuncs.GetLocalPlayer();
entity_state_t *pstate = IEngineStudio.GetPlayerState( 0 );

int save_interp;
save_interp = m_fDoInterp;
m_fDoInterp = 0;

// draw as though it were a player
int newflags = 3;
newflags |= 2048;

m_pCurrentEntity = plr;

StudioDrawPlayer( newflags, pstate );

b_PlayerMarkerParsed = true;
m_fDoInterp = save_interp;
}
return 1;
}

Переходим в функцию StudioDrawPlayer. В начале ее измените:

m_pCurrentEntity = IEngineStudio.GetCurrentEntity();

на вот такое условие:

if (!(flags & 2048))
{
m_pCurrentEntity = IEngineStudio.GetCurrentEntity();
}

Далее, измените строку

m_pRenderModel = IEngineStudio.SetupPlayerModel( m_nPlayerIndex );

на следующий код:

if (!(flags & 2048))
m_pRenderModel = IEngineStudio.SetupPlayerModel( m_nPlayerIndex );
else
m_pRenderModel = m_pCurrentEntity->model;

далее, после строки

IEngineStudio.SetRenderModel( m_pRenderModel );

Измените все до конца тела функции:

if (gHUD.numMirrors>0)
{
//m_pCurrentEntity->curstate.effects = 0;
int savedrenderfx = m_pCurrentEntity->curstate.renderfx;
m_pCurrentEntity->curstate.renderfx = 100;

for (int ic=0;ic < gHUD.numMirrors;ic++)
{
//Parsing mirror

m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel);
IEngineStudio.StudioSetHeader( m_pStudioHeader );
IEngineStudio.SetRenderModel( m_pRenderModel );

if (!gHUD.Mirrors[ic].enabled)
{
continue;
}

vec3_t delta;
float dist;
VectorSubtract(gHUD.Mirrors[ic].origin,m_pCurrentEntity->origin,delta);
dist = Length(delta);

if (gHUD.Mirrors[ic].radius < dist)
{
continue;
}

mirror_id = ic;

gEngfuncs.pTriAPI->CullFace( TRI_NONE );

if (flags & STUDIO_RENDER)
{
// see if the bounding box lets us trivially reject, also sets
if (!IEngineStudio.StudioCheckBBox ())
return 0;

(*m_pModelsDrawn)++;
(*m_pStudioModelCount)++; // render data cache cookie

if (m_pStudioHeader->numbodyparts == 0)
return 1;
}

m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex );
StudioSetupBones( );
StudioSaveBones( );
m_pPlayerInfo->renderframe = m_nFrameCount;

m_pPlayerInfo = NULL;

if (flags & STUDIO_RENDER)
{
if (m_pCvarHiModels->value && m_pRenderModel != m_pCurrentEntity->model )
{
// show highest resolution multiplayer model
m_pCurrentEntity->curstate.body = 255;
}

lighting.plightvec = dir;
IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting );

IEngineStudio.StudioEntityLight( &lighting );

// model and frame independant
IEngineStudio.StudioSetupLighting (&lighting);

m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex );

// get remap colors
m_nTopColor = m_pPlayerInfo->topcolor;
if (m_nTopColor < 0)
m_nTopColor = 0;
if (m_nTopColor > 360)
m_nTopColor = 360;
m_nBottomColor = m_pPlayerInfo->bottomcolor;
if (m_nBottomColor < 0)
m_nBottomColor = 0;
if (m_nBottomColor > 360)
m_nBottomColor = 360;

IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor );

StudioRenderModel();

m_pPlayerInfo = NULL;

if (pplayer->weaponmodel)
{
cl_entity_t saveent = *m_pCurrentEntity;
model_t *pweaponmodel = IEngineStudio.GetModelByIndex( pplayer->weaponmodel );
m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (pweaponmodel);
IEngineStudio.StudioSetHeader( m_pStudioHeader );
StudioMergeBones( pweaponmodel );
IEngineStudio.StudioSetupLighting (&lighting);
gEngfuncs.pTriAPI->CullFace( TRI_NONE );
StudioRenderModel( );
gEngfuncs.pTriAPI->CullFace( TRI_FRONT );
StudioCalcAttachments( );
*m_pCurrentEntity = saveent;
}
}

} //end for

gEngfuncs.pTriAPI->CullFace( TRI_FRONT );

m_pCurrentEntity->curstate.renderfx = savedrenderfx;
}


if (!(flags & 2048))
{

m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel);
IEngineStudio.StudioSetHeader( m_pStudioHeader );
IEngineStudio.SetRenderModel( m_pRenderModel );

if (flags & STUDIO_RENDER)
{
// see if the bounding box lets us trivially reject, also sets
if (!IEngineStudio.StudioCheckBBox ())
return 0;

(*m_pModelsDrawn)++;
(*m_pStudioModelCount)++; // render data cache cookie

if (m_pStudioHeader->numbodyparts == 0)
return 1;
}

m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex );
StudioSetupBones( );
StudioSaveBones( );
m_pPlayerInfo->renderframe = m_nFrameCount;

m_pPlayerInfo = NULL;

if (flags & STUDIO_EVENTS)
{
StudioCalcAttachments( );
IEngineStudio.StudioClientEvents( );
// copy attachments into global entity array
if ( m_pCurrentEntity->index > 0 )
{
cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index );

memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 );
}
}

if (flags & STUDIO_RENDER)
{
if (m_pCvarHiModels->value && m_pRenderModel != m_pCurrentEntity->model )
{
// show highest resolution multiplayer model
m_pCurrentEntity->curstate.body = 255;
}

lighting.plightvec = dir;
IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting );

IEngineStudio.StudioEntityLight( &lighting );

// model and frame independant
IEngineStudio.StudioSetupLighting (&lighting);

m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex );

// get remap colors
m_nTopColor = m_pPlayerInfo->topcolor;
if (m_nTopColor < 0)
m_nTopColor = 0;
if (m_nTopColor > 360)
m_nTopColor = 360;
m_nBottomColor = m_pPlayerInfo->bottomcolor;
if (m_nBottomColor < 0)
m_nBottomColor = 0;
if (m_nBottomColor > 360)
m_nBottomColor = 360;

IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor );
StudioRenderModel( );

m_pPlayerInfo = NULL;

if (pplayer->weaponmodel)
{
cl_entity_t saveent = *m_pCurrentEntity;
model_t *pweaponmodel = IEngineStudio.GetModelByIndex( pplayer->weaponmodel );
m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (pweaponmodel);
IEngineStudio.StudioSetHeader( m_pStudioHeader );
StudioMergeBones( pweaponmodel );
IEngineStudio.StudioSetupLighting (&lighting);
StudioRenderModel( );
StudioCalcAttachments( );
*m_pCurrentEntity = saveent;
}
}
}

return 1;

В конце тела функции Init добавьте:

b_PlayerMarkerParsed = false;

Почти что все. Переходим в функцию StudioFxTransform, и в конструкции switch добавляем еще одну ветвь:

case 100:
//Mirror
{

switch (gHUD.Mirrors[mirror_id].type)
{
case 0:
transform[0][0] *= -1;
transform[0][1] *= -1;
transform[0][2] *= -1;

transform[0][3] = gHUD.Mirrors[mirror_id].origin[0]*2 - ent->origin[0];
break;
case 1:
transform[1][1] *= -1;
transform[1][0] *= -1;
transform[1][2] *= -1;

transform[1][3] = gHUD.Mirrors[mirror_id].origin[1]*2 - ent->origin[1];
break;
case 2:
default:
transform[2][2] *= -1;
transform[2][1] *= -1;
transform[2][0] *= -1;

transform[2][3] = gHUD.Mirrors[mirror_id].origin[2]*2 - ent->origin[2];
break;
}

break;
}

Все. Компилируйте оба проекта. В FGD-файл вашего мода добавьте строки:

@SolidClass base(Targetname, Appearflags, RenderFields, Global) = func_mirror : "Mirror surface"
[
armorvalue(integer) : "Mirror radius" : 256
frags(choices) : "Mirror type" : 0 =
[
0 : "Wall (X)"
1 : "Wall (Y)"
2 : "Floor/Ceil (Z)"
]
spawnflags(flags) =
[
1 : "Start OFF" : 0
2 : "Passable" : 0
]
_minlight(string) : "Minimum light level"
]
@PointClass size(-16 -16 -16, 16 16 16) studio("models/player_marker.mdl") = player_marker : "Mirror marker for player" []

Известные на данный момент баги зеркала:
1) Не отражается дульное пламя игрока
2) Корявые отражения у субмоделей (гм… В Retribution 2 я это как-то пофиксил, а как - хоть убей не помню :( )
3) Модели не отражаются, если далеко за спиной у игрока
4) Не отражается НИЧЕГО, кроме моделей :). Зато модели отражаюся ВСЕ, в т.ч. gibs и гильзы.


COPYRIGHT CHAIN STUDIOS, 2001-2004

Hosted by uCoz