ЗЕРКАЛА В 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 |